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) && (other.takedamage != DAMAGE_NO))
33 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
39 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
40 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
43 //Dont chamge direction for dead or dying stuff
44 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
48 if (self.state == STATE_DOWN)
49 if (self.classname == "door")
54 door_rotating_go_up ();
57 if (self.classname == "door")
62 door_rotating_go_down ();
69 //gib dying stuff just to make sure
70 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
71 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
79 if (self.noise1 != "")
80 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
81 self.state = STATE_TOP;
82 if (self.spawnflags & DOOR_TOGGLE)
83 return; // don't come down automatically
84 if (self.classname == "door")
86 self.think = door_go_down;
89 self.think = door_rotating_go_down;
91 self.nextthink = self.ltime + self.wait;
94 void door_hit_bottom()
96 if (self.noise1 != "")
97 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
98 self.state = STATE_BOTTOM;
103 if (self.noise2 != "")
104 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
107 self.takedamage = DAMAGE_YES;
108 self.health = self.max_health;
116 "going down at time ", ftos(time), "\n");
118 self.state = STATE_DOWN;
119 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
124 if (self.state == STATE_UP)
125 return; // already going up
127 if (self.state == STATE_TOP)
128 { // reset top wait time
129 self.nextthink = self.ltime + self.wait;
133 if (self.noise2 != "")
134 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
135 self.state = STATE_UP;
136 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
139 oldmessage = self.message;
142 self.message = oldmessage;
147 =============================================================================
151 =============================================================================
154 float door_check_keys(void)
168 // this door require a key
169 // only a player can have a key
170 if (!IS_PLAYER(other))
174 if (item_keys_usekey(door, other))
176 // some keys were used
177 if (other.key_door_messagetime <= time)
180 play2(other, "misc/talk.wav");
181 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
182 other.key_door_messagetime = time + 2;
188 if (other.key_door_messagetime <= time)
190 play2(other, "misc/talk.wav");
191 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
193 other.key_door_messagetime = time + 2;
201 // door is now unlocked
202 play2(other, "misc/talk.wav");
203 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
216 if (self.owner != self)
217 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) );
274 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
285 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
288 if(self.spawnflags & DOOR_NOSPLASH)
289 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
291 self.health = self.health - damage;
295 // don't allow opening doors through damage if keys are required
299 if (self.health <= 0)
303 self.health = self.max_health;
304 self.takedamage = DAMAGE_NO; // wil be reset upon return
321 if (!IS_PLAYER(other))
323 if (self.owner.attack_finished_single > time)
326 self.owner.attack_finished_single = time + 2;
329 if (!(self.owner.dmg) && (self.owner.message != ""))
331 if (IS_CLIENT(other))
332 centerprint(other, self.owner.message);
333 play2(other, "misc/talk.wav");
338 void door_generic_plat_blocked()
341 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
343 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
350 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
351 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
354 //Dont chamge direction for dead or dying stuff
355 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
359 if (self.state == STATE_DOWN)
360 door_rotating_go_up ();
362 door_rotating_go_down ();
368 //gib dying stuff just to make sure
369 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
370 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
376 void door_rotating_hit_top()
378 if (self.noise1 != "")
379 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
380 self.state = STATE_TOP;
381 if (self.spawnflags & DOOR_TOGGLE)
382 return; // don't come down automatically
383 self.think = door_rotating_go_down;
384 self.nextthink = self.ltime + self.wait;
387 void door_rotating_hit_bottom()
389 if (self.noise1 != "")
390 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
391 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
393 self.pos2 = '0 0 0' - self.pos2;
396 self.state = STATE_BOTTOM;
399 void door_rotating_go_down()
401 if (self.noise2 != "")
402 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
405 self.takedamage = DAMAGE_YES;
406 self.health = self.max_health;
409 self.state = STATE_DOWN;
410 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
413 void door_rotating_go_up()
415 if (self.state == STATE_UP)
416 return; // already going up
418 if (self.state == STATE_TOP)
419 { // reset top wait time
420 self.nextthink = self.ltime + self.wait;
423 if (self.noise2 != "")
424 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
425 self.state = STATE_UP;
426 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
429 oldmessage = self.message;
432 self.message = oldmessage;
437 =========================================
440 Spawned if a door lacks a real activator
441 =========================================
444 void door_trigger_touch()
446 if (other.health < 1)
448 if (!(other.iscreature && !PHYS_DEAD(other)))
450 if(!(IS_CLIENT(other) && !PHYS_DEAD(other)))
454 if (time < self.attack_finished_single)
457 // check if door is locked
458 if (!door_check_keys())
461 self.attack_finished_single = time + 1;
471 float door_trigger_send(entity to, float sf)
473 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR_TRIGGER);
475 WriteShort(MSG_ENTITY, num_for_edict(self.owner));
476 WriteCoord(MSG_ENTITY, self.origin_x);
477 WriteCoord(MSG_ENTITY, self.origin_y);
478 WriteCoord(MSG_ENTITY, self.origin_z);
480 WriteCoord(MSG_ENTITY, self.mins_x);
481 WriteCoord(MSG_ENTITY, self.mins_y);
482 WriteCoord(MSG_ENTITY, self.mins_z);
483 WriteCoord(MSG_ENTITY, self.maxs_x);
484 WriteCoord(MSG_ENTITY, self.maxs_y);
485 WriteCoord(MSG_ENTITY, self.maxs_z);
490 void door_trigger_link(entity trig)
492 Net_LinkEntity(trig, FALSE, 0, door_trigger_send);
495 void spawn_field(vector fmins, vector fmaxs)
498 vector t1 = fmins, t2 = fmaxs;
501 trigger.classname = "doortriggerfield";
502 trigger.movetype = MOVETYPE_NONE;
503 trigger.solid = SOLID_TRIGGER;
504 trigger.owner = self;
505 trigger.touch = door_trigger_touch;
507 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
508 door_trigger_link(trigger);
513 void ent_door_trigger()
515 float entnum = ReadShort();
516 self.origin_x = ReadCoord();
517 self.origin_y = ReadCoord();
518 self.origin_z = ReadCoord();
519 setorigin(self, self.origin);
520 self.mins_x = ReadCoord();
521 self.mins_y = ReadCoord();
522 self.mins_z = ReadCoord();
523 self.maxs_x = ReadCoord();
524 self.maxs_y = ReadCoord();
525 self.maxs_z = ReadCoord();
526 setsize(self, self.mins, self.maxs);
528 self.owner = findfloat(world, sv_entnum, entnum); // if owner doesn't exist, it shouldn't matter much
529 self.classname = "doortriggerfield";
530 self.movetype = MOVETYPE_NONE;
531 self.solid = SOLID_TRIGGER;
532 self.trigger_touch = door_trigger_touch;
533 self.draw = trigger_draw_generic;
534 self.drawmask = MASK_NORMAL;
535 self.move_time = time;
548 entity LinkDoors_nextent(entity cur, entity near, entity pass)
550 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
556 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
559 if((e1.absmin_x > e2.absmax_x + DELTA)
560 || (e1.absmin_y > e2.absmax_y + DELTA)
561 || (e1.absmin_z > e2.absmax_z + DELTA)
562 || (e2.absmin_x > e1.absmax_x + DELTA)
563 || (e2.absmin_y > e1.absmax_y + DELTA)
564 || (e2.absmin_z > e1.absmax_z + DELTA)
578 return; // already linked by another door
579 if (self.spawnflags & 4)
581 self.owner = self.enemy = self;
589 spawn_field(self.absmin, self.absmax);
591 return; // don't want to link this door
594 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
596 // set owner, and make a loop of the chain
597 dprint("LinkDoors: linking doors:");
598 for(t = self; ; t = t.enemy)
600 dprint(" ", etos(t));
610 // collect health, targetname, message, size
613 for(t = self; ; t = t.enemy)
615 if(t.health && !self.health)
616 self.health = t.health;
617 if((t.targetname != "") && (self.targetname == ""))
618 self.targetname = t.targetname;
619 if((t.message != "") && (self.message == ""))
620 self.message = t.message;
621 if (t.absmin_x < cmins_x)
622 cmins_x = t.absmin_x;
623 if (t.absmin_y < cmins_y)
624 cmins_y = t.absmin_y;
625 if (t.absmin_z < cmins_z)
626 cmins_z = t.absmin_z;
627 if (t.absmax_x > cmaxs_x)
628 cmaxs_x = t.absmax_x;
629 if (t.absmax_y > cmaxs_y)
630 cmaxs_y = t.absmax_y;
631 if (t.absmax_z > cmaxs_z)
632 cmaxs_z = t.absmax_z;
637 // distribute health, targetname, message
638 for(t = self; t; t = t.enemy)
640 t.health = self.health;
641 t.targetname = self.targetname;
642 t.message = self.message;
647 // shootable, or triggered doors just needed the owner/enemy links,
648 // they don't spawn a field
657 spawn_field(cmins, cmaxs);
661 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
662 if two doors touch, they are assumed to be connected and operate as a unit.
664 TOGGLE causes the door to wait in both the start and end states for a trigger event.
666 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).
668 GOLD_KEY causes the door to open only if the activator holds a gold key.
670 SILVER_KEY causes the door to open only if the activator holds a silver key.
672 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
673 "angle" determines the opening direction
674 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
675 "health" if set, door must be shot open
676 "speed" movement speed (100 default)
677 "wait" wait before returning (3 default, -1 = never return)
678 "lip" lip remaining at end of move (8 default)
679 "dmg" damage to inflict when blocked (2 default)
686 FIXME: only one sound set available at the time being
690 float door_send(entity to, float sf)
692 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
693 WriteByte(MSG_ENTITY, sf);
695 if(sf & SF_TRIGGER_INIT)
697 WriteString(MSG_ENTITY, self.classname);
698 WriteByte(MSG_ENTITY, self.spawnflags);
699 WriteShort(MSG_ENTITY, ((self.owner == self || !self.owner) ? -1 : num_for_edict(self.owner)));
700 WriteShort(MSG_ENTITY, ((self.enemy == self || !self.enemy) ? -1 : num_for_edict(self.enemy)));
701 WriteShort(MSG_ENTITY, num_for_edict(self));
703 WriteByte(MSG_ENTITY, self.scale);
705 WriteCoord(MSG_ENTITY, self.origin_x);
706 WriteCoord(MSG_ENTITY, self.origin_y);
707 WriteCoord(MSG_ENTITY, self.origin_z);
709 WriteString(MSG_ENTITY, self.model);
711 WriteCoord(MSG_ENTITY, self.mins_x);
712 WriteCoord(MSG_ENTITY, self.mins_y);
713 WriteCoord(MSG_ENTITY, self.mins_z);
714 WriteCoord(MSG_ENTITY, self.maxs_x);
715 WriteCoord(MSG_ENTITY, self.maxs_y);
716 WriteCoord(MSG_ENTITY, self.maxs_z);
718 WriteCoord(MSG_ENTITY, self.movedir_x);
719 WriteCoord(MSG_ENTITY, self.movedir_y);
720 WriteCoord(MSG_ENTITY, self.movedir_z);
722 WriteAngle(MSG_ENTITY, self.angles_x);
723 WriteAngle(MSG_ENTITY, self.angles_y);
724 WriteAngle(MSG_ENTITY, self.angles_z);
726 WriteCoord(MSG_ENTITY, self.pos1_x);
727 WriteCoord(MSG_ENTITY, self.pos1_y);
728 WriteCoord(MSG_ENTITY, self.pos1_z);
729 WriteCoord(MSG_ENTITY, self.pos2_x);
730 WriteCoord(MSG_ENTITY, self.pos2_y);
731 WriteCoord(MSG_ENTITY, self.pos2_z);
733 WriteCoord(MSG_ENTITY, self.size_x);
734 WriteCoord(MSG_ENTITY, self.size_y);
735 WriteCoord(MSG_ENTITY, self.size_z);
737 WriteShort(MSG_ENTITY, self.wait);
738 WriteShort(MSG_ENTITY, self.speed);
739 WriteByte(MSG_ENTITY, self.lip);
740 WriteByte(MSG_ENTITY, self.state);
741 WriteShort(MSG_ENTITY, self.ltime);
744 if(sf & SF_TRIGGER_RESET)
746 // client makes use of this, we do not
749 if(sf & SF_TRIGGER_UPDATE)
751 WriteCoord(MSG_ENTITY, self.origin_x);
752 WriteCoord(MSG_ENTITY, self.origin_y);
753 WriteCoord(MSG_ENTITY, self.origin_z);
755 WriteCoord(MSG_ENTITY, self.pos1_x);
756 WriteCoord(MSG_ENTITY, self.pos1_y);
757 WriteCoord(MSG_ENTITY, self.pos1_z);
758 WriteCoord(MSG_ENTITY, self.pos2_x);
759 WriteCoord(MSG_ENTITY, self.pos2_y);
760 WriteCoord(MSG_ENTITY, self.pos2_z);
768 // set size now, as everything is loaded
770 Net_LinkEntity(self, FALSE, 0, door_send);
773 void door_init_startopen()
775 setorigin (self, self.pos2);
776 self.pos2 = self.pos1;
777 self.pos1 = self.origin;
779 self.SendFlags |= SF_TRIGGER_UPDATE;
786 setorigin(self, self.pos1);
787 self.velocity = '0 0 0';
788 self.state = STATE_BOTTOM;
789 self.think = func_null;
793 self.SendFlags |= SF_TRIGGER_RESET;
799 // spawnflags require key (for now only func_door)
800 void spawnfunc_func_door()
802 // Quake 1 keys compatibility
803 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
804 self.itemkeys |= ITEM_KEY_BIT(0);
805 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
806 self.itemkeys |= ITEM_KEY_BIT(1);
810 self.max_health = self.health;
811 if (!InitMovingBrushTrigger())
813 self.effects |= EF_LOWPRECISION;
814 self.classname = "door";
816 self.blocked = door_blocked;
819 if(self.dmg && (self.message == ""))
820 self.message = "was squished";
821 if(self.dmg && (self.message2 == ""))
822 self.message2 = "was squished by";
826 precache_sound ("plats/medplat1.wav");
827 precache_sound ("plats/medplat2.wav");
828 self.noise2 = "plats/medplat1.wav";
829 self.noise1 = "plats/medplat2.wav";
839 self.pos1 = self.origin;
840 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
842 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
843 // but spawn in the open position
844 if (self.spawnflags & DOOR_START_OPEN)
845 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
847 self.state = STATE_BOTTOM;
851 self.takedamage = DAMAGE_YES;
852 self.event_damage = door_damage;
858 self.touch = door_touch;
860 // LinkDoors can't be done until all of the doors have been spawned, so
861 // the sizes can be detected properly.
862 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
864 self.reset = door_reset;
871 float sf = ReadByte();
873 if(sf & SF_TRIGGER_INIT)
875 self.classname = strzone(ReadString());
876 self.spawnflags = ReadByte();
877 float myowner = ReadShort();
878 float myenemy = ReadShort();
879 self.sv_entnum = ReadShort();
881 self.scale = ReadByte();
883 self.origin_x = ReadCoord();
884 self.origin_y = ReadCoord();
885 self.origin_z = ReadCoord();
886 setorigin(self, self.origin);
888 self.mdl = strzone(ReadString());
889 setmodel(self, self.mdl);
891 self.mins_x = ReadCoord();
892 self.mins_y = ReadCoord();
893 self.mins_z = ReadCoord();
894 self.maxs_x = ReadCoord();
895 self.maxs_y = ReadCoord();
896 self.maxs_z = ReadCoord();
897 setsize(self, self.mins, self.maxs);
899 self.movedir_x = ReadCoord();
900 self.movedir_y = ReadCoord();
901 self.movedir_z = ReadCoord();
903 self.angles_x = ReadAngle();
904 self.angles_y = ReadAngle();
905 self.angles_z = ReadAngle();
907 self.pos1_x = ReadCoord();
908 self.pos1_y = ReadCoord();
909 self.pos1_z = ReadCoord();
910 self.pos2_x = ReadCoord();
911 self.pos2_y = ReadCoord();
912 self.pos2_z = ReadCoord();
914 self.size_x = ReadCoord();
915 self.size_y = ReadCoord();
916 self.size_z = ReadCoord();
918 self.wait = ReadShort();
919 self.speed = ReadShort();
920 self.lip = ReadByte();
921 self.state = ReadByte();
922 self.ltime = ReadShort();
924 self.movetype = MOVETYPE_PUSH;
925 self.solid = SOLID_BSP;
926 self.trigger_touch = door_touch;
927 self.draw = trigger_draw_generic;
928 self.drawmask = MASK_NORMAL;
929 self.move_time = time;
931 self.blocked = door_blocked;
933 print(ftos(self.entnum), " ", ftos(self.sv_entnum), "\n");
935 self.owner = ((myowner == -1) ? self : findfloat(world, sv_entnum, myowner));
936 self.enemy = ((myenemy == -1) ? self : findfloat(world, sv_entnum, myenemy));
939 if(sf & SF_TRIGGER_RESET)
944 if(sf & SF_TRIGGER_UPDATE)
946 self.origin_x = ReadCoord();
947 self.origin_y = ReadCoord();
948 self.origin_z = ReadCoord();
949 setorigin(self, self.origin);
951 self.pos1_x = ReadCoord();
952 self.pos1_y = ReadCoord();
953 self.pos1_z = ReadCoord();
954 self.pos2_x = ReadCoord();
955 self.pos2_y = ReadCoord();
956 self.pos2_z = ReadCoord();