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;
461 trigger = new(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 WriteHeader(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";
739 if(self.noise3 == "")
740 self.noise3 = "misc/talk.wav";
741 precache_sound(self.noise);
742 precache_sound(self.noise3);
744 self.blocked = door_blocked;
747 if(self.dmg && (self.message == ""))
748 self.message = "was squished";
749 if(self.dmg && (self.message2 == ""))
750 self.message2 = "was squished by";
754 precache_sound ("plats/medplat1.wav");
755 precache_sound ("plats/medplat2.wav");
756 self.noise2 = "plats/medplat1.wav";
757 self.noise1 = "plats/medplat2.wav";
767 self.pos1 = self.SUB_ORIGIN;
768 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
770 if(self.spawnflags & DOOR_NONSOLID)
771 self.solid = SOLID_NOT;
773 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
774 // but spawn in the open position
775 if (self.spawnflags & DOOR_START_OPEN)
776 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
778 self.state = STATE_BOTTOM;
782 self.takedamage = DAMAGE_YES;
783 self.event_damage = door_damage;
789 self.touch = door_touch;
791 // LinkDoors can't be done until all of the doors have been spawned, so
792 // the sizes can be detected properly.
793 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
795 self.reset = door_reset;
800 void door_draw(entity this)
802 Movetype_Physics_NoMatchServer();
804 trigger_draw_generic(this);
807 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
809 float sf = ReadByte();
811 if(sf & SF_TRIGGER_INIT)
813 self.classname = strzone(ReadString());
814 self.spawnflags = ReadByte();
816 self.mdl = strzone(ReadString());
817 _setmodel(self, self.mdl);
819 trigger_common_read(true);
821 self.pos1_x = ReadCoord();
822 self.pos1_y = ReadCoord();
823 self.pos1_z = ReadCoord();
824 self.pos2_x = ReadCoord();
825 self.pos2_y = ReadCoord();
826 self.pos2_z = ReadCoord();
828 self.size_x = ReadCoord();
829 self.size_y = ReadCoord();
830 self.size_z = ReadCoord();
832 self.wait = ReadShort();
833 self.speed = ReadShort();
834 self.lip = ReadByte();
835 self.state = ReadByte();
836 self.SUB_LTIME = ReadCoord();
838 self.solid = SOLID_BSP;
839 self.movetype = MOVETYPE_PUSH;
840 self.trigger_touch = door_touch;
841 self.draw = door_draw;
842 self.drawmask = MASK_NORMAL;
847 if(self.spawnflags & DOOR_START_OPEN)
848 door_init_startopen();
850 self.move_time = time;
851 self.move_origin = self.origin;
852 self.move_movetype = MOVETYPE_PUSH;
853 self.move_angles = self.angles;
854 self.move_blocked = door_blocked;
857 if(sf & SF_TRIGGER_RESET)
862 if(sf & SF_TRIGGER_UPDATE)
864 self.origin_x = ReadCoord();
865 self.origin_y = ReadCoord();
866 self.origin_z = ReadCoord();
867 setorigin(self, self.origin);
868 self.move_origin = self.origin;
870 self.pos1_x = ReadCoord();
871 self.pos1_y = ReadCoord();
872 self.pos1_z = ReadCoord();
873 self.pos2_x = ReadCoord();
874 self.pos2_y = ReadCoord();
875 self.pos2_z = ReadCoord();