3 Doors are similar to buttons, but can spawn a fat trigger field around them
4 to open without a touch, and they link together to form simultanious
7 Door.owner is the master door. If there is only one door, it points to itself.
8 If multiple doors, all will point to a single one.
10 Door.enemy chains from the master door through all doors linked in the chain.
16 =============================================================================
20 =============================================================================
25 void() door_rotating_go_down;
26 void() door_rotating_go_up;
30 if((self.spawnflags & 8)
32 && (other.takedamage != DAMAGE_NO)
39 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
45 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
46 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
49 // don't change direction for dead or dying stuff
52 && (other.takedamage == DAMAGE_NO)
58 if (self.state == STATE_DOWN)
59 if (self.classname == "door")
64 door_rotating_go_up ();
67 if (self.classname == "door")
72 door_rotating_go_down ();
79 //gib dying stuff just to make sure
80 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
81 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
89 if (self.noise1 != "")
90 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
91 self.state = STATE_TOP;
92 if (self.spawnflags & DOOR_TOGGLE)
93 return; // don't come down automatically
94 if (self.classname == "door")
96 self.SUB_THINK = door_go_down;
99 self.SUB_THINK = door_rotating_go_down;
101 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
104 void door_hit_bottom()
106 if (self.noise1 != "")
107 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
108 self.state = STATE_BOTTOM;
113 if (self.noise2 != "")
114 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
117 self.takedamage = DAMAGE_YES;
118 self.health = self.max_health;
121 self.state = STATE_DOWN;
122 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
127 if (self.state == STATE_UP)
128 return; // already going up
130 if (self.state == STATE_TOP)
131 { // reset top wait time
132 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
136 if (self.noise2 != "")
137 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
138 self.state = STATE_UP;
139 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
142 oldmessage = self.message;
145 self.message = oldmessage;
150 =============================================================================
154 =============================================================================
157 bool door_check_keys(entity door, entity player)
166 // this door require a key
167 // only a player can have a key
168 if(!IS_PLAYER(player))
171 int valid = (door.itemkeys & player.itemkeys);
172 door.itemkeys &= ~valid; // only some of the needed keys were given
177 play2(player, SND(TALK));
178 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
186 if(player.key_door_messagetime <= time)
188 play2(player, SND(TALK));
189 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
190 player.key_door_messagetime = time + 2;
196 // door needs keys the player doesn't have
198 if(player.key_door_messagetime <= time)
200 play2(player, SND(TALK));
201 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
202 player.key_door_messagetime = time + 2;
213 if (self.owner != self)
214 objerror ("door_fire: self.owner != self");
216 if (self.spawnflags & DOOR_TOGGLE)
218 if (self.state == STATE_UP || self.state == STATE_TOP)
223 if (self.classname == "door")
229 door_rotating_go_down ();
232 } while ( (self != starte) && (self != world) );
238 // trigger all paired doors
242 if (self.classname == "door")
247 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
248 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
250 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
251 self.pos2 = '0 0 0' - self.pos2;
253 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
254 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
255 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
257 door_rotating_go_up ();
261 } while ( (self != starte) && (self != world) );
267 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
271 WITH(entity, self, self.owner, door_fire());
275 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
277 if(self.spawnflags & DOOR_NOSPLASH)
278 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
280 self.health = self.health - damage;
284 // don't allow opening doors through damage if keys are required
288 if (self.health <= 0)
290 self.owner.health = self.owner.max_health;
291 self.owner.takedamage = DAMAGE_NO; // wil be reset upon return
292 WITH(entity, self, self.owner, door_use());
307 if (!IS_PLAYER(other))
309 if (self.owner.attack_finished_single > time)
312 self.owner.attack_finished_single = time + 2;
315 if (!(self.owner.dmg) && (self.owner.message != ""))
317 if (IS_CLIENT(other))
318 centerprint(other, self.owner.message);
319 play2(other, self.owner.noise);
324 void door_generic_plat_blocked()
327 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
329 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
336 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
337 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
340 //Dont chamge direction for dead or dying stuff
341 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
345 if (self.state == STATE_DOWN)
346 door_rotating_go_up ();
348 door_rotating_go_down ();
354 //gib dying stuff just to make sure
355 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
356 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
362 void door_rotating_hit_top()
364 if (self.noise1 != "")
365 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
366 self.state = STATE_TOP;
367 if (self.spawnflags & DOOR_TOGGLE)
368 return; // don't come down automatically
369 self.SUB_THINK = door_rotating_go_down;
370 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
373 void door_rotating_hit_bottom()
375 if (self.noise1 != "")
376 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
377 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
379 self.pos2 = '0 0 0' - self.pos2;
382 self.state = STATE_BOTTOM;
385 void door_rotating_go_down()
387 if (self.noise2 != "")
388 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
391 self.takedamage = DAMAGE_YES;
392 self.health = self.max_health;
395 self.state = STATE_DOWN;
396 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
399 void door_rotating_go_up()
401 if (self.state == STATE_UP)
402 return; // already going up
404 if (self.state == STATE_TOP)
405 { // reset top wait time
406 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
409 if (self.noise2 != "")
410 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
411 self.state = STATE_UP;
412 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
415 oldmessage = self.message;
418 self.message = oldmessage;
423 =========================================
426 Spawned if a door lacks a real activator
427 =========================================
430 void door_trigger_touch()
432 if (other.health < 1)
434 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
436 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
440 if (time < self.attack_finished_single)
443 // check if door is locked
444 if (!door_check_keys(self, other))
447 self.attack_finished_single = time + 1;
455 void door_spawnfield(vector fmins, vector fmaxs)
458 vector t1 = fmins, t2 = fmaxs;
461 trigger.classname = "doortriggerfield";
462 trigger.movetype = MOVETYPE_NONE;
463 trigger.solid = SOLID_TRIGGER;
464 trigger.owner = self;
466 trigger.touch = door_trigger_touch;
468 trigger.trigger_touch = door_trigger_touch;
469 trigger.draw = trigger_draw_generic;
470 trigger.drawmask = MASK_NORMAL;
473 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
485 entity LinkDoors_nextent(entity cur, entity near, entity pass)
487 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
493 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
496 if((e1.absmin_x > e2.absmax_x + DELTA)
497 || (e1.absmin_y > e2.absmax_y + DELTA)
498 || (e1.absmin_z > e2.absmax_z + DELTA)
499 || (e2.absmin_x > e1.absmax_x + DELTA)
500 || (e2.absmin_y > e1.absmax_y + DELTA)
501 || (e2.absmin_z > e1.absmax_z + DELTA)
519 return; // already linked by another door
520 if (self.spawnflags & 4)
522 self.owner = self.enemy = self;
531 door_spawnfield(self.absmin, self.absmax);
533 return; // don't want to link this door
536 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
538 // set owner, and make a loop of the chain
539 LOG_TRACE("LinkDoors: linking doors:");
540 for(t = self; ; t = t.enemy)
542 LOG_TRACE(" ", etos(t));
552 // collect health, targetname, message, size
555 for(t = self; ; t = t.enemy)
557 if(t.health && !self.health)
558 self.health = t.health;
559 if((t.targetname != "") && (self.targetname == ""))
560 self.targetname = t.targetname;
561 if((t.message != "") && (self.message == ""))
562 self.message = t.message;
563 if (t.absmin_x < cmins_x)
564 cmins_x = t.absmin_x;
565 if (t.absmin_y < cmins_y)
566 cmins_y = t.absmin_y;
567 if (t.absmin_z < cmins_z)
568 cmins_z = t.absmin_z;
569 if (t.absmax_x > cmaxs_x)
570 cmaxs_x = t.absmax_x;
571 if (t.absmax_y > cmaxs_y)
572 cmaxs_y = t.absmax_y;
573 if (t.absmax_z > cmaxs_z)
574 cmaxs_z = t.absmax_z;
579 // distribute health, targetname, message
580 for(t = self; t; t = t.enemy)
582 t.health = self.health;
583 t.targetname = self.targetname;
584 t.message = self.message;
589 // shootable, or triggered doors just needed the owner/enemy links,
590 // they don't spawn a field
599 door_spawnfield(cmins, cmaxs);
603 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
604 if two doors touch, they are assumed to be connected and operate as a unit.
606 TOGGLE causes the door to wait in both the start and end states for a trigger event.
608 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
610 GOLD_KEY causes the door to open only if the activator holds a gold key.
612 SILVER_KEY causes the door to open only if the activator holds a silver key.
614 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
615 "angle" determines the opening direction
616 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
617 "health" if set, door must be shot open
618 "speed" movement speed (100 default)
619 "wait" wait before returning (3 default, -1 = never return)
620 "lip" lip remaining at end of move (8 default)
621 "dmg" damage to inflict when blocked (2 default)
628 FIXME: only one sound set available at the time being
632 float door_send(entity to, float sf)
634 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
635 WriteByte(MSG_ENTITY, sf);
637 if(sf & SF_TRIGGER_INIT)
639 WriteString(MSG_ENTITY, self.classname);
640 WriteByte(MSG_ENTITY, self.spawnflags);
642 WriteString(MSG_ENTITY, self.model);
644 trigger_common_write(true);
646 WriteCoord(MSG_ENTITY, self.pos1_x);
647 WriteCoord(MSG_ENTITY, self.pos1_y);
648 WriteCoord(MSG_ENTITY, self.pos1_z);
649 WriteCoord(MSG_ENTITY, self.pos2_x);
650 WriteCoord(MSG_ENTITY, self.pos2_y);
651 WriteCoord(MSG_ENTITY, self.pos2_z);
653 WriteCoord(MSG_ENTITY, self.size_x);
654 WriteCoord(MSG_ENTITY, self.size_y);
655 WriteCoord(MSG_ENTITY, self.size_z);
657 WriteShort(MSG_ENTITY, self.wait);
658 WriteShort(MSG_ENTITY, self.speed);
659 WriteByte(MSG_ENTITY, self.lip);
660 WriteByte(MSG_ENTITY, self.state);
661 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
664 if(sf & SF_TRIGGER_RESET)
666 // client makes use of this, we do not
669 if(sf & SF_TRIGGER_UPDATE)
671 WriteCoord(MSG_ENTITY, self.origin_x);
672 WriteCoord(MSG_ENTITY, self.origin_y);
673 WriteCoord(MSG_ENTITY, self.origin_z);
675 WriteCoord(MSG_ENTITY, self.pos1_x);
676 WriteCoord(MSG_ENTITY, self.pos1_y);
677 WriteCoord(MSG_ENTITY, self.pos1_z);
678 WriteCoord(MSG_ENTITY, self.pos2_x);
679 WriteCoord(MSG_ENTITY, self.pos2_y);
680 WriteCoord(MSG_ENTITY, self.pos2_z);
688 // set size now, as everything is loaded
690 //Net_LinkEntity(self, false, 0, door_send);
694 void door_init_startopen()
696 SUB_SETORIGIN(self, self.pos2);
697 self.pos2 = self.pos1;
698 self.pos1 = self.origin;
701 self.SendFlags |= SF_TRIGGER_UPDATE;
707 SUB_SETORIGIN(self, self.pos1);
708 self.SUB_VELOCITY = '0 0 0';
709 self.state = STATE_BOTTOM;
710 self.SUB_THINK = func_null;
711 self.SUB_NEXTTHINK = 0;
714 self.SendFlags |= SF_TRIGGER_RESET;
720 // spawnflags require key (for now only func_door)
723 // Quake 1 keys compatibility
724 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
725 self.itemkeys |= ITEM_KEY_BIT(0);
726 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
727 self.itemkeys |= ITEM_KEY_BIT(1);
731 self.max_health = self.health;
732 if (!InitMovingBrushTrigger())
734 self.effects |= EF_LOWPRECISION;
735 self.classname = "door";
738 self.noise = "misc/talk.wav";
740 self.blocked = door_blocked;
743 if(self.dmg && (self.message == ""))
744 self.message = "was squished";
745 if(self.dmg && (self.message2 == ""))
746 self.message2 = "was squished by";
750 precache_sound ("plats/medplat1.wav");
751 precache_sound ("plats/medplat2.wav");
752 self.noise2 = "plats/medplat1.wav";
753 self.noise1 = "plats/medplat2.wav";
763 self.pos1 = self.SUB_ORIGIN;
764 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
766 if(self.spawnflags & DOOR_NONSOLID)
767 self.solid = SOLID_NOT;
769 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
770 // but spawn in the open position
771 if (self.spawnflags & DOOR_START_OPEN)
772 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
774 self.state = STATE_BOTTOM;
778 self.takedamage = DAMAGE_YES;
779 self.event_damage = door_damage;
785 self.touch = door_touch;
787 // LinkDoors can't be done until all of the doors have been spawned, so
788 // the sizes can be detected properly.
789 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
791 self.reset = door_reset;
796 void door_draw(entity this)
798 Movetype_Physics_NoMatchServer();
800 trigger_draw_generic(this);
805 float sf = ReadByte();
807 if(sf & SF_TRIGGER_INIT)
809 self.classname = strzone(ReadString());
810 self.spawnflags = ReadByte();
812 self.mdl = strzone(ReadString());
813 _setmodel(self, self.mdl);
815 trigger_common_read(true);
817 self.pos1_x = ReadCoord();
818 self.pos1_y = ReadCoord();
819 self.pos1_z = ReadCoord();
820 self.pos2_x = ReadCoord();
821 self.pos2_y = ReadCoord();
822 self.pos2_z = ReadCoord();
824 self.size_x = ReadCoord();
825 self.size_y = ReadCoord();
826 self.size_z = ReadCoord();
828 self.wait = ReadShort();
829 self.speed = ReadShort();
830 self.lip = ReadByte();
831 self.state = ReadByte();
832 self.SUB_LTIME = ReadCoord();
834 self.solid = SOLID_BSP;
835 self.movetype = MOVETYPE_PUSH;
836 self.trigger_touch = door_touch;
837 self.draw = door_draw;
838 self.drawmask = MASK_NORMAL;
843 if(self.spawnflags & DOOR_START_OPEN)
844 door_init_startopen();
846 self.move_time = time;
847 self.move_origin = self.origin;
848 self.move_movetype = MOVETYPE_PUSH;
849 self.move_angles = self.angles;
850 self.move_blocked = door_blocked;
853 if(sf & SF_TRIGGER_RESET)
858 if(sf & SF_TRIGGER_UPDATE)
860 self.origin_x = ReadCoord();
861 self.origin_y = ReadCoord();
862 self.origin_z = ReadCoord();
863 setorigin(self, self.origin);
864 self.move_origin = self.origin;
866 self.pos1_x = ReadCoord();
867 self.pos1_y = ReadCoord();
868 self.pos1_z = ReadCoord();
869 self.pos2_x = ReadCoord();
870 self.pos2_y = ReadCoord();
871 self.pos2_z = ReadCoord();