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);
219 if (self.owner != self)
220 objerror ("door_fire: self.owner != self");
224 if (self.spawnflags & DOOR_TOGGLE)
226 if (self.state == STATE_UP || self.state == STATE_TOP)
231 if (self.classname == "door")
237 door_rotating_go_down ();
240 } while ( (self != starte) && (self != world) );
246 // trigger all paired doors
250 if (self.classname == "door")
255 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
256 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
258 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
259 self.pos2 = '0 0 0' - self.pos2;
261 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
262 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
263 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
265 door_rotating_go_up ();
269 } while ( (self != starte) && (self != world) );
277 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
288 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
291 if(self.spawnflags & DOOR_NOSPLASH)
292 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
294 self.health = self.health - damage;
298 // don't allow opening doors through damage if keys are required
302 if (self.health <= 0)
306 self.health = self.max_health;
307 self.takedamage = DAMAGE_NO; // wil be reset upon return
324 if (!IS_PLAYER(other))
326 if (self.owner.attack_finished_single > time)
329 self.owner.attack_finished_single = time + 2;
332 if (!(self.owner.dmg) && (self.owner.message != ""))
334 if (IS_CLIENT(other))
335 centerprint(other, self.owner.message);
336 play2(other, "misc/talk.wav");
341 void door_generic_plat_blocked()
344 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
346 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
353 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
354 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
357 //Dont chamge direction for dead or dying stuff
358 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
362 if (self.state == STATE_DOWN)
363 door_rotating_go_up ();
365 door_rotating_go_down ();
371 //gib dying stuff just to make sure
372 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
373 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
379 void door_rotating_hit_top()
381 if (self.noise1 != "")
382 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
383 self.state = STATE_TOP;
384 if (self.spawnflags & DOOR_TOGGLE)
385 return; // don't come down automatically
386 self.SUB_THINK = door_rotating_go_down;
387 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
390 void door_rotating_hit_bottom()
392 if (self.noise1 != "")
393 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
394 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
396 self.pos2 = '0 0 0' - self.pos2;
399 self.state = STATE_BOTTOM;
402 void door_rotating_go_down()
404 if (self.noise2 != "")
405 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
408 self.takedamage = DAMAGE_YES;
409 self.health = self.max_health;
412 self.state = STATE_DOWN;
413 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
416 void door_rotating_go_up()
418 if (self.state == STATE_UP)
419 return; // already going up
421 if (self.state == STATE_TOP)
422 { // reset top wait time
423 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
426 if (self.noise2 != "")
427 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
428 self.state = STATE_UP;
429 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
432 oldmessage = self.message;
435 self.message = oldmessage;
440 =========================================
443 Spawned if a door lacks a real activator
444 =========================================
447 void door_trigger_touch()
449 if (other.health < 1)
451 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
453 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
457 if (time < self.attack_finished_single)
460 // check if door is locked
461 if (!door_check_keys())
464 self.attack_finished_single = time + 1;
472 void spawn_field(vector fmins, vector fmaxs)
475 vector t1 = fmins, t2 = fmaxs;
478 trigger.classname = "doortriggerfield";
479 trigger.movetype = MOVETYPE_NONE;
480 trigger.solid = SOLID_TRIGGER;
481 trigger.owner = self;
483 trigger.touch = door_trigger_touch;
485 trigger.trigger_touch = door_trigger_touch;
486 trigger.draw = trigger_draw_generic;
487 trigger.drawmask = MASK_NORMAL;
490 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
502 entity LinkDoors_nextent(entity cur, entity near, entity pass)
504 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
510 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
513 if((e1.absmin_x > e2.absmax_x + DELTA)
514 || (e1.absmin_y > e2.absmax_y + DELTA)
515 || (e1.absmin_z > e2.absmax_z + DELTA)
516 || (e2.absmin_x > e1.absmax_x + DELTA)
517 || (e2.absmin_y > e1.absmax_y + DELTA)
518 || (e2.absmin_z > e1.absmax_z + DELTA)
536 return; // already linked by another door
537 if (self.spawnflags & 4)
539 self.owner = self.enemy = self;
548 spawn_field(self.absmin, self.absmax);
550 return; // don't want to link this door
553 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
555 // set owner, and make a loop of the chain
556 dprint("LinkDoors: linking doors:");
557 for(t = self; ; t = t.enemy)
559 dprint(" ", etos(t));
569 // collect health, targetname, message, size
572 for(t = self; ; t = t.enemy)
574 if(t.health && !self.health)
575 self.health = t.health;
576 if((t.targetname != "") && (self.targetname == ""))
577 self.targetname = t.targetname;
578 if((t.message != "") && (self.message == ""))
579 self.message = t.message;
580 if (t.absmin_x < cmins_x)
581 cmins_x = t.absmin_x;
582 if (t.absmin_y < cmins_y)
583 cmins_y = t.absmin_y;
584 if (t.absmin_z < cmins_z)
585 cmins_z = t.absmin_z;
586 if (t.absmax_x > cmaxs_x)
587 cmaxs_x = t.absmax_x;
588 if (t.absmax_y > cmaxs_y)
589 cmaxs_y = t.absmax_y;
590 if (t.absmax_z > cmaxs_z)
591 cmaxs_z = t.absmax_z;
596 // distribute health, targetname, message
597 for(t = self; t; t = t.enemy)
599 t.health = self.health;
600 t.targetname = self.targetname;
601 t.message = self.message;
606 // shootable, or triggered doors just needed the owner/enemy links,
607 // they don't spawn a field
616 spawn_field(cmins, cmaxs);
620 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
621 if two doors touch, they are assumed to be connected and operate as a unit.
623 TOGGLE causes the door to wait in both the start and end states for a trigger event.
625 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).
627 GOLD_KEY causes the door to open only if the activator holds a gold key.
629 SILVER_KEY causes the door to open only if the activator holds a silver key.
631 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
632 "angle" determines the opening direction
633 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
634 "health" if set, door must be shot open
635 "speed" movement speed (100 default)
636 "wait" wait before returning (3 default, -1 = never return)
637 "lip" lip remaining at end of move (8 default)
638 "dmg" damage to inflict when blocked (2 default)
645 FIXME: only one sound set available at the time being
649 float door_send(entity to, float sf)
651 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
652 WriteByte(MSG_ENTITY, sf);
654 if(sf & SF_TRIGGER_INIT)
656 WriteString(MSG_ENTITY, self.classname);
657 WriteByte(MSG_ENTITY, self.spawnflags);
659 WriteString(MSG_ENTITY, self.model);
661 trigger_common_write(true);
663 WriteCoord(MSG_ENTITY, self.pos1_x);
664 WriteCoord(MSG_ENTITY, self.pos1_y);
665 WriteCoord(MSG_ENTITY, self.pos1_z);
666 WriteCoord(MSG_ENTITY, self.pos2_x);
667 WriteCoord(MSG_ENTITY, self.pos2_y);
668 WriteCoord(MSG_ENTITY, self.pos2_z);
670 WriteCoord(MSG_ENTITY, self.size_x);
671 WriteCoord(MSG_ENTITY, self.size_y);
672 WriteCoord(MSG_ENTITY, self.size_z);
674 WriteShort(MSG_ENTITY, self.wait);
675 WriteShort(MSG_ENTITY, self.speed);
676 WriteByte(MSG_ENTITY, self.lip);
677 WriteByte(MSG_ENTITY, self.state);
678 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
681 if(sf & SF_TRIGGER_RESET)
683 // client makes use of this, we do not
686 if(sf & SF_TRIGGER_UPDATE)
688 WriteCoord(MSG_ENTITY, self.origin_x);
689 WriteCoord(MSG_ENTITY, self.origin_y);
690 WriteCoord(MSG_ENTITY, self.origin_z);
692 WriteCoord(MSG_ENTITY, self.pos1_x);
693 WriteCoord(MSG_ENTITY, self.pos1_y);
694 WriteCoord(MSG_ENTITY, self.pos1_z);
695 WriteCoord(MSG_ENTITY, self.pos2_x);
696 WriteCoord(MSG_ENTITY, self.pos2_y);
697 WriteCoord(MSG_ENTITY, self.pos2_z);
705 // set size now, as everything is loaded
707 //Net_LinkEntity(self, false, 0, door_send);
711 void door_init_startopen()
713 SUB_SETORIGIN(self, self.pos2);
714 self.pos2 = self.pos1;
715 self.pos1 = self.origin;
718 self.SendFlags |= SF_TRIGGER_UPDATE;
724 SUB_SETORIGIN(self, self.pos1);
725 self.SUB_VELOCITY = '0 0 0';
726 self.state = STATE_BOTTOM;
727 self.SUB_THINK = func_null;
728 self.SUB_NEXTTHINK = 0;
731 self.SendFlags |= SF_TRIGGER_RESET;
737 // spawnflags require key (for now only func_door)
738 void spawnfunc_func_door()
740 // Quake 1 keys compatibility
741 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
742 self.itemkeys |= ITEM_KEY_BIT(0);
743 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
744 self.itemkeys |= ITEM_KEY_BIT(1);
748 self.max_health = self.health;
749 if (!InitMovingBrushTrigger())
751 self.effects |= EF_LOWPRECISION;
752 self.classname = "door";
754 self.blocked = door_blocked;
757 if(self.dmg && (self.message == ""))
758 self.message = "was squished";
759 if(self.dmg && (self.message2 == ""))
760 self.message2 = "was squished by";
764 precache_sound ("plats/medplat1.wav");
765 precache_sound ("plats/medplat2.wav");
766 self.noise2 = "plats/medplat1.wav";
767 self.noise1 = "plats/medplat2.wav";
777 self.pos1 = self.SUB_ORIGIN;
778 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
780 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
781 // but spawn in the open position
782 if (self.spawnflags & DOOR_START_OPEN)
783 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
785 self.state = STATE_BOTTOM;
789 self.takedamage = DAMAGE_YES;
790 self.event_damage = door_damage;
796 self.touch = door_touch;
798 // LinkDoors can't be done until all of the doors have been spawned, so
799 // the sizes can be detected properly.
800 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
802 self.reset = door_reset;
809 Movetype_Physics_NoMatchServer();
811 trigger_draw_generic();
816 float sf = ReadByte();
818 if(sf & SF_TRIGGER_INIT)
820 self.classname = strzone(ReadString());
821 self.spawnflags = ReadByte();
823 self.mdl = strzone(ReadString());
824 setmodel(self, self.mdl);
826 trigger_common_read(true);
828 self.pos1_x = ReadCoord();
829 self.pos1_y = ReadCoord();
830 self.pos1_z = ReadCoord();
831 self.pos2_x = ReadCoord();
832 self.pos2_y = ReadCoord();
833 self.pos2_z = ReadCoord();
835 self.size_x = ReadCoord();
836 self.size_y = ReadCoord();
837 self.size_z = ReadCoord();
839 self.wait = ReadShort();
840 self.speed = ReadShort();
841 self.lip = ReadByte();
842 self.state = ReadByte();
843 self.SUB_LTIME = ReadCoord();
845 self.solid = SOLID_BSP;
846 self.movetype = MOVETYPE_PUSH;
847 self.trigger_touch = door_touch;
848 self.draw = door_draw;
849 self.drawmask = MASK_NORMAL;
854 if(self.spawnflags & DOOR_START_OPEN)
855 door_init_startopen();
857 self.move_time = time;
858 self.move_origin = self.origin;
859 self.move_movetype = MOVETYPE_PUSH;
860 self.move_angles = self.angles;
861 self.move_blocked = door_blocked;
864 if(sf & SF_TRIGGER_RESET)
869 if(sf & SF_TRIGGER_UPDATE)
871 self.origin_x = ReadCoord();
872 self.origin_y = ReadCoord();
873 self.origin_z = ReadCoord();
874 setorigin(self, self.origin);
875 self.move_origin = self.origin;
877 self.pos1_x = ReadCoord();
878 self.pos1_y = ReadCoord();
879 self.pos1_z = ReadCoord();
880 self.pos2_x = ReadCoord();
881 self.pos2_y = ReadCoord();
882 self.pos2_z = ReadCoord();