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, 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, 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, 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)
171 // this door require a key
172 // only a player can have a key
173 if (!IS_PLAYER(other))
177 if (item_keys_usekey(door, other))
179 // some keys were used
180 if (other.key_door_messagetime <= time)
183 play2(other, "misc/talk.wav");
184 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
185 other.key_door_messagetime = time + 2;
191 if (other.key_door_messagetime <= time)
193 play2(other, "misc/talk.wav");
194 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
196 other.key_door_messagetime = time + 2;
204 // door is now unlocked
205 play2(other, "misc/talk.wav");
206 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
218 if (self.owner != self)
219 objerror ("door_fire: self.owner != self");
221 if (self.spawnflags & DOOR_TOGGLE)
223 if (self.state == STATE_UP || self.state == STATE_TOP)
228 if (self.classname == "door")
234 door_rotating_go_down ();
237 } while ( (self != starte) && (self != world) );
243 // trigger all paired doors
247 if (self.classname == "door")
252 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
253 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
255 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
256 self.pos2 = '0 0 0' - self.pos2;
258 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
259 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
260 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
262 door_rotating_go_up ();
266 } while ( (self != starte) && (self != world) );
272 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
276 WITH(entity, self, self.owner, door_fire());
280 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
282 if(self.spawnflags & DOOR_NOSPLASH)
283 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
285 self.health = self.health - damage;
289 // don't allow opening doors through damage if keys are required
293 if (self.health <= 0)
295 self.owner.health = self.owner.max_health;
296 self.owner.takedamage = DAMAGE_NO; // wil be reset upon return
297 WITH(entity, self, self.owner, door_use());
312 if (!IS_PLAYER(other))
314 if (self.owner.attack_finished_single > time)
317 self.owner.attack_finished_single = time + 2;
320 if (!(self.owner.dmg) && (self.owner.message != ""))
322 if (IS_CLIENT(other))
323 centerprint(other, self.owner.message);
324 play2(other, "misc/talk.wav");
329 void door_generic_plat_blocked()
332 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
334 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
341 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
342 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
345 //Dont chamge direction for dead or dying stuff
346 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
350 if (self.state == STATE_DOWN)
351 door_rotating_go_up ();
353 door_rotating_go_down ();
359 //gib dying stuff just to make sure
360 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
361 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
367 void door_rotating_hit_top()
369 if (self.noise1 != "")
370 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
371 self.state = STATE_TOP;
372 if (self.spawnflags & DOOR_TOGGLE)
373 return; // don't come down automatically
374 self.SUB_THINK = door_rotating_go_down;
375 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
378 void door_rotating_hit_bottom()
380 if (self.noise1 != "")
381 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
382 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
384 self.pos2 = '0 0 0' - self.pos2;
387 self.state = STATE_BOTTOM;
390 void door_rotating_go_down()
392 if (self.noise2 != "")
393 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
396 self.takedamage = DAMAGE_YES;
397 self.health = self.max_health;
400 self.state = STATE_DOWN;
401 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
404 void door_rotating_go_up()
406 if (self.state == STATE_UP)
407 return; // already going up
409 if (self.state == STATE_TOP)
410 { // reset top wait time
411 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
414 if (self.noise2 != "")
415 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
416 self.state = STATE_UP;
417 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
420 oldmessage = self.message;
423 self.message = oldmessage;
428 =========================================
431 Spawned if a door lacks a real activator
432 =========================================
435 void door_trigger_touch()
437 if (other.health < 1)
439 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
441 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
445 if (time < self.attack_finished_single)
448 // check if door is locked
449 if (!door_check_keys())
452 self.attack_finished_single = time + 1;
460 void spawn_field(vector fmins, vector fmaxs)
463 vector t1 = fmins, t2 = fmaxs;
466 trigger.classname = "doortriggerfield";
467 trigger.movetype = MOVETYPE_NONE;
468 trigger.solid = SOLID_TRIGGER;
469 trigger.owner = self;
471 trigger.touch = door_trigger_touch;
473 trigger.trigger_touch = door_trigger_touch;
474 trigger.draw = trigger_draw_generic;
475 trigger.drawmask = MASK_NORMAL;
478 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
490 entity LinkDoors_nextent(entity cur, entity near, entity pass)
492 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
498 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
501 if((e1.absmin_x > e2.absmax_x + DELTA)
502 || (e1.absmin_y > e2.absmax_y + DELTA)
503 || (e1.absmin_z > e2.absmax_z + DELTA)
504 || (e2.absmin_x > e1.absmax_x + DELTA)
505 || (e2.absmin_y > e1.absmax_y + DELTA)
506 || (e2.absmin_z > e1.absmax_z + DELTA)
524 return; // already linked by another door
525 if (self.spawnflags & 4)
527 self.owner = self.enemy = self;
536 spawn_field(self.absmin, self.absmax);
538 return; // don't want to link this door
541 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
543 // set owner, and make a loop of the chain
544 LOG_TRACE("LinkDoors: linking doors:");
545 for(t = self; ; t = t.enemy)
547 LOG_TRACE(" ", etos(t));
557 // collect health, targetname, message, size
560 for(t = self; ; t = t.enemy)
562 if(t.health && !self.health)
563 self.health = t.health;
564 if((t.targetname != "") && (self.targetname == ""))
565 self.targetname = t.targetname;
566 if((t.message != "") && (self.message == ""))
567 self.message = t.message;
568 if (t.absmin_x < cmins_x)
569 cmins_x = t.absmin_x;
570 if (t.absmin_y < cmins_y)
571 cmins_y = t.absmin_y;
572 if (t.absmin_z < cmins_z)
573 cmins_z = t.absmin_z;
574 if (t.absmax_x > cmaxs_x)
575 cmaxs_x = t.absmax_x;
576 if (t.absmax_y > cmaxs_y)
577 cmaxs_y = t.absmax_y;
578 if (t.absmax_z > cmaxs_z)
579 cmaxs_z = t.absmax_z;
584 // distribute health, targetname, message
585 for(t = self; t; t = t.enemy)
587 t.health = self.health;
588 t.targetname = self.targetname;
589 t.message = self.message;
594 // shootable, or triggered doors just needed the owner/enemy links,
595 // they don't spawn a field
604 spawn_field(cmins, cmaxs);
608 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
609 if two doors touch, they are assumed to be connected and operate as a unit.
611 TOGGLE causes the door to wait in both the start and end states for a trigger event.
613 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).
615 GOLD_KEY causes the door to open only if the activator holds a gold key.
617 SILVER_KEY causes the door to open only if the activator holds a silver key.
619 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
620 "angle" determines the opening direction
621 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
622 "health" if set, door must be shot open
623 "speed" movement speed (100 default)
624 "wait" wait before returning (3 default, -1 = never return)
625 "lip" lip remaining at end of move (8 default)
626 "dmg" damage to inflict when blocked (2 default)
633 FIXME: only one sound set available at the time being
637 float door_send(entity to, float sf)
639 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
640 WriteByte(MSG_ENTITY, sf);
642 if(sf & SF_TRIGGER_INIT)
644 WriteString(MSG_ENTITY, self.classname);
645 WriteByte(MSG_ENTITY, self.spawnflags);
647 WriteString(MSG_ENTITY, self.model);
649 trigger_common_write(true);
651 WriteCoord(MSG_ENTITY, self.pos1_x);
652 WriteCoord(MSG_ENTITY, self.pos1_y);
653 WriteCoord(MSG_ENTITY, self.pos1_z);
654 WriteCoord(MSG_ENTITY, self.pos2_x);
655 WriteCoord(MSG_ENTITY, self.pos2_y);
656 WriteCoord(MSG_ENTITY, self.pos2_z);
658 WriteCoord(MSG_ENTITY, self.size_x);
659 WriteCoord(MSG_ENTITY, self.size_y);
660 WriteCoord(MSG_ENTITY, self.size_z);
662 WriteShort(MSG_ENTITY, self.wait);
663 WriteShort(MSG_ENTITY, self.speed);
664 WriteByte(MSG_ENTITY, self.lip);
665 WriteByte(MSG_ENTITY, self.state);
666 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
669 if(sf & SF_TRIGGER_RESET)
671 // client makes use of this, we do not
674 if(sf & SF_TRIGGER_UPDATE)
676 WriteCoord(MSG_ENTITY, self.origin_x);
677 WriteCoord(MSG_ENTITY, self.origin_y);
678 WriteCoord(MSG_ENTITY, self.origin_z);
680 WriteCoord(MSG_ENTITY, self.pos1_x);
681 WriteCoord(MSG_ENTITY, self.pos1_y);
682 WriteCoord(MSG_ENTITY, self.pos1_z);
683 WriteCoord(MSG_ENTITY, self.pos2_x);
684 WriteCoord(MSG_ENTITY, self.pos2_y);
685 WriteCoord(MSG_ENTITY, self.pos2_z);
693 // set size now, as everything is loaded
695 //Net_LinkEntity(self, false, 0, door_send);
699 void door_init_startopen()
701 SUB_SETORIGIN(self, self.pos2);
702 self.pos2 = self.pos1;
703 self.pos1 = self.origin;
706 self.SendFlags |= SF_TRIGGER_UPDATE;
712 SUB_SETORIGIN(self, self.pos1);
713 self.SUB_VELOCITY = '0 0 0';
714 self.state = STATE_BOTTOM;
715 self.SUB_THINK = func_null;
716 self.SUB_NEXTTHINK = 0;
719 self.SendFlags |= SF_TRIGGER_RESET;
725 // spawnflags require key (for now only func_door)
726 void spawnfunc_func_door()
728 // Quake 1 keys compatibility
729 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
730 self.itemkeys |= ITEM_KEY_BIT(0);
731 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
732 self.itemkeys |= ITEM_KEY_BIT(1);
736 self.max_health = self.health;
737 if (!InitMovingBrushTrigger())
739 self.effects |= EF_LOWPRECISION;
740 self.classname = "door";
742 self.blocked = door_blocked;
745 if(self.dmg && (self.message == ""))
746 self.message = "was squished";
747 if(self.dmg && (self.message2 == ""))
748 self.message2 = "was squished by";
752 precache_sound ("plats/medplat1.wav");
753 precache_sound ("plats/medplat2.wav");
754 self.noise2 = "plats/medplat1.wav";
755 self.noise1 = "plats/medplat2.wav";
765 self.pos1 = self.SUB_ORIGIN;
766 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
768 if(self.spawnflags & DOOR_NONSOLID)
769 self.solid = SOLID_NOT;
771 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
772 // but spawn in the open position
773 if (self.spawnflags & DOOR_START_OPEN)
774 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
776 self.state = STATE_BOTTOM;
780 self.takedamage = DAMAGE_YES;
781 self.event_damage = door_damage;
787 self.touch = door_touch;
789 // LinkDoors can't be done until all of the doors have been spawned, so
790 // the sizes can be detected properly.
791 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
793 self.reset = door_reset;
800 Movetype_Physics_NoMatchServer();
802 trigger_draw_generic();
807 float sf = ReadByte();
809 if(sf & SF_TRIGGER_INIT)
811 self.classname = strzone(ReadString());
812 self.spawnflags = ReadByte();
814 self.mdl = strzone(ReadString());
815 _setmodel(self, self.mdl);
817 trigger_common_read(true);
819 self.pos1_x = ReadCoord();
820 self.pos1_y = ReadCoord();
821 self.pos1_z = ReadCoord();
822 self.pos2_x = ReadCoord();
823 self.pos2_y = ReadCoord();
824 self.pos2_z = ReadCoord();
826 self.size_x = ReadCoord();
827 self.size_y = ReadCoord();
828 self.size_z = ReadCoord();
830 self.wait = ReadShort();
831 self.speed = ReadShort();
832 self.lip = ReadByte();
833 self.state = ReadByte();
834 self.SUB_LTIME = ReadCoord();
836 self.solid = SOLID_BSP;
837 self.movetype = MOVETYPE_PUSH;
838 self.trigger_touch = door_touch;
839 self.draw = door_draw;
840 self.drawmask = MASK_NORMAL;
845 if(self.spawnflags & DOOR_START_OPEN)
846 door_init_startopen();
848 self.move_time = time;
849 self.move_origin = self.origin;
850 self.move_movetype = MOVETYPE_PUSH;
851 self.move_angles = self.angles;
852 self.move_blocked = door_blocked;
855 if(sf & SF_TRIGGER_RESET)
860 if(sf & SF_TRIGGER_UPDATE)
862 self.origin_x = ReadCoord();
863 self.origin_y = ReadCoord();
864 self.origin_z = ReadCoord();
865 setorigin(self, self.origin);
866 self.move_origin = self.origin;
868 self.pos1_x = ReadCoord();
869 self.pos1_y = ReadCoord();
870 self.pos1_z = ReadCoord();
871 self.pos2_x = ReadCoord();
872 self.pos2_y = ReadCoord();
873 self.pos2_z = ReadCoord();