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;
144 SUB_UseTargets(self, NULL, NULL);
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;
209 void door_fire(entity this, entity actor, entity trigger)
212 if (this.owner != this)
213 objerror ("door_fire: this.owner != this");
215 if (this.spawnflags & DOOR_TOGGLE)
217 if (this.state == STATE_UP || this.state == STATE_TOP)
222 if (self.classname == "door")
228 door_rotating_go_down ();
231 } while ( (self != starte) && (self != world) );
237 // trigger all paired doors
241 if (self.classname == "door")
246 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
247 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
249 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
250 self.pos2 = '0 0 0' - self.pos2;
252 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
253 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
254 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
256 door_rotating_go_up ();
260 } while ( (self != starte) && (self != world) );
264 void door_use(entity this, entity actor, entity trigger)
266 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
269 WITHSELF(this.owner, door_fire(this.owner, actor, trigger));
272 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
274 if(this.spawnflags & DOOR_NOSPLASH)
275 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
277 this.health = this.health - damage;
281 // don't allow opening doors through damage if keys are required
285 if (this.health <= 0)
287 this.owner.health = this.owner.max_health;
288 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
289 door_use(this.owner, NULL, NULL);
293 .float door_finished;
305 if (!IS_PLAYER(other))
307 if (self.owner.door_finished > time)
310 self.owner.door_finished = time + 2;
313 if (!(self.owner.dmg) && (self.owner.message != ""))
315 if (IS_CLIENT(other))
316 centerprint(other, self.owner.message);
317 play2(other, self.owner.noise);
322 void door_generic_plat_blocked()
325 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
327 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
334 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
335 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
338 //Dont chamge direction for dead or dying stuff
339 if(IS_DEAD(other) && (other.takedamage == DAMAGE_NO))
343 if (self.state == STATE_DOWN)
344 door_rotating_go_up ();
346 door_rotating_go_down ();
352 //gib dying stuff just to make sure
353 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
354 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
360 void door_rotating_hit_top()
362 if (self.noise1 != "")
363 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
364 self.state = STATE_TOP;
365 if (self.spawnflags & DOOR_TOGGLE)
366 return; // don't come down automatically
367 self.SUB_THINK = door_rotating_go_down;
368 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
371 void door_rotating_hit_bottom()
373 if (self.noise1 != "")
374 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
375 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
377 self.pos2 = '0 0 0' - self.pos2;
380 self.state = STATE_BOTTOM;
383 void door_rotating_go_down()
385 if (self.noise2 != "")
386 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
389 self.takedamage = DAMAGE_YES;
390 self.health = self.max_health;
393 self.state = STATE_DOWN;
394 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
397 void door_rotating_go_up()
399 if (self.state == STATE_UP)
400 return; // already going up
402 if (self.state == STATE_TOP)
403 { // reset top wait time
404 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
407 if (self.noise2 != "")
408 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
409 self.state = STATE_UP;
410 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
413 oldmessage = self.message;
415 SUB_UseTargets(self, NULL, other); // TODO: is other needed here?
416 self.message = oldmessage;
421 =========================================
424 Spawned if a door lacks a real activator
425 =========================================
428 void door_trigger_touch()
430 if (other.health < 1)
432 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !IS_DEAD(other)))
434 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !IS_DEAD(other)))
438 if (time < self.door_finished)
441 // check if door is locked
442 if (!door_check_keys(self, other))
445 self.door_finished = time + 1;
447 door_use(this.owner, other, NULL);
450 void door_spawnfield(vector fmins, vector fmaxs)
453 vector t1 = fmins, t2 = fmaxs;
455 trigger = new(doortriggerfield);
456 trigger.movetype = MOVETYPE_NONE;
457 trigger.solid = SOLID_TRIGGER;
458 trigger.owner = self;
460 trigger.touch = door_trigger_touch;
462 trigger.trigger_touch = door_trigger_touch;
463 trigger.draw = trigger_draw_generic;
464 trigger.drawmask = MASK_NORMAL;
467 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
479 entity LinkDoors_nextent(entity cur, entity near, entity pass)
481 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
487 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
490 if((e1.absmin_x > e2.absmax_x + DELTA)
491 || (e1.absmin_y > e2.absmax_y + DELTA)
492 || (e1.absmin_z > e2.absmax_z + DELTA)
493 || (e2.absmin_x > e1.absmax_x + DELTA)
494 || (e2.absmin_y > e1.absmax_y + DELTA)
495 || (e2.absmin_z > e1.absmax_z + DELTA)
513 return; // already linked by another door
514 if (self.spawnflags & 4)
516 self.owner = self.enemy = self;
525 door_spawnfield(self.absmin, self.absmax);
527 return; // don't want to link this door
530 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
532 // set owner, and make a loop of the chain
533 LOG_TRACE("LinkDoors: linking doors:");
534 for(t = self; ; t = t.enemy)
536 LOG_TRACE(" ", etos(t));
546 // collect health, targetname, message, size
549 for(t = self; ; t = t.enemy)
551 if(t.health && !self.health)
552 self.health = t.health;
553 if((t.targetname != "") && (self.targetname == ""))
554 self.targetname = t.targetname;
555 if((t.message != "") && (self.message == ""))
556 self.message = t.message;
557 if (t.absmin_x < cmins_x)
558 cmins_x = t.absmin_x;
559 if (t.absmin_y < cmins_y)
560 cmins_y = t.absmin_y;
561 if (t.absmin_z < cmins_z)
562 cmins_z = t.absmin_z;
563 if (t.absmax_x > cmaxs_x)
564 cmaxs_x = t.absmax_x;
565 if (t.absmax_y > cmaxs_y)
566 cmaxs_y = t.absmax_y;
567 if (t.absmax_z > cmaxs_z)
568 cmaxs_z = t.absmax_z;
573 // distribute health, targetname, message
574 for(t = self; t; t = t.enemy)
576 t.health = self.health;
577 t.targetname = self.targetname;
578 t.message = self.message;
583 // shootable, or triggered doors just needed the owner/enemy links,
584 // they don't spawn a field
593 door_spawnfield(cmins, cmaxs);
596 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
599 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
600 if two doors touch, they are assumed to be connected and operate as a unit.
602 TOGGLE causes the door to wait in both the start and end states for a trigger event.
604 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).
606 GOLD_KEY causes the door to open only if the activator holds a gold key.
608 SILVER_KEY causes the door to open only if the activator holds a silver key.
610 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
611 "angle" determines the opening direction
612 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
613 "health" if set, door must be shot open
614 "speed" movement speed (100 default)
615 "wait" wait before returning (3 default, -1 = never return)
616 "lip" lip remaining at end of move (8 default)
617 "dmg" damage to inflict when blocked (2 default)
624 FIXME: only one sound set available at the time being
628 float door_send(entity to, float sf)
630 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
631 WriteByte(MSG_ENTITY, sf);
633 if(sf & SF_TRIGGER_INIT)
635 WriteString(MSG_ENTITY, self.classname);
636 WriteByte(MSG_ENTITY, self.spawnflags);
638 WriteString(MSG_ENTITY, self.model);
640 trigger_common_write(self, true);
642 WriteCoord(MSG_ENTITY, self.pos1_x);
643 WriteCoord(MSG_ENTITY, self.pos1_y);
644 WriteCoord(MSG_ENTITY, self.pos1_z);
645 WriteCoord(MSG_ENTITY, self.pos2_x);
646 WriteCoord(MSG_ENTITY, self.pos2_y);
647 WriteCoord(MSG_ENTITY, self.pos2_z);
649 WriteCoord(MSG_ENTITY, self.size_x);
650 WriteCoord(MSG_ENTITY, self.size_y);
651 WriteCoord(MSG_ENTITY, self.size_z);
653 WriteShort(MSG_ENTITY, self.wait);
654 WriteShort(MSG_ENTITY, self.speed);
655 WriteByte(MSG_ENTITY, self.lip);
656 WriteByte(MSG_ENTITY, self.state);
657 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
660 if(sf & SF_TRIGGER_RESET)
662 // client makes use of this, we do not
665 if(sf & SF_TRIGGER_UPDATE)
667 WriteCoord(MSG_ENTITY, self.origin_x);
668 WriteCoord(MSG_ENTITY, self.origin_y);
669 WriteCoord(MSG_ENTITY, self.origin_z);
671 WriteCoord(MSG_ENTITY, self.pos1_x);
672 WriteCoord(MSG_ENTITY, self.pos1_y);
673 WriteCoord(MSG_ENTITY, self.pos1_z);
674 WriteCoord(MSG_ENTITY, self.pos2_x);
675 WriteCoord(MSG_ENTITY, self.pos2_y);
676 WriteCoord(MSG_ENTITY, self.pos2_z);
684 // set size now, as everything is loaded
686 //Net_LinkEntity(self, false, 0, door_send);
690 void door_init_startopen()
692 SUB_SETORIGIN(self, self.pos2);
693 self.pos2 = self.pos1;
694 self.pos1 = self.origin;
697 self.SendFlags |= SF_TRIGGER_UPDATE;
701 void door_reset(entity this)
703 SUB_SETORIGIN(this, this.pos1);
704 this.SUB_VELOCITY = '0 0 0';
705 this.state = STATE_BOTTOM;
706 this.SUB_THINK = func_null;
707 this.SUB_NEXTTHINK = 0;
710 this.SendFlags |= SF_TRIGGER_RESET;
716 // spawnflags require key (for now only func_door)
719 // Quake 1 keys compatibility
720 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
721 self.itemkeys |= ITEM_KEY_BIT(0);
722 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
723 self.itemkeys |= ITEM_KEY_BIT(1);
727 self.max_health = self.health;
728 if (!InitMovingBrushTrigger())
730 self.effects |= EF_LOWPRECISION;
731 self.classname = "door";
734 self.noise = "misc/talk.wav";
735 if(self.noise3 == "")
736 self.noise3 = "misc/talk.wav";
737 precache_sound(self.noise);
738 precache_sound(self.noise3);
740 self.blocked = door_blocked;
741 self.use1 = door_use;
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(this);
800 trigger_draw_generic(this);
803 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
805 float sf = ReadByte();
807 if(sf & SF_TRIGGER_INIT)
809 this.classname = strzone(ReadString());
810 this.spawnflags = ReadByte();
812 this.mdl = strzone(ReadString());
813 _setmodel(this, this.mdl);
815 trigger_common_read(true);
834 this.wait = ReadShort();
835 this.speed = ReadShort();
836 this.lip = ReadByte();
837 this.state = ReadByte();
838 this.SUB_LTIME = ReadCoord();
840 this.solid = SOLID_BSP;
841 this.movetype = MOVETYPE_PUSH;
842 this.trigger_touch = door_touch;
843 this.draw = door_draw;
844 this.drawmask = MASK_NORMAL;
845 this.use1 = door_use;
849 if(this.spawnflags & DOOR_START_OPEN)
850 door_init_startopen();
852 this.move_time = time;
853 this.move_origin = this.origin;
854 this.move_movetype = MOVETYPE_PUSH;
855 this.move_angles = this.angles;
856 this.move_blocked = door_blocked;
859 if(sf & SF_TRIGGER_RESET)
864 if(sf & SF_TRIGGER_UPDATE)
866 this.origin_x = ReadCoord();
867 this.origin_y = ReadCoord();
868 this.origin_z = ReadCoord();
869 setorigin(this, this.origin);
870 this.move_origin = this.origin;
872 this.pos1_x = ReadCoord();
873 this.pos1_y = ReadCoord();
874 this.pos1_z = ReadCoord();
875 this.pos2_x = ReadCoord();
876 this.pos2_y = ReadCoord();
877 this.pos2_z = ReadCoord();