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 SELFCALL(self.owner, door_fire());
281 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
283 if(self.spawnflags & DOOR_NOSPLASH)
284 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
286 self.health = self.health - damage;
290 // don't allow opening doors through damage if keys are required
294 if (self.health <= 0)
296 self.owner.health = self.owner.max_health;
297 self.owner.takedamage = DAMAGE_NO; // wil be reset upon return
298 SELFCALL(self.owner, door_use());
314 if (!IS_PLAYER(other))
316 if (self.owner.attack_finished_single > time)
319 self.owner.attack_finished_single = time + 2;
322 if (!(self.owner.dmg) && (self.owner.message != ""))
324 if (IS_CLIENT(other))
325 centerprint(other, self.owner.message);
326 play2(other, "misc/talk.wav");
331 void door_generic_plat_blocked()
334 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
336 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
343 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
344 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
347 //Dont chamge direction for dead or dying stuff
348 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
352 if (self.state == STATE_DOWN)
353 door_rotating_go_up ();
355 door_rotating_go_down ();
361 //gib dying stuff just to make sure
362 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
363 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
369 void door_rotating_hit_top()
371 if (self.noise1 != "")
372 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
373 self.state = STATE_TOP;
374 if (self.spawnflags & DOOR_TOGGLE)
375 return; // don't come down automatically
376 self.SUB_THINK = door_rotating_go_down;
377 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
380 void door_rotating_hit_bottom()
382 if (self.noise1 != "")
383 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
384 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
386 self.pos2 = '0 0 0' - self.pos2;
389 self.state = STATE_BOTTOM;
392 void door_rotating_go_down()
394 if (self.noise2 != "")
395 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
398 self.takedamage = DAMAGE_YES;
399 self.health = self.max_health;
402 self.state = STATE_DOWN;
403 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
406 void door_rotating_go_up()
408 if (self.state == STATE_UP)
409 return; // already going up
411 if (self.state == STATE_TOP)
412 { // reset top wait time
413 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
416 if (self.noise2 != "")
417 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
418 self.state = STATE_UP;
419 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
422 oldmessage = self.message;
425 self.message = oldmessage;
430 =========================================
433 Spawned if a door lacks a real activator
434 =========================================
437 void door_trigger_touch()
439 if (other.health < 1)
441 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
443 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
447 if (time < self.attack_finished_single)
450 // check if door is locked
451 if (!door_check_keys())
454 self.attack_finished_single = time + 1;
462 void spawn_field(vector fmins, vector fmaxs)
465 vector t1 = fmins, t2 = fmaxs;
468 trigger.classname = "doortriggerfield";
469 trigger.movetype = MOVETYPE_NONE;
470 trigger.solid = SOLID_TRIGGER;
471 trigger.owner = self;
473 trigger.touch = door_trigger_touch;
475 trigger.trigger_touch = door_trigger_touch;
476 trigger.draw = trigger_draw_generic;
477 trigger.drawmask = MASK_NORMAL;
480 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
492 entity LinkDoors_nextent(entity cur, entity near, entity pass)
494 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
500 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
503 if((e1.absmin_x > e2.absmax_x + DELTA)
504 || (e1.absmin_y > e2.absmax_y + DELTA)
505 || (e1.absmin_z > e2.absmax_z + DELTA)
506 || (e2.absmin_x > e1.absmax_x + DELTA)
507 || (e2.absmin_y > e1.absmax_y + DELTA)
508 || (e2.absmin_z > e1.absmax_z + DELTA)
526 return; // already linked by another door
527 if (self.spawnflags & 4)
529 self.owner = self.enemy = self;
538 spawn_field(self.absmin, self.absmax);
540 return; // don't want to link this door
543 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
545 // set owner, and make a loop of the chain
546 LOG_TRACE("LinkDoors: linking doors:");
547 for(t = self; ; t = t.enemy)
549 LOG_TRACE(" ", etos(t));
559 // collect health, targetname, message, size
562 for(t = self; ; t = t.enemy)
564 if(t.health && !self.health)
565 self.health = t.health;
566 if((t.targetname != "") && (self.targetname == ""))
567 self.targetname = t.targetname;
568 if((t.message != "") && (self.message == ""))
569 self.message = t.message;
570 if (t.absmin_x < cmins_x)
571 cmins_x = t.absmin_x;
572 if (t.absmin_y < cmins_y)
573 cmins_y = t.absmin_y;
574 if (t.absmin_z < cmins_z)
575 cmins_z = t.absmin_z;
576 if (t.absmax_x > cmaxs_x)
577 cmaxs_x = t.absmax_x;
578 if (t.absmax_y > cmaxs_y)
579 cmaxs_y = t.absmax_y;
580 if (t.absmax_z > cmaxs_z)
581 cmaxs_z = t.absmax_z;
586 // distribute health, targetname, message
587 for(t = self; t; t = t.enemy)
589 t.health = self.health;
590 t.targetname = self.targetname;
591 t.message = self.message;
596 // shootable, or triggered doors just needed the owner/enemy links,
597 // they don't spawn a field
606 spawn_field(cmins, cmaxs);
610 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
611 if two doors touch, they are assumed to be connected and operate as a unit.
613 TOGGLE causes the door to wait in both the start and end states for a trigger event.
615 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).
617 GOLD_KEY causes the door to open only if the activator holds a gold key.
619 SILVER_KEY causes the door to open only if the activator holds a silver key.
621 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
622 "angle" determines the opening direction
623 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
624 "health" if set, door must be shot open
625 "speed" movement speed (100 default)
626 "wait" wait before returning (3 default, -1 = never return)
627 "lip" lip remaining at end of move (8 default)
628 "dmg" damage to inflict when blocked (2 default)
635 FIXME: only one sound set available at the time being
639 float door_send(entity to, float sf)
641 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
642 WriteByte(MSG_ENTITY, sf);
644 if(sf & SF_TRIGGER_INIT)
646 WriteString(MSG_ENTITY, self.classname);
647 WriteByte(MSG_ENTITY, self.spawnflags);
649 WriteString(MSG_ENTITY, self.model);
651 trigger_common_write(true);
653 WriteCoord(MSG_ENTITY, self.pos1_x);
654 WriteCoord(MSG_ENTITY, self.pos1_y);
655 WriteCoord(MSG_ENTITY, self.pos1_z);
656 WriteCoord(MSG_ENTITY, self.pos2_x);
657 WriteCoord(MSG_ENTITY, self.pos2_y);
658 WriteCoord(MSG_ENTITY, self.pos2_z);
660 WriteCoord(MSG_ENTITY, self.size_x);
661 WriteCoord(MSG_ENTITY, self.size_y);
662 WriteCoord(MSG_ENTITY, self.size_z);
664 WriteShort(MSG_ENTITY, self.wait);
665 WriteShort(MSG_ENTITY, self.speed);
666 WriteByte(MSG_ENTITY, self.lip);
667 WriteByte(MSG_ENTITY, self.state);
668 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
671 if(sf & SF_TRIGGER_RESET)
673 // client makes use of this, we do not
676 if(sf & SF_TRIGGER_UPDATE)
678 WriteCoord(MSG_ENTITY, self.origin_x);
679 WriteCoord(MSG_ENTITY, self.origin_y);
680 WriteCoord(MSG_ENTITY, self.origin_z);
682 WriteCoord(MSG_ENTITY, self.pos1_x);
683 WriteCoord(MSG_ENTITY, self.pos1_y);
684 WriteCoord(MSG_ENTITY, self.pos1_z);
685 WriteCoord(MSG_ENTITY, self.pos2_x);
686 WriteCoord(MSG_ENTITY, self.pos2_y);
687 WriteCoord(MSG_ENTITY, self.pos2_z);
695 // set size now, as everything is loaded
697 //Net_LinkEntity(self, false, 0, door_send);
701 void door_init_startopen()
703 SUB_SETORIGIN(self, self.pos2);
704 self.pos2 = self.pos1;
705 self.pos1 = self.origin;
708 self.SendFlags |= SF_TRIGGER_UPDATE;
714 SUB_SETORIGIN(self, self.pos1);
715 self.SUB_VELOCITY = '0 0 0';
716 self.state = STATE_BOTTOM;
717 self.SUB_THINK = func_null;
718 self.SUB_NEXTTHINK = 0;
721 self.SendFlags |= SF_TRIGGER_RESET;
727 // spawnflags require key (for now only func_door)
728 void spawnfunc_func_door()
730 // Quake 1 keys compatibility
731 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
732 self.itemkeys |= ITEM_KEY_BIT(0);
733 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
734 self.itemkeys |= ITEM_KEY_BIT(1);
738 self.max_health = self.health;
739 if (!InitMovingBrushTrigger())
741 self.effects |= EF_LOWPRECISION;
742 self.classname = "door";
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;
802 Movetype_Physics_NoMatchServer();
804 trigger_draw_generic();
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();