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.think = door_go_down;
99 self.think = door_rotating_go_down;
101 trigger_setnextthink(self, self.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 trigger_setnextthink(self, self.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, float 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.think = door_rotating_go_down;
387 self.nextthink = self.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 trigger_setnextthink(self, self.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 && !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;
474 float door_trigger_send(entity to, float sf)
476 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR_TRIGGER);
478 WriteShort(MSG_ENTITY, num_for_edict(self.owner));
479 WriteCoord(MSG_ENTITY, self.origin_x);
480 WriteCoord(MSG_ENTITY, self.origin_y);
481 WriteCoord(MSG_ENTITY, self.origin_z);
483 WriteCoord(MSG_ENTITY, self.mins_x);
484 WriteCoord(MSG_ENTITY, self.mins_y);
485 WriteCoord(MSG_ENTITY, self.mins_z);
486 WriteCoord(MSG_ENTITY, self.maxs_x);
487 WriteCoord(MSG_ENTITY, self.maxs_y);
488 WriteCoord(MSG_ENTITY, self.maxs_z);
493 void door_trigger_link(entity trig)
495 Net_LinkEntity(trig, false, 0, door_trigger_send);
498 void spawn_field(vector fmins, vector fmaxs)
501 vector t1 = fmins, t2 = fmaxs;
504 trigger.classname = "doortriggerfield";
505 trigger.movetype = MOVETYPE_NONE;
506 trigger.solid = SOLID_TRIGGER;
507 trigger.owner = self;
508 trigger.touch = door_trigger_touch;
510 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
511 door_trigger_link(trigger);
516 void ent_door_trigger()
518 float entnum = ReadShort();
519 self.origin_x = ReadCoord();
520 self.origin_y = ReadCoord();
521 self.origin_z = ReadCoord();
522 setorigin(self, self.origin);
523 self.mins_x = ReadCoord();
524 self.mins_y = ReadCoord();
525 self.mins_z = ReadCoord();
526 self.maxs_x = ReadCoord();
527 self.maxs_y = ReadCoord();
528 self.maxs_z = ReadCoord();
529 setsize(self, self.mins, self.maxs);
531 self.owner = findfloat(world, sv_entnum, entnum); // if owner doesn't exist, it shouldn't matter much
532 self.classname = "doortriggerfield";
533 self.movetype = MOVETYPE_NONE;
534 self.solid = SOLID_TRIGGER;
535 self.trigger_touch = door_trigger_touch;
536 self.draw = trigger_draw_generic;
537 self.drawmask = MASK_NORMAL;
538 self.move_time = time;
551 entity LinkDoors_nextent(entity cur, entity near, entity pass)
553 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
559 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
562 if((e1.absmin_x > e2.absmax_x + DELTA)
563 || (e1.absmin_y > e2.absmax_y + DELTA)
564 || (e1.absmin_z > e2.absmax_z + DELTA)
565 || (e2.absmin_x > e1.absmax_x + DELTA)
566 || (e2.absmin_y > e1.absmax_y + DELTA)
567 || (e2.absmin_z > e1.absmax_z + DELTA)
581 return; // already linked by another door
582 if (self.spawnflags & 4)
584 self.owner = self.enemy = self;
592 spawn_field(self.absmin, self.absmax);
594 return; // don't want to link this door
597 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
599 // set owner, and make a loop of the chain
600 dprint("LinkDoors: linking doors:");
601 for(t = self; ; t = t.enemy)
603 dprint(" ", etos(t));
613 // collect health, targetname, message, size
616 for(t = self; ; t = t.enemy)
618 if(t.health && !self.health)
619 self.health = t.health;
620 if((t.targetname != "") && (self.targetname == ""))
621 self.targetname = t.targetname;
622 if((t.message != "") && (self.message == ""))
623 self.message = t.message;
624 if (t.absmin_x < cmins_x)
625 cmins_x = t.absmin_x;
626 if (t.absmin_y < cmins_y)
627 cmins_y = t.absmin_y;
628 if (t.absmin_z < cmins_z)
629 cmins_z = t.absmin_z;
630 if (t.absmax_x > cmaxs_x)
631 cmaxs_x = t.absmax_x;
632 if (t.absmax_y > cmaxs_y)
633 cmaxs_y = t.absmax_y;
634 if (t.absmax_z > cmaxs_z)
635 cmaxs_z = t.absmax_z;
640 // distribute health, targetname, message
641 for(t = self; t; t = t.enemy)
643 t.health = self.health;
644 t.targetname = self.targetname;
645 t.message = self.message;
650 // shootable, or triggered doors just needed the owner/enemy links,
651 // they don't spawn a field
660 spawn_field(cmins, cmaxs);
664 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
665 if two doors touch, they are assumed to be connected and operate as a unit.
667 TOGGLE causes the door to wait in both the start and end states for a trigger event.
669 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).
671 GOLD_KEY causes the door to open only if the activator holds a gold key.
673 SILVER_KEY causes the door to open only if the activator holds a silver key.
675 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
676 "angle" determines the opening direction
677 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
678 "health" if set, door must be shot open
679 "speed" movement speed (100 default)
680 "wait" wait before returning (3 default, -1 = never return)
681 "lip" lip remaining at end of move (8 default)
682 "dmg" damage to inflict when blocked (2 default)
689 FIXME: only one sound set available at the time being
693 float door_send(entity to, float sf)
695 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
696 WriteByte(MSG_ENTITY, sf);
698 if(sf & SF_TRIGGER_INIT)
700 WriteString(MSG_ENTITY, self.classname);
701 WriteByte(MSG_ENTITY, self.spawnflags);
702 WriteShort(MSG_ENTITY, ((self.owner == self || !self.owner) ? -1 : num_for_edict(self.owner)));
703 WriteShort(MSG_ENTITY, ((self.enemy == self || !self.enemy) ? -1 : num_for_edict(self.enemy)));
704 WriteShort(MSG_ENTITY, num_for_edict(self));
706 WriteByte(MSG_ENTITY, self.scale);
708 WriteCoord(MSG_ENTITY, self.origin_x);
709 WriteCoord(MSG_ENTITY, self.origin_y);
710 WriteCoord(MSG_ENTITY, self.origin_z);
712 WriteString(MSG_ENTITY, self.model);
714 WriteCoord(MSG_ENTITY, self.mins_x);
715 WriteCoord(MSG_ENTITY, self.mins_y);
716 WriteCoord(MSG_ENTITY, self.mins_z);
717 WriteCoord(MSG_ENTITY, self.maxs_x);
718 WriteCoord(MSG_ENTITY, self.maxs_y);
719 WriteCoord(MSG_ENTITY, self.maxs_z);
721 WriteCoord(MSG_ENTITY, self.movedir_x);
722 WriteCoord(MSG_ENTITY, self.movedir_y);
723 WriteCoord(MSG_ENTITY, self.movedir_z);
725 WriteAngle(MSG_ENTITY, self.angles_x);
726 WriteAngle(MSG_ENTITY, self.angles_y);
727 WriteAngle(MSG_ENTITY, self.angles_z);
729 WriteCoord(MSG_ENTITY, self.pos1_x);
730 WriteCoord(MSG_ENTITY, self.pos1_y);
731 WriteCoord(MSG_ENTITY, self.pos1_z);
732 WriteCoord(MSG_ENTITY, self.pos2_x);
733 WriteCoord(MSG_ENTITY, self.pos2_y);
734 WriteCoord(MSG_ENTITY, self.pos2_z);
736 WriteCoord(MSG_ENTITY, self.size_x);
737 WriteCoord(MSG_ENTITY, self.size_y);
738 WriteCoord(MSG_ENTITY, self.size_z);
740 WriteShort(MSG_ENTITY, self.wait);
741 WriteShort(MSG_ENTITY, self.speed);
742 WriteByte(MSG_ENTITY, self.lip);
743 WriteByte(MSG_ENTITY, self.state);
744 WriteCoord(MSG_ENTITY, self.ltime);
747 if(sf & SF_TRIGGER_RESET)
749 // client makes use of this, we do not
752 if(sf & SF_TRIGGER_UPDATE)
754 WriteCoord(MSG_ENTITY, self.origin_x);
755 WriteCoord(MSG_ENTITY, self.origin_y);
756 WriteCoord(MSG_ENTITY, self.origin_z);
758 WriteCoord(MSG_ENTITY, self.pos1_x);
759 WriteCoord(MSG_ENTITY, self.pos1_y);
760 WriteCoord(MSG_ENTITY, self.pos1_z);
761 WriteCoord(MSG_ENTITY, self.pos2_x);
762 WriteCoord(MSG_ENTITY, self.pos2_y);
763 WriteCoord(MSG_ENTITY, self.pos2_z);
771 // set size now, as everything is loaded
773 Net_LinkEntity(self, false, 0, door_send);
776 void door_init_startopen()
778 setorigin (self, self.pos2);
779 self.pos2 = self.pos1;
780 self.pos1 = self.origin;
782 self.SendFlags |= SF_TRIGGER_UPDATE;
789 setorigin(self, self.pos1);
790 self.velocity = '0 0 0';
791 self.state = STATE_BOTTOM;
792 self.think = func_null;
796 self.SendFlags |= SF_TRIGGER_RESET;
802 // spawnflags require key (for now only func_door)
803 void spawnfunc_func_door()
805 // Quake 1 keys compatibility
806 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
807 self.itemkeys |= ITEM_KEY_BIT(0);
808 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
809 self.itemkeys |= ITEM_KEY_BIT(1);
813 self.max_health = self.health;
814 if (!InitMovingBrushTrigger())
816 self.effects |= EF_LOWPRECISION;
817 self.classname = "door";
819 self.blocked = door_blocked;
822 if(self.dmg && (self.message == ""))
823 self.message = "was squished";
824 if(self.dmg && (self.message2 == ""))
825 self.message2 = "was squished by";
829 precache_sound ("plats/medplat1.wav");
830 precache_sound ("plats/medplat2.wav");
831 self.noise2 = "plats/medplat1.wav";
832 self.noise1 = "plats/medplat2.wav";
842 self.pos1 = self.origin;
843 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
845 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
846 // but spawn in the open position
847 if (self.spawnflags & DOOR_START_OPEN)
848 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
850 self.state = STATE_BOTTOM;
854 self.takedamage = DAMAGE_YES;
855 self.event_damage = door_damage;
861 self.touch = door_touch;
863 // LinkDoors can't be done until all of the doors have been spawned, so
864 // the sizes can be detected properly.
865 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
867 self.reset = door_reset;
874 float sf = ReadByte();
876 if(sf & SF_TRIGGER_INIT)
878 self.classname = strzone(ReadString());
879 self.spawnflags = ReadByte();
880 float myowner = ReadShort();
881 float myenemy = ReadShort();
882 self.sv_entnum = ReadShort();
884 self.scale = ReadByte();
886 self.origin_x = ReadCoord();
887 self.origin_y = ReadCoord();
888 self.origin_z = ReadCoord();
889 setorigin(self, self.origin);
891 self.mdl = strzone(ReadString());
892 setmodel(self, self.mdl);
894 self.mins_x = ReadCoord();
895 self.mins_y = ReadCoord();
896 self.mins_z = ReadCoord();
897 self.maxs_x = ReadCoord();
898 self.maxs_y = ReadCoord();
899 self.maxs_z = ReadCoord();
900 setsize(self, self.mins, self.maxs);
902 self.movedir_x = ReadCoord();
903 self.movedir_y = ReadCoord();
904 self.movedir_z = ReadCoord();
906 self.angles_x = ReadAngle();
907 self.angles_y = ReadAngle();
908 self.angles_z = ReadAngle();
910 self.pos1_x = ReadCoord();
911 self.pos1_y = ReadCoord();
912 self.pos1_z = ReadCoord();
913 self.pos2_x = ReadCoord();
914 self.pos2_y = ReadCoord();
915 self.pos2_z = ReadCoord();
917 self.size_x = ReadCoord();
918 self.size_y = ReadCoord();
919 self.size_z = ReadCoord();
921 self.wait = ReadShort();
922 self.speed = ReadShort();
923 self.lip = ReadByte();
924 self.state = ReadByte();
925 self.ltime = ReadCoord();
927 self.movetype = MOVETYPE_PUSH;
928 self.solid = SOLID_BSP;
929 self.trigger_touch = door_touch;
930 self.draw = trigger_draw_generic;
931 self.drawmask = MASK_NORMAL;
932 self.move_time = time;
934 self.blocked = door_blocked;
936 print(ftos(self.entnum), " ", ftos(self.sv_entnum), "\n");
938 self.owner = ((myowner == -1) ? self : findfloat(world, sv_entnum, myowner));
939 self.enemy = ((myenemy == -1) ? self : findfloat(world, sv_entnum, myenemy));
942 if(sf & SF_TRIGGER_RESET)
947 if(sf & SF_TRIGGER_UPDATE)
949 self.origin_x = ReadCoord();
950 self.origin_y = ReadCoord();
951 self.origin_z = ReadCoord();
952 setorigin(self, self.origin);
954 self.pos1_x = ReadCoord();
955 self.pos1_y = ReadCoord();
956 self.pos1_z = ReadCoord();
957 self.pos2_x = ReadCoord();
958 self.pos2_y = ReadCoord();
959 self.pos2_z = ReadCoord();