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, door.noise3);
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, door.noise3);
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());
296 .float door_finished;
308 if (!IS_PLAYER(other))
310 if (self.owner.door_finished > time)
313 self.owner.door_finished = time + 2;
316 if (!(self.owner.dmg) && (self.owner.message != ""))
318 if (IS_CLIENT(other))
319 centerprint(other, self.owner.message);
320 play2(other, self.owner.noise);
325 void door_generic_plat_blocked()
328 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
330 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
337 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
338 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
341 //Dont chamge direction for dead or dying stuff
342 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
346 if (self.state == STATE_DOWN)
347 door_rotating_go_up ();
349 door_rotating_go_down ();
355 //gib dying stuff just to make sure
356 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
357 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
363 void door_rotating_hit_top()
365 if (self.noise1 != "")
366 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
367 self.state = STATE_TOP;
368 if (self.spawnflags & DOOR_TOGGLE)
369 return; // don't come down automatically
370 self.SUB_THINK = door_rotating_go_down;
371 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
374 void door_rotating_hit_bottom()
376 if (self.noise1 != "")
377 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
378 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
380 self.pos2 = '0 0 0' - self.pos2;
383 self.state = STATE_BOTTOM;
386 void door_rotating_go_down()
388 if (self.noise2 != "")
389 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
392 self.takedamage = DAMAGE_YES;
393 self.health = self.max_health;
396 self.state = STATE_DOWN;
397 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
400 void door_rotating_go_up()
402 if (self.state == STATE_UP)
403 return; // already going up
405 if (self.state == STATE_TOP)
406 { // reset top wait time
407 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
410 if (self.noise2 != "")
411 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
412 self.state = STATE_UP;
413 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
416 oldmessage = self.message;
419 self.message = oldmessage;
424 =========================================
427 Spawned if a door lacks a real activator
428 =========================================
431 void door_trigger_touch()
433 if (other.health < 1)
435 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
437 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
441 if (time < self.door_finished)
444 // check if door is locked
445 if (!door_check_keys(self, other))
448 self.door_finished = time + 1;
456 void door_spawnfield(vector fmins, vector fmaxs)
459 vector t1 = fmins, t2 = fmaxs;
462 trigger.classname = "doortriggerfield";
463 trigger.movetype = MOVETYPE_NONE;
464 trigger.solid = SOLID_TRIGGER;
465 trigger.owner = self;
467 trigger.touch = door_trigger_touch;
469 trigger.trigger_touch = door_trigger_touch;
470 trigger.draw = trigger_draw_generic;
471 trigger.drawmask = MASK_NORMAL;
474 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
486 entity LinkDoors_nextent(entity cur, entity near, entity pass)
488 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
494 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
497 if((e1.absmin_x > e2.absmax_x + DELTA)
498 || (e1.absmin_y > e2.absmax_y + DELTA)
499 || (e1.absmin_z > e2.absmax_z + DELTA)
500 || (e2.absmin_x > e1.absmax_x + DELTA)
501 || (e2.absmin_y > e1.absmax_y + DELTA)
502 || (e2.absmin_z > e1.absmax_z + DELTA)
520 return; // already linked by another door
521 if (self.spawnflags & 4)
523 self.owner = self.enemy = self;
532 door_spawnfield(self.absmin, self.absmax);
534 return; // don't want to link this door
537 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
539 // set owner, and make a loop of the chain
540 LOG_TRACE("LinkDoors: linking doors:");
541 for(t = self; ; t = t.enemy)
543 LOG_TRACE(" ", etos(t));
553 // collect health, targetname, message, size
556 for(t = self; ; t = t.enemy)
558 if(t.health && !self.health)
559 self.health = t.health;
560 if((t.targetname != "") && (self.targetname == ""))
561 self.targetname = t.targetname;
562 if((t.message != "") && (self.message == ""))
563 self.message = t.message;
564 if (t.absmin_x < cmins_x)
565 cmins_x = t.absmin_x;
566 if (t.absmin_y < cmins_y)
567 cmins_y = t.absmin_y;
568 if (t.absmin_z < cmins_z)
569 cmins_z = t.absmin_z;
570 if (t.absmax_x > cmaxs_x)
571 cmaxs_x = t.absmax_x;
572 if (t.absmax_y > cmaxs_y)
573 cmaxs_y = t.absmax_y;
574 if (t.absmax_z > cmaxs_z)
575 cmaxs_z = t.absmax_z;
580 // distribute health, targetname, message
581 for(t = self; t; t = t.enemy)
583 t.health = self.health;
584 t.targetname = self.targetname;
585 t.message = self.message;
590 // shootable, or triggered doors just needed the owner/enemy links,
591 // they don't spawn a field
600 door_spawnfield(cmins, cmaxs);
604 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
605 if two doors touch, they are assumed to be connected and operate as a unit.
607 TOGGLE causes the door to wait in both the start and end states for a trigger event.
609 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).
611 GOLD_KEY causes the door to open only if the activator holds a gold key.
613 SILVER_KEY causes the door to open only if the activator holds a silver key.
615 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
616 "angle" determines the opening direction
617 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
618 "health" if set, door must be shot open
619 "speed" movement speed (100 default)
620 "wait" wait before returning (3 default, -1 = never return)
621 "lip" lip remaining at end of move (8 default)
622 "dmg" damage to inflict when blocked (2 default)
629 FIXME: only one sound set available at the time being
633 float door_send(entity to, float sf)
635 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
636 WriteByte(MSG_ENTITY, sf);
638 if(sf & SF_TRIGGER_INIT)
640 WriteString(MSG_ENTITY, self.classname);
641 WriteByte(MSG_ENTITY, self.spawnflags);
643 WriteString(MSG_ENTITY, self.model);
645 trigger_common_write(true);
647 WriteCoord(MSG_ENTITY, self.pos1_x);
648 WriteCoord(MSG_ENTITY, self.pos1_y);
649 WriteCoord(MSG_ENTITY, self.pos1_z);
650 WriteCoord(MSG_ENTITY, self.pos2_x);
651 WriteCoord(MSG_ENTITY, self.pos2_y);
652 WriteCoord(MSG_ENTITY, self.pos2_z);
654 WriteCoord(MSG_ENTITY, self.size_x);
655 WriteCoord(MSG_ENTITY, self.size_y);
656 WriteCoord(MSG_ENTITY, self.size_z);
658 WriteShort(MSG_ENTITY, self.wait);
659 WriteShort(MSG_ENTITY, self.speed);
660 WriteByte(MSG_ENTITY, self.lip);
661 WriteByte(MSG_ENTITY, self.state);
662 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
665 if(sf & SF_TRIGGER_RESET)
667 // client makes use of this, we do not
670 if(sf & SF_TRIGGER_UPDATE)
672 WriteCoord(MSG_ENTITY, self.origin_x);
673 WriteCoord(MSG_ENTITY, self.origin_y);
674 WriteCoord(MSG_ENTITY, self.origin_z);
676 WriteCoord(MSG_ENTITY, self.pos1_x);
677 WriteCoord(MSG_ENTITY, self.pos1_y);
678 WriteCoord(MSG_ENTITY, self.pos1_z);
679 WriteCoord(MSG_ENTITY, self.pos2_x);
680 WriteCoord(MSG_ENTITY, self.pos2_y);
681 WriteCoord(MSG_ENTITY, self.pos2_z);
689 // set size now, as everything is loaded
691 //Net_LinkEntity(self, false, 0, door_send);
695 void door_init_startopen()
697 SUB_SETORIGIN(self, self.pos2);
698 self.pos2 = self.pos1;
699 self.pos1 = self.origin;
702 self.SendFlags |= SF_TRIGGER_UPDATE;
708 SUB_SETORIGIN(self, self.pos1);
709 self.SUB_VELOCITY = '0 0 0';
710 self.state = STATE_BOTTOM;
711 self.SUB_THINK = func_null;
712 self.SUB_NEXTTHINK = 0;
715 self.SendFlags |= SF_TRIGGER_RESET;
721 // spawnflags require key (for now only func_door)
724 // Quake 1 keys compatibility
725 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
726 self.itemkeys |= ITEM_KEY_BIT(0);
727 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
728 self.itemkeys |= ITEM_KEY_BIT(1);
732 self.max_health = self.health;
733 if (!InitMovingBrushTrigger())
735 self.effects |= EF_LOWPRECISION;
736 self.classname = "door";
739 self.noise = "misc/talk.wav";
740 if(self.noise3 == "")
741 self.noise3 = "misc/talk.wav";
742 precache_sound(self.noise);
743 precache_sound(self.noise3);
745 self.blocked = door_blocked;
748 if(self.dmg && (self.message == ""))
749 self.message = "was squished";
750 if(self.dmg && (self.message2 == ""))
751 self.message2 = "was squished by";
755 precache_sound ("plats/medplat1.wav");
756 precache_sound ("plats/medplat2.wav");
757 self.noise2 = "plats/medplat1.wav";
758 self.noise1 = "plats/medplat2.wav";
768 self.pos1 = self.SUB_ORIGIN;
769 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
771 if(self.spawnflags & DOOR_NONSOLID)
772 self.solid = SOLID_NOT;
774 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
775 // but spawn in the open position
776 if (self.spawnflags & DOOR_START_OPEN)
777 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
779 self.state = STATE_BOTTOM;
783 self.takedamage = DAMAGE_YES;
784 self.event_damage = door_damage;
790 self.touch = door_touch;
792 // LinkDoors can't be done until all of the doors have been spawned, so
793 // the sizes can be detected properly.
794 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
796 self.reset = door_reset;
801 void door_draw(entity this)
803 Movetype_Physics_NoMatchServer();
805 trigger_draw_generic(this);
810 float sf = ReadByte();
812 if(sf & SF_TRIGGER_INIT)
814 self.classname = strzone(ReadString());
815 self.spawnflags = ReadByte();
817 self.mdl = strzone(ReadString());
818 _setmodel(self, self.mdl);
820 trigger_common_read(true);
822 self.pos1_x = ReadCoord();
823 self.pos1_y = ReadCoord();
824 self.pos1_z = ReadCoord();
825 self.pos2_x = ReadCoord();
826 self.pos2_y = ReadCoord();
827 self.pos2_z = ReadCoord();
829 self.size_x = ReadCoord();
830 self.size_y = ReadCoord();
831 self.size_z = ReadCoord();
833 self.wait = ReadShort();
834 self.speed = ReadShort();
835 self.lip = ReadByte();
836 self.state = ReadByte();
837 self.SUB_LTIME = ReadCoord();
839 self.solid = SOLID_BSP;
840 self.movetype = MOVETYPE_PUSH;
841 self.trigger_touch = door_touch;
842 self.draw = door_draw;
843 self.drawmask = MASK_NORMAL;
848 if(self.spawnflags & DOOR_START_OPEN)
849 door_init_startopen();
851 self.move_time = time;
852 self.move_origin = self.origin;
853 self.move_movetype = MOVETYPE_PUSH;
854 self.move_angles = self.angles;
855 self.move_blocked = door_blocked;
858 if(sf & SF_TRIGGER_RESET)
863 if(sf & SF_TRIGGER_UPDATE)
865 self.origin_x = ReadCoord();
866 self.origin_y = ReadCoord();
867 self.origin_z = ReadCoord();
868 setorigin(self, self.origin);
869 self.move_origin = self.origin;
871 self.pos1_x = ReadCoord();
872 self.pos1_y = ReadCoord();
873 self.pos1_z = ReadCoord();
874 self.pos2_x = ReadCoord();
875 self.pos2_y = ReadCoord();
876 self.pos2_z = ReadCoord();