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 =============================================================================
23 void FixSize(entity e)
25 e.mins_x = rint(e.mins_x);
26 e.mins_y = rint(e.mins_y);
27 e.mins_z = rint(e.mins_z);
29 e.maxs_x = rint(e.maxs_x);
30 e.maxs_y = rint(e.maxs_y);
31 e.maxs_z = rint(e.maxs_z);
36 void() door_rotating_go_down;
37 void() door_rotating_go_up;
41 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO))
44 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
50 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
51 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
54 //Dont chamge direction for dead or dying stuff
55 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
59 if (self.state == STATE_DOWN)
60 if (self.classname == "door")
65 door_rotating_go_up ();
68 if (self.classname == "door")
73 door_rotating_go_down ();
80 //gib dying stuff just to make sure
81 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
82 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
90 if (self.noise1 != "")
91 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
92 self.state = STATE_TOP;
93 if (self.spawnflags & DOOR_TOGGLE)
94 return; // don't come down automatically
95 if (self.classname == "door")
97 self.think = door_go_down;
100 self.think = door_rotating_go_down;
102 self.nextthink = self.ltime + self.wait;
105 void door_hit_bottom()
107 if (self.noise1 != "")
108 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
109 self.state = STATE_BOTTOM;
114 if (self.noise2 != "")
115 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
118 self.takedamage = DAMAGE_YES;
119 self.health = self.max_health;
127 "going down at time ", ftos(time), "\n");
129 self.state = STATE_DOWN;
130 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
135 if (self.state == STATE_UP)
136 return; // already going up
138 if (self.state == STATE_TOP)
139 { // reset top wait time
140 self.nextthink = self.ltime + self.wait;
144 if (self.noise2 != "")
145 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
146 self.state = STATE_UP;
147 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
150 oldmessage = self.message;
153 self.message = oldmessage;
158 =============================================================================
162 =============================================================================
165 float door_check_keys(void)
179 // this door require a key
180 // only a player can have a key
181 if (!IS_PLAYER(other))
184 if (item_keys_usekey(door, other))
186 // some keys were used
187 if (other.key_door_messagetime <= time)
190 play2(other, "misc/talk.wav");
191 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
193 other.key_door_messagetime = time + 2;
199 if (other.key_door_messagetime <= time)
202 play2(other, "misc/talk.wav");
203 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
205 other.key_door_messagetime = time + 2;
212 // door is now unlocked
213 play2(other, "misc/talk.wav");
214 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
227 if (self.owner != self)
228 objerror ("door_fire: self.owner != self");
232 if (self.spawnflags & DOOR_TOGGLE)
234 if (self.state == STATE_UP || self.state == STATE_TOP)
239 if (self.classname == "door")
245 door_rotating_go_down ();
248 } while ( (self != starte) && (self != world) );
254 // trigger all paired doors
258 if (self.classname == "door")
263 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
264 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
266 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
267 self.pos2 = '0 0 0' - self.pos2;
269 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
270 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
271 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
273 door_rotating_go_up ();
277 } while ( (self != starte) && (self != world) );
285 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
296 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
299 if(self.spawnflags & DOOR_NOSPLASH)
300 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
302 self.health = self.health - damage;
306 // don't allow opening doors through damage if keys are required
310 if (self.health <= 0)
314 self.health = self.max_health;
315 self.takedamage = DAMAGE_NO; // wil be reset upon return
332 if (!IS_PLAYER(other))
334 if (self.owner.attack_finished_single > time)
337 self.owner.attack_finished_single = time + 2;
340 if (!(self.owner.dmg) && (self.owner.message != ""))
342 if (IS_CLIENT(other))
343 centerprint(other, self.owner.message);
344 play2(other, "misc/talk.wav");
349 void door_generic_plat_blocked()
352 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
354 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
361 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
362 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
365 //Dont chamge direction for dead or dying stuff
366 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
370 if (self.state == STATE_DOWN)
371 door_rotating_go_up ();
373 door_rotating_go_down ();
379 //gib dying stuff just to make sure
380 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
381 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
387 void door_rotating_hit_top()
389 if (self.noise1 != "")
390 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
391 self.state = STATE_TOP;
392 if (self.spawnflags & DOOR_TOGGLE)
393 return; // don't come down automatically
394 self.think = door_rotating_go_down;
395 self.nextthink = self.ltime + self.wait;
398 void door_rotating_hit_bottom()
400 if (self.noise1 != "")
401 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
402 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
404 self.pos2 = '0 0 0' - self.pos2;
407 self.state = STATE_BOTTOM;
410 void door_rotating_go_down()
412 if (self.noise2 != "")
413 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
416 self.takedamage = DAMAGE_YES;
417 self.health = self.max_health;
420 self.state = STATE_DOWN;
421 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
424 void door_rotating_go_up()
426 if (self.state == STATE_UP)
427 return; // already going up
429 if (self.state == STATE_TOP)
430 { // reset top wait time
431 self.nextthink = self.ltime + self.wait;
434 if (self.noise2 != "")
435 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
436 self.state = STATE_UP;
437 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
440 oldmessage = self.message;
443 self.message = oldmessage;
448 =========================================
451 Spawned if a door lacks a real activator
452 =========================================
455 void door_trigger_touch()
457 if (other.health < 1)
459 if (!(other.iscreature && !PHYS_DEAD(other)))
461 if(!(IS_CLIENT(other) && !PHYS_DEAD(other)))
465 if (time < self.attack_finished_single)
468 // check if door is locked
469 if (!door_check_keys())
472 self.attack_finished_single = time + 1;
482 float door_trigger_send(entity to, float sf)
484 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR_TRIGGER);
486 WriteShort(MSG_ENTITY, num_for_edict(self.owner));
487 WriteCoord(MSG_ENTITY, self.origin_x);
488 WriteCoord(MSG_ENTITY, self.origin_y);
489 WriteCoord(MSG_ENTITY, self.origin_z);
491 WriteCoord(MSG_ENTITY, self.mins_x);
492 WriteCoord(MSG_ENTITY, self.mins_y);
493 WriteCoord(MSG_ENTITY, self.mins_z);
494 WriteCoord(MSG_ENTITY, self.maxs_x);
495 WriteCoord(MSG_ENTITY, self.maxs_y);
496 WriteCoord(MSG_ENTITY, self.maxs_z);
501 void door_trigger_link(entity trig)
503 Net_LinkEntity(trig, FALSE, 0, door_trigger_send);
506 void spawn_field(vector fmins, vector fmaxs)
509 vector t1 = fmins, t2 = fmaxs;
512 trigger.classname = "doortriggerfield";
513 trigger.movetype = MOVETYPE_NONE;
514 trigger.solid = SOLID_TRIGGER;
515 trigger.owner = self;
516 trigger.touch = door_trigger_touch;
518 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
519 door_trigger_link(trigger);
524 void ent_door_trigger()
526 float entnum = ReadShort();
527 self.origin_x = ReadCoord();
528 self.origin_y = ReadCoord();
529 self.origin_z = ReadCoord();
530 setorigin(self, self.origin);
531 self.mins_x = ReadCoord();
532 self.mins_y = ReadCoord();
533 self.mins_z = ReadCoord();
534 self.maxs_x = ReadCoord();
535 self.maxs_y = ReadCoord();
536 self.maxs_z = ReadCoord();
537 setsize(self, self.mins, self.maxs);
539 self.owner = findfloat(world, sv_entnum, entnum); // if owner doesn't exist, it shouldn't matter much
540 self.classname = "doortriggerfield";
541 self.movetype = MOVETYPE_NONE;
542 self.solid = SOLID_TRIGGER;
543 self.trigger_touch = door_trigger_touch;
544 self.draw = trigger_draw_generic;
545 self.drawmask = MASK_NORMAL;
546 self.move_time = time;
559 entity LinkDoors_nextent(entity cur, entity near, entity pass)
561 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
567 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
570 if((e1.absmin_x > e2.absmax_x + DELTA)
571 || (e1.absmin_y > e2.absmax_y + DELTA)
572 || (e1.absmin_z > e2.absmax_z + DELTA)
573 || (e2.absmin_x > e1.absmax_x + DELTA)
574 || (e2.absmin_y > e1.absmax_y + DELTA)
575 || (e2.absmin_z > e1.absmax_z + DELTA)
589 return; // already linked by another door
590 if (self.spawnflags & 4)
592 self.owner = self.enemy = self;
600 spawn_field(self.absmin, self.absmax);
602 return; // don't want to link this door
605 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
607 // set owner, and make a loop of the chain
608 dprint("LinkDoors: linking doors:");
609 for(t = self; ; t = t.enemy)
611 dprint(" ", etos(t));
621 // collect health, targetname, message, size
624 for(t = self; ; t = t.enemy)
626 if(t.health && !self.health)
627 self.health = t.health;
628 if((t.targetname != "") && (self.targetname == ""))
629 self.targetname = t.targetname;
630 if((t.message != "") && (self.message == ""))
631 self.message = t.message;
632 if (t.absmin_x < cmins_x)
633 cmins_x = t.absmin_x;
634 if (t.absmin_y < cmins_y)
635 cmins_y = t.absmin_y;
636 if (t.absmin_z < cmins_z)
637 cmins_z = t.absmin_z;
638 if (t.absmax_x > cmaxs_x)
639 cmaxs_x = t.absmax_x;
640 if (t.absmax_y > cmaxs_y)
641 cmaxs_y = t.absmax_y;
642 if (t.absmax_z > cmaxs_z)
643 cmaxs_z = t.absmax_z;
648 // distribute health, targetname, message
649 for(t = self; t; t = t.enemy)
651 t.health = self.health;
652 t.targetname = self.targetname;
653 t.message = self.message;
658 // shootable, or triggered doors just needed the owner/enemy links,
659 // they don't spawn a field
668 spawn_field(cmins, cmaxs);
672 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
673 if two doors touch, they are assumed to be connected and operate as a unit.
675 TOGGLE causes the door to wait in both the start and end states for a trigger event.
677 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).
679 GOLD_KEY causes the door to open only if the activator holds a gold key.
681 SILVER_KEY causes the door to open only if the activator holds a silver key.
683 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
684 "angle" determines the opening direction
685 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
686 "health" if set, door must be shot open
687 "speed" movement speed (100 default)
688 "wait" wait before returning (3 default, -1 = never return)
689 "lip" lip remaining at end of move (8 default)
690 "dmg" damage to inflict when blocked (2 default)
697 FIXME: only one sound set available at the time being
701 float door_send(entity to, float sf)
703 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
704 WriteByte(MSG_ENTITY, sf);
706 if(sf & SF_TRIGGER_INIT)
708 WriteString(MSG_ENTITY, self.classname);
709 WriteByte(MSG_ENTITY, self.spawnflags);
710 WriteShort(MSG_ENTITY, ((self.owner == self || !self.owner) ? -1 : num_for_edict(self.owner)));
711 WriteShort(MSG_ENTITY, ((self.enemy == self || !self.enemy) ? -1 : num_for_edict(self.enemy)));
712 WriteShort(MSG_ENTITY, num_for_edict(self));
714 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
716 WriteAngle(MSG_ENTITY, self.origin_x);
717 WriteAngle(MSG_ENTITY, self.origin_y);
718 WriteAngle(MSG_ENTITY, self.origin_z);
720 WriteCoord(MSG_ENTITY, self.mins_x);
721 WriteCoord(MSG_ENTITY, self.mins_y);
722 WriteCoord(MSG_ENTITY, self.mins_z);
723 WriteCoord(MSG_ENTITY, self.maxs_x);
724 WriteCoord(MSG_ENTITY, self.maxs_y);
725 WriteCoord(MSG_ENTITY, self.maxs_z);
727 WriteCoord(MSG_ENTITY, self.movedir_x);
728 WriteCoord(MSG_ENTITY, self.movedir_y);
729 WriteCoord(MSG_ENTITY, self.movedir_z);
731 WriteAngle(MSG_ENTITY, self.angles_x);
732 WriteAngle(MSG_ENTITY, self.angles_y);
733 WriteAngle(MSG_ENTITY, self.angles_z);
735 WriteAngle(MSG_ENTITY, self.pos1_x);
736 WriteAngle(MSG_ENTITY, self.pos1_y);
737 WriteAngle(MSG_ENTITY, self.pos1_z);
738 WriteAngle(MSG_ENTITY, self.pos2_x);
739 WriteAngle(MSG_ENTITY, self.pos2_y);
740 WriteAngle(MSG_ENTITY, self.pos2_z);
742 WriteCoord(MSG_ENTITY, self.size_x);
743 WriteCoord(MSG_ENTITY, self.size_y);
744 WriteCoord(MSG_ENTITY, self.size_z);
746 WriteByte(MSG_ENTITY, self.wait);
747 WriteShort(MSG_ENTITY, self.speed);
748 WriteByte(MSG_ENTITY, self.lip);
749 WriteByte(MSG_ENTITY, self.state);
750 WriteShort(MSG_ENTITY, self.ltime);
752 WriteString(MSG_ENTITY, self.model);
753 WriteShort(MSG_ENTITY, self.modelindex);
756 if(sf & SF_TRIGGER_RESET)
758 // client makes use of this, we do not
761 if(sf & SF_TRIGGER_UPDATE)
763 WriteAngle(MSG_ENTITY, self.origin_x);
764 WriteAngle(MSG_ENTITY, self.origin_y);
765 WriteAngle(MSG_ENTITY, self.origin_z);
767 WriteAngle(MSG_ENTITY, self.pos1_x);
768 WriteAngle(MSG_ENTITY, self.pos1_y);
769 WriteAngle(MSG_ENTITY, self.pos1_z);
770 WriteAngle(MSG_ENTITY, self.pos2_x);
771 WriteAngle(MSG_ENTITY, self.pos2_y);
772 WriteAngle(MSG_ENTITY, self.pos2_z);
780 // set size now, as everything is loaded
782 Net_LinkEntity(self, FALSE, 0, door_send);
785 void door_init_startopen()
787 setorigin (self, self.pos2);
788 self.pos2 = self.pos1;
789 self.pos1 = self.origin;
791 self.SendFlags |= SF_TRIGGER_UPDATE;
798 setorigin(self, self.pos1);
799 self.velocity = '0 0 0';
800 self.state = STATE_BOTTOM;
801 self.think = func_null;
805 self.SendFlags |= SF_TRIGGER_RESET;
811 // spawnflags require key (for now only func_door)
812 void spawnfunc_func_door()
814 // Quake 1 keys compatibility
815 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
816 self.itemkeys |= ITEM_KEY_BIT(0);
817 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
818 self.itemkeys |= ITEM_KEY_BIT(1);
822 self.max_health = self.health;
823 if (!InitMovingBrushTrigger())
825 self.effects |= EF_LOWPRECISION;
826 self.classname = "door";
828 self.blocked = door_blocked;
831 if(self.dmg && (self.message == ""))
832 self.message = "was squished";
833 if(self.dmg && (self.message2 == ""))
834 self.message2 = "was squished by";
838 precache_sound ("plats/medplat1.wav");
839 precache_sound ("plats/medplat2.wav");
840 self.noise2 = "plats/medplat1.wav";
841 self.noise1 = "plats/medplat2.wav";
851 self.pos1 = self.origin;
852 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
854 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
855 // but spawn in the open position
856 if (self.spawnflags & DOOR_START_OPEN)
857 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
859 self.state = STATE_BOTTOM;
863 self.takedamage = DAMAGE_YES;
864 self.event_damage = door_damage;
870 self.touch = door_touch;
872 // LinkDoors can't be done until all of the doors have been spawned, so
873 // the sizes can be detected properly.
874 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
876 self.reset = door_reset;
883 float sf = ReadByte();
885 if(sf & SF_TRIGGER_INIT)
887 self.classname = strzone(ReadString());
888 self.spawnflags = ReadByte();
889 float myowner = ReadShort();
890 float myenemy = ReadShort();
891 self.sv_entnum = ReadShort();
893 self.warpzone_isboxy = ReadByte();
895 self.origin_x = ReadAngle();
896 self.origin_y = ReadAngle();
897 self.origin_z = ReadAngle();
898 setorigin(self, self.origin);
900 self.mins_x = ReadCoord();
901 self.mins_y = ReadCoord();
902 self.mins_z = ReadCoord();
903 self.maxs_x = ReadCoord();
904 self.maxs_y = ReadCoord();
905 self.maxs_z = ReadCoord();
906 setsize(self, self.mins, self.maxs);
908 self.movedir_x = ReadCoord();
909 self.movedir_y = ReadCoord();
910 self.movedir_z = ReadCoord();
912 self.angles_x = ReadAngle();
913 self.angles_y = ReadAngle();
914 self.angles_z = ReadAngle();
916 self.pos1_x = ReadAngle();
917 self.pos1_y = ReadAngle();
918 self.pos1_z = ReadAngle();
919 self.pos2_x = ReadAngle();
920 self.pos2_y = ReadAngle();
921 self.pos2_z = ReadAngle();
923 self.size_x = ReadCoord();
924 self.size_y = ReadCoord();
925 self.size_z = ReadCoord();
927 self.wait = ReadByte();
928 self.speed = ReadShort();
929 self.lip = ReadByte();
930 self.state = ReadByte();
931 self.ltime = ReadShort();
933 self.model = strzone(ReadString());
934 self.modelindex = ReadShort();
936 self.movetype = MOVETYPE_PUSH;
937 self.solid = SOLID_TRIGGER;
938 self.trigger_touch = door_touch;
939 self.draw = trigger_draw_generic;
940 self.drawmask = MASK_NORMAL;
941 self.move_time = time;
943 self.blocked = door_blocked;
945 self.owner = ((myowner == -1) ? self : findfloat(world, sv_entnum, myowner));
946 self.enemy = ((myenemy == -1) ? self : findfloat(world, sv_entnum, myenemy));
949 if(sf & SF_TRIGGER_RESET)
954 if(sf & SF_TRIGGER_UPDATE)
956 self.origin_x = ReadAngle();
957 self.origin_y = ReadAngle();
958 self.origin_z = ReadAngle();
959 setorigin(self, self.origin);
961 self.pos1_x = ReadAngle();
962 self.pos1_y = ReadAngle();
963 self.pos1_z = ReadAngle();
964 self.pos2_x = ReadAngle();
965 self.pos2_y = ReadAngle();
966 self.pos2_z = ReadAngle();