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);
602 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
605 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
606 if two doors touch, they are assumed to be connected and operate as a unit.
608 TOGGLE causes the door to wait in both the start and end states for a trigger event.
610 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).
612 GOLD_KEY causes the door to open only if the activator holds a gold key.
614 SILVER_KEY causes the door to open only if the activator holds a silver key.
616 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
617 "angle" determines the opening direction
618 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
619 "health" if set, door must be shot open
620 "speed" movement speed (100 default)
621 "wait" wait before returning (3 default, -1 = never return)
622 "lip" lip remaining at end of move (8 default)
623 "dmg" damage to inflict when blocked (2 default)
630 FIXME: only one sound set available at the time being
634 float door_send(entity to, float sf)
636 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
637 WriteByte(MSG_ENTITY, sf);
639 if(sf & SF_TRIGGER_INIT)
641 WriteString(MSG_ENTITY, self.classname);
642 WriteByte(MSG_ENTITY, self.spawnflags);
644 WriteString(MSG_ENTITY, self.model);
646 trigger_common_write(self, true);
648 WriteCoord(MSG_ENTITY, self.pos1_x);
649 WriteCoord(MSG_ENTITY, self.pos1_y);
650 WriteCoord(MSG_ENTITY, self.pos1_z);
651 WriteCoord(MSG_ENTITY, self.pos2_x);
652 WriteCoord(MSG_ENTITY, self.pos2_y);
653 WriteCoord(MSG_ENTITY, self.pos2_z);
655 WriteCoord(MSG_ENTITY, self.size_x);
656 WriteCoord(MSG_ENTITY, self.size_y);
657 WriteCoord(MSG_ENTITY, self.size_z);
659 WriteShort(MSG_ENTITY, self.wait);
660 WriteShort(MSG_ENTITY, self.speed);
661 WriteByte(MSG_ENTITY, self.lip);
662 WriteByte(MSG_ENTITY, self.state);
663 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
666 if(sf & SF_TRIGGER_RESET)
668 // client makes use of this, we do not
671 if(sf & SF_TRIGGER_UPDATE)
673 WriteCoord(MSG_ENTITY, self.origin_x);
674 WriteCoord(MSG_ENTITY, self.origin_y);
675 WriteCoord(MSG_ENTITY, self.origin_z);
677 WriteCoord(MSG_ENTITY, self.pos1_x);
678 WriteCoord(MSG_ENTITY, self.pos1_y);
679 WriteCoord(MSG_ENTITY, self.pos1_z);
680 WriteCoord(MSG_ENTITY, self.pos2_x);
681 WriteCoord(MSG_ENTITY, self.pos2_y);
682 WriteCoord(MSG_ENTITY, self.pos2_z);
690 // set size now, as everything is loaded
692 //Net_LinkEntity(self, false, 0, door_send);
696 void door_init_startopen()
698 SUB_SETORIGIN(self, self.pos2);
699 self.pos2 = self.pos1;
700 self.pos1 = self.origin;
703 self.SendFlags |= SF_TRIGGER_UPDATE;
707 void door_reset(entity this)
709 SUB_SETORIGIN(this, this.pos1);
710 this.SUB_VELOCITY = '0 0 0';
711 this.state = STATE_BOTTOM;
712 this.SUB_THINK = func_null;
713 this.SUB_NEXTTHINK = 0;
716 this.SendFlags |= SF_TRIGGER_RESET;
722 // spawnflags require key (for now only func_door)
725 // Quake 1 keys compatibility
726 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
727 self.itemkeys |= ITEM_KEY_BIT(0);
728 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
729 self.itemkeys |= ITEM_KEY_BIT(1);
733 self.max_health = self.health;
734 if (!InitMovingBrushTrigger())
736 self.effects |= EF_LOWPRECISION;
737 self.classname = "door";
740 self.noise = "misc/talk.wav";
741 if(self.noise3 == "")
742 self.noise3 = "misc/talk.wav";
743 precache_sound(self.noise);
744 precache_sound(self.noise3);
746 self.blocked = door_blocked;
749 if(self.dmg && (self.message == ""))
750 self.message = "was squished";
751 if(self.dmg && (self.message2 == ""))
752 self.message2 = "was squished by";
756 precache_sound ("plats/medplat1.wav");
757 precache_sound ("plats/medplat2.wav");
758 self.noise2 = "plats/medplat1.wav";
759 self.noise1 = "plats/medplat2.wav";
769 self.pos1 = self.SUB_ORIGIN;
770 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
772 if(self.spawnflags & DOOR_NONSOLID)
773 self.solid = SOLID_NOT;
775 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
776 // but spawn in the open position
777 if (self.spawnflags & DOOR_START_OPEN)
778 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
780 self.state = STATE_BOTTOM;
784 self.takedamage = DAMAGE_YES;
785 self.event_damage = door_damage;
791 self.touch = door_touch;
793 // LinkDoors can't be done until all of the doors have been spawned, so
794 // the sizes can be detected properly.
795 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
797 self.reset = door_reset;
802 void door_draw(entity this)
804 Movetype_Physics_NoMatchServer(this);
806 trigger_draw_generic(this);
809 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
811 float sf = ReadByte();
813 if(sf & SF_TRIGGER_INIT)
815 this.classname = strzone(ReadString());
816 this.spawnflags = ReadByte();
818 this.mdl = strzone(ReadString());
819 _setmodel(this, this.mdl);
821 trigger_common_read(true);
840 this.wait = ReadShort();
841 this.speed = ReadShort();
842 this.lip = ReadByte();
843 this.state = ReadByte();
844 this.SUB_LTIME = ReadCoord();
846 this.solid = SOLID_BSP;
847 this.movetype = MOVETYPE_PUSH;
848 this.trigger_touch = door_touch;
849 this.draw = door_draw;
850 this.drawmask = MASK_NORMAL;
855 if(this.spawnflags & DOOR_START_OPEN)
856 door_init_startopen();
858 this.move_time = time;
859 this.move_origin = this.origin;
860 this.move_movetype = MOVETYPE_PUSH;
861 this.move_angles = this.angles;
862 this.move_blocked = door_blocked;
865 if(sf & SF_TRIGGER_RESET)
870 if(sf & SF_TRIGGER_UPDATE)
872 this.origin_x = ReadCoord();
873 this.origin_y = ReadCoord();
874 this.origin_z = ReadCoord();
875 setorigin(this, this.origin);
876 this.move_origin = this.origin;
878 this.pos1_x = ReadCoord();
879 this.pos1_y = ReadCoord();
880 this.pos1_z = ReadCoord();
881 this.pos2_x = ReadCoord();
882 this.pos2_y = ReadCoord();
883 this.pos2_z = ReadCoord();