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 float door_check_keys(void)
170 // this door require a key
171 // only a player can have a key
172 if (!IS_PLAYER(other))
176 if (item_keys_usekey(door, other))
178 // some keys were used
179 if (other.key_door_messagetime <= time)
181 play2(other, SND(TALK));
182 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
183 other.key_door_messagetime = time + 2;
189 if (other.key_door_messagetime <= time)
191 play2(other, SND(TALK));
192 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
194 other.key_door_messagetime = time + 2;
202 // door is now unlocked
203 play2(other, SND(TALK));
204 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
216 if (self.owner != self)
217 objerror ("door_fire: self.owner != self");
219 if (self.spawnflags & DOOR_TOGGLE)
221 if (self.state == STATE_UP || self.state == STATE_TOP)
226 if (self.classname == "door")
232 door_rotating_go_down ();
235 } while ( (self != starte) && (self != world) );
241 // trigger all paired doors
245 if (self.classname == "door")
250 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
251 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
253 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
254 self.pos2 = '0 0 0' - self.pos2;
256 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
257 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
258 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
260 door_rotating_go_up ();
264 } while ( (self != starte) && (self != world) );
270 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
274 WITH(entity, self, self.owner, door_fire());
278 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
280 if(self.spawnflags & DOOR_NOSPLASH)
281 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
283 self.health = self.health - damage;
287 // don't allow opening doors through damage if keys are required
291 if (self.health <= 0)
293 self.owner.health = self.owner.max_health;
294 self.owner.takedamage = DAMAGE_NO; // wil be reset upon return
295 WITH(entity, self, self.owner, door_use());
310 if (!IS_PLAYER(other))
312 if (self.owner.attack_finished_single > time)
315 self.owner.attack_finished_single = time + 2;
318 if (!(self.owner.dmg) && (self.owner.message != ""))
320 if (IS_CLIENT(other))
321 centerprint(other, self.owner.message);
322 play2(other, SND(TALK));
327 void door_generic_plat_blocked()
330 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
332 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
339 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
340 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
343 //Dont chamge direction for dead or dying stuff
344 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
348 if (self.state == STATE_DOWN)
349 door_rotating_go_up ();
351 door_rotating_go_down ();
357 //gib dying stuff just to make sure
358 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
359 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
365 void door_rotating_hit_top()
367 if (self.noise1 != "")
368 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
369 self.state = STATE_TOP;
370 if (self.spawnflags & DOOR_TOGGLE)
371 return; // don't come down automatically
372 self.SUB_THINK = door_rotating_go_down;
373 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
376 void door_rotating_hit_bottom()
378 if (self.noise1 != "")
379 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
380 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
382 self.pos2 = '0 0 0' - self.pos2;
385 self.state = STATE_BOTTOM;
388 void door_rotating_go_down()
390 if (self.noise2 != "")
391 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
394 self.takedamage = DAMAGE_YES;
395 self.health = self.max_health;
398 self.state = STATE_DOWN;
399 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
402 void door_rotating_go_up()
404 if (self.state == STATE_UP)
405 return; // already going up
407 if (self.state == STATE_TOP)
408 { // reset top wait time
409 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
412 if (self.noise2 != "")
413 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
414 self.state = STATE_UP;
415 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
418 oldmessage = self.message;
421 self.message = oldmessage;
426 =========================================
429 Spawned if a door lacks a real activator
430 =========================================
433 void door_trigger_touch()
435 if (other.health < 1)
437 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
439 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
443 if (time < self.attack_finished_single)
446 // check if door is locked
447 if (!door_check_keys())
450 self.attack_finished_single = time + 1;
458 void spawn_field(vector fmins, vector fmaxs)
461 vector t1 = fmins, t2 = fmaxs;
464 trigger.classname = "doortriggerfield";
465 trigger.movetype = MOVETYPE_NONE;
466 trigger.solid = SOLID_TRIGGER;
467 trigger.owner = self;
469 trigger.touch = door_trigger_touch;
471 trigger.trigger_touch = door_trigger_touch;
472 trigger.draw = trigger_draw_generic;
473 trigger.drawmask = MASK_NORMAL;
476 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
488 entity LinkDoors_nextent(entity cur, entity near, entity pass)
490 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
496 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
499 if((e1.absmin_x > e2.absmax_x + DELTA)
500 || (e1.absmin_y > e2.absmax_y + DELTA)
501 || (e1.absmin_z > e2.absmax_z + DELTA)
502 || (e2.absmin_x > e1.absmax_x + DELTA)
503 || (e2.absmin_y > e1.absmax_y + DELTA)
504 || (e2.absmin_z > e1.absmax_z + DELTA)
522 return; // already linked by another door
523 if (self.spawnflags & 4)
525 self.owner = self.enemy = self;
534 spawn_field(self.absmin, self.absmax);
536 return; // don't want to link this door
539 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
541 // set owner, and make a loop of the chain
542 LOG_TRACE("LinkDoors: linking doors:");
543 for(t = self; ; t = t.enemy)
545 LOG_TRACE(" ", etos(t));
555 // collect health, targetname, message, size
558 for(t = self; ; t = t.enemy)
560 if(t.health && !self.health)
561 self.health = t.health;
562 if((t.targetname != "") && (self.targetname == ""))
563 self.targetname = t.targetname;
564 if((t.message != "") && (self.message == ""))
565 self.message = t.message;
566 if (t.absmin_x < cmins_x)
567 cmins_x = t.absmin_x;
568 if (t.absmin_y < cmins_y)
569 cmins_y = t.absmin_y;
570 if (t.absmin_z < cmins_z)
571 cmins_z = t.absmin_z;
572 if (t.absmax_x > cmaxs_x)
573 cmaxs_x = t.absmax_x;
574 if (t.absmax_y > cmaxs_y)
575 cmaxs_y = t.absmax_y;
576 if (t.absmax_z > cmaxs_z)
577 cmaxs_z = t.absmax_z;
582 // distribute health, targetname, message
583 for(t = self; t; t = t.enemy)
585 t.health = self.health;
586 t.targetname = self.targetname;
587 t.message = self.message;
592 // shootable, or triggered doors just needed the owner/enemy links,
593 // they don't spawn a field
602 spawn_field(cmins, cmaxs);
606 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
607 if two doors touch, they are assumed to be connected and operate as a unit.
609 TOGGLE causes the door to wait in both the start and end states for a trigger event.
611 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).
613 GOLD_KEY causes the door to open only if the activator holds a gold key.
615 SILVER_KEY causes the door to open only if the activator holds a silver key.
617 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
618 "angle" determines the opening direction
619 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
620 "health" if set, door must be shot open
621 "speed" movement speed (100 default)
622 "wait" wait before returning (3 default, -1 = never return)
623 "lip" lip remaining at end of move (8 default)
624 "dmg" damage to inflict when blocked (2 default)
631 FIXME: only one sound set available at the time being
635 float door_send(entity to, float sf)
637 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
638 WriteByte(MSG_ENTITY, sf);
640 if(sf & SF_TRIGGER_INIT)
642 WriteString(MSG_ENTITY, self.classname);
643 WriteByte(MSG_ENTITY, self.spawnflags);
645 WriteString(MSG_ENTITY, self.model);
647 trigger_common_write(true);
649 WriteCoord(MSG_ENTITY, self.pos1_x);
650 WriteCoord(MSG_ENTITY, self.pos1_y);
651 WriteCoord(MSG_ENTITY, self.pos1_z);
652 WriteCoord(MSG_ENTITY, self.pos2_x);
653 WriteCoord(MSG_ENTITY, self.pos2_y);
654 WriteCoord(MSG_ENTITY, self.pos2_z);
656 WriteCoord(MSG_ENTITY, self.size_x);
657 WriteCoord(MSG_ENTITY, self.size_y);
658 WriteCoord(MSG_ENTITY, self.size_z);
660 WriteShort(MSG_ENTITY, self.wait);
661 WriteShort(MSG_ENTITY, self.speed);
662 WriteByte(MSG_ENTITY, self.lip);
663 WriteByte(MSG_ENTITY, self.state);
664 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
667 if(sf & SF_TRIGGER_RESET)
669 // client makes use of this, we do not
672 if(sf & SF_TRIGGER_UPDATE)
674 WriteCoord(MSG_ENTITY, self.origin_x);
675 WriteCoord(MSG_ENTITY, self.origin_y);
676 WriteCoord(MSG_ENTITY, self.origin_z);
678 WriteCoord(MSG_ENTITY, self.pos1_x);
679 WriteCoord(MSG_ENTITY, self.pos1_y);
680 WriteCoord(MSG_ENTITY, self.pos1_z);
681 WriteCoord(MSG_ENTITY, self.pos2_x);
682 WriteCoord(MSG_ENTITY, self.pos2_y);
683 WriteCoord(MSG_ENTITY, self.pos2_z);
691 // set size now, as everything is loaded
693 //Net_LinkEntity(self, false, 0, door_send);
697 void door_init_startopen()
699 SUB_SETORIGIN(self, self.pos2);
700 self.pos2 = self.pos1;
701 self.pos1 = self.origin;
704 self.SendFlags |= SF_TRIGGER_UPDATE;
710 SUB_SETORIGIN(self, self.pos1);
711 self.SUB_VELOCITY = '0 0 0';
712 self.state = STATE_BOTTOM;
713 self.SUB_THINK = func_null;
714 self.SUB_NEXTTHINK = 0;
717 self.SendFlags |= SF_TRIGGER_RESET;
723 // spawnflags require key (for now only func_door)
726 // Quake 1 keys compatibility
727 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
728 self.itemkeys |= ITEM_KEY_BIT(0);
729 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
730 self.itemkeys |= ITEM_KEY_BIT(1);
734 self.max_health = self.health;
735 if (!InitMovingBrushTrigger())
737 self.effects |= EF_LOWPRECISION;
738 self.classname = "door";
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();