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) );
275 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
279 SELFCALL(self.owner, door_fire());
284 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
286 if(self.spawnflags & DOOR_NOSPLASH)
287 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
289 self.health = self.health - damage;
293 // don't allow opening doors through damage if keys are required
297 if (self.health <= 0)
299 self.owner.health = self.owner.max_health;
300 self.owner.takedamage = DAMAGE_NO; // wil be reset upon return
301 SELFCALL(self.owner, door_use());
317 if (!IS_PLAYER(other))
319 if (self.owner.attack_finished_single > time)
322 self.owner.attack_finished_single = time + 2;
325 if (!(self.owner.dmg) && (self.owner.message != ""))
327 if (IS_CLIENT(other))
328 centerprint(other, self.owner.message);
329 play2(other, "misc/talk.wav");
334 void door_generic_plat_blocked()
337 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
339 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
346 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
347 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
350 //Dont chamge direction for dead or dying stuff
351 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
355 if (self.state == STATE_DOWN)
356 door_rotating_go_up ();
358 door_rotating_go_down ();
364 //gib dying stuff just to make sure
365 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
366 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
372 void door_rotating_hit_top()
374 if (self.noise1 != "")
375 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
376 self.state = STATE_TOP;
377 if (self.spawnflags & DOOR_TOGGLE)
378 return; // don't come down automatically
379 self.SUB_THINK = door_rotating_go_down;
380 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
383 void door_rotating_hit_bottom()
385 if (self.noise1 != "")
386 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
387 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
389 self.pos2 = '0 0 0' - self.pos2;
392 self.state = STATE_BOTTOM;
395 void door_rotating_go_down()
397 if (self.noise2 != "")
398 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
401 self.takedamage = DAMAGE_YES;
402 self.health = self.max_health;
405 self.state = STATE_DOWN;
406 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
409 void door_rotating_go_up()
411 if (self.state == STATE_UP)
412 return; // already going up
414 if (self.state == STATE_TOP)
415 { // reset top wait time
416 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
419 if (self.noise2 != "")
420 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
421 self.state = STATE_UP;
422 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
425 oldmessage = self.message;
428 self.message = oldmessage;
433 =========================================
436 Spawned if a door lacks a real activator
437 =========================================
440 void door_trigger_touch()
442 if (other.health < 1)
444 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !PHYS_DEAD(other)))
446 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !PHYS_DEAD(other)))
450 if (time < self.attack_finished_single)
453 // check if door is locked
454 if (!door_check_keys())
457 self.attack_finished_single = time + 1;
465 void spawn_field(vector fmins, vector fmaxs)
468 vector t1 = fmins, t2 = fmaxs;
471 trigger.classname = "doortriggerfield";
472 trigger.movetype = MOVETYPE_NONE;
473 trigger.solid = SOLID_TRIGGER;
474 trigger.owner = self;
476 trigger.touch = door_trigger_touch;
478 trigger.trigger_touch = door_trigger_touch;
479 trigger.draw = trigger_draw_generic;
480 trigger.drawmask = MASK_NORMAL;
483 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
495 entity LinkDoors_nextent(entity cur, entity near, entity pass)
497 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
503 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
506 if((e1.absmin_x > e2.absmax_x + DELTA)
507 || (e1.absmin_y > e2.absmax_y + DELTA)
508 || (e1.absmin_z > e2.absmax_z + DELTA)
509 || (e2.absmin_x > e1.absmax_x + DELTA)
510 || (e2.absmin_y > e1.absmax_y + DELTA)
511 || (e2.absmin_z > e1.absmax_z + DELTA)
529 return; // already linked by another door
530 if (self.spawnflags & 4)
532 self.owner = self.enemy = self;
541 spawn_field(self.absmin, self.absmax);
543 return; // don't want to link this door
546 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
548 // set owner, and make a loop of the chain
549 LOG_TRACE("LinkDoors: linking doors:");
550 for(t = self; ; t = t.enemy)
552 LOG_TRACE(" ", etos(t));
562 // collect health, targetname, message, size
565 for(t = self; ; t = t.enemy)
567 if(t.health && !self.health)
568 self.health = t.health;
569 if((t.targetname != "") && (self.targetname == ""))
570 self.targetname = t.targetname;
571 if((t.message != "") && (self.message == ""))
572 self.message = t.message;
573 if (t.absmin_x < cmins_x)
574 cmins_x = t.absmin_x;
575 if (t.absmin_y < cmins_y)
576 cmins_y = t.absmin_y;
577 if (t.absmin_z < cmins_z)
578 cmins_z = t.absmin_z;
579 if (t.absmax_x > cmaxs_x)
580 cmaxs_x = t.absmax_x;
581 if (t.absmax_y > cmaxs_y)
582 cmaxs_y = t.absmax_y;
583 if (t.absmax_z > cmaxs_z)
584 cmaxs_z = t.absmax_z;
589 // distribute health, targetname, message
590 for(t = self; t; t = t.enemy)
592 t.health = self.health;
593 t.targetname = self.targetname;
594 t.message = self.message;
599 // shootable, or triggered doors just needed the owner/enemy links,
600 // they don't spawn a field
609 spawn_field(cmins, cmaxs);
613 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
614 if two doors touch, they are assumed to be connected and operate as a unit.
616 TOGGLE causes the door to wait in both the start and end states for a trigger event.
618 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).
620 GOLD_KEY causes the door to open only if the activator holds a gold key.
622 SILVER_KEY causes the door to open only if the activator holds a silver key.
624 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
625 "angle" determines the opening direction
626 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
627 "health" if set, door must be shot open
628 "speed" movement speed (100 default)
629 "wait" wait before returning (3 default, -1 = never return)
630 "lip" lip remaining at end of move (8 default)
631 "dmg" damage to inflict when blocked (2 default)
638 FIXME: only one sound set available at the time being
642 float door_send(entity to, float sf)
644 WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
645 WriteByte(MSG_ENTITY, sf);
647 if(sf & SF_TRIGGER_INIT)
649 WriteString(MSG_ENTITY, self.classname);
650 WriteByte(MSG_ENTITY, self.spawnflags);
652 WriteString(MSG_ENTITY, self.model);
654 trigger_common_write(true);
656 WriteCoord(MSG_ENTITY, self.pos1_x);
657 WriteCoord(MSG_ENTITY, self.pos1_y);
658 WriteCoord(MSG_ENTITY, self.pos1_z);
659 WriteCoord(MSG_ENTITY, self.pos2_x);
660 WriteCoord(MSG_ENTITY, self.pos2_y);
661 WriteCoord(MSG_ENTITY, self.pos2_z);
663 WriteCoord(MSG_ENTITY, self.size_x);
664 WriteCoord(MSG_ENTITY, self.size_y);
665 WriteCoord(MSG_ENTITY, self.size_z);
667 WriteShort(MSG_ENTITY, self.wait);
668 WriteShort(MSG_ENTITY, self.speed);
669 WriteByte(MSG_ENTITY, self.lip);
670 WriteByte(MSG_ENTITY, self.state);
671 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
674 if(sf & SF_TRIGGER_RESET)
676 // client makes use of this, we do not
679 if(sf & SF_TRIGGER_UPDATE)
681 WriteCoord(MSG_ENTITY, self.origin_x);
682 WriteCoord(MSG_ENTITY, self.origin_y);
683 WriteCoord(MSG_ENTITY, self.origin_z);
685 WriteCoord(MSG_ENTITY, self.pos1_x);
686 WriteCoord(MSG_ENTITY, self.pos1_y);
687 WriteCoord(MSG_ENTITY, self.pos1_z);
688 WriteCoord(MSG_ENTITY, self.pos2_x);
689 WriteCoord(MSG_ENTITY, self.pos2_y);
690 WriteCoord(MSG_ENTITY, self.pos2_z);
698 // set size now, as everything is loaded
700 //Net_LinkEntity(self, false, 0, door_send);
704 void door_init_startopen()
706 SUB_SETORIGIN(self, self.pos2);
707 self.pos2 = self.pos1;
708 self.pos1 = self.origin;
711 self.SendFlags |= SF_TRIGGER_UPDATE;
717 SUB_SETORIGIN(self, self.pos1);
718 self.SUB_VELOCITY = '0 0 0';
719 self.state = STATE_BOTTOM;
720 self.SUB_THINK = func_null;
721 self.SUB_NEXTTHINK = 0;
724 self.SendFlags |= SF_TRIGGER_RESET;
730 // spawnflags require key (for now only func_door)
731 void spawnfunc_func_door()
733 // Quake 1 keys compatibility
734 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
735 self.itemkeys |= ITEM_KEY_BIT(0);
736 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
737 self.itemkeys |= ITEM_KEY_BIT(1);
741 self.max_health = self.health;
742 if (!InitMovingBrushTrigger())
744 self.effects |= EF_LOWPRECISION;
745 self.classname = "door";
747 self.blocked = door_blocked;
750 if(self.dmg && (self.message == ""))
751 self.message = "was squished";
752 if(self.dmg && (self.message2 == ""))
753 self.message2 = "was squished by";
757 precache_sound ("plats/medplat1.wav");
758 precache_sound ("plats/medplat2.wav");
759 self.noise2 = "plats/medplat1.wav";
760 self.noise1 = "plats/medplat2.wav";
770 self.pos1 = self.SUB_ORIGIN;
771 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
773 if(self.spawnflags & DOOR_NONSOLID)
774 self.solid = SOLID_NOT;
776 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
777 // but spawn in the open position
778 if (self.spawnflags & DOOR_START_OPEN)
779 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
781 self.state = STATE_BOTTOM;
785 self.takedamage = DAMAGE_YES;
786 self.event_damage = door_damage;
792 self.touch = door_touch;
794 // LinkDoors can't be done until all of the doors have been spawned, so
795 // the sizes can be detected properly.
796 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
798 self.reset = door_reset;
805 Movetype_Physics_NoMatchServer();
807 trigger_draw_generic();
812 float sf = ReadByte();
814 if(sf & SF_TRIGGER_INIT)
816 self.classname = strzone(ReadString());
817 self.spawnflags = ReadByte();
819 self.mdl = strzone(ReadString());
820 setmodel(self, self.mdl);
822 trigger_common_read(true);
824 self.pos1_x = ReadCoord();
825 self.pos1_y = ReadCoord();
826 self.pos1_z = ReadCoord();
827 self.pos2_x = ReadCoord();
828 self.pos2_y = ReadCoord();
829 self.pos2_z = ReadCoord();
831 self.size_x = ReadCoord();
832 self.size_y = ReadCoord();
833 self.size_z = ReadCoord();
835 self.wait = ReadShort();
836 self.speed = ReadShort();
837 self.lip = ReadByte();
838 self.state = ReadByte();
839 self.SUB_LTIME = ReadCoord();
841 self.solid = SOLID_BSP;
842 self.movetype = MOVETYPE_PUSH;
843 self.trigger_touch = door_touch;
844 self.draw = door_draw;
845 self.drawmask = MASK_NORMAL;
850 if(self.spawnflags & DOOR_START_OPEN)
851 door_init_startopen();
853 self.move_time = time;
854 self.move_origin = self.origin;
855 self.move_movetype = MOVETYPE_PUSH;
856 self.move_angles = self.angles;
857 self.move_blocked = door_blocked;
860 if(sf & SF_TRIGGER_RESET)
865 if(sf & SF_TRIGGER_UPDATE)
867 self.origin_x = ReadCoord();
868 self.origin_y = ReadCoord();
869 self.origin_z = ReadCoord();
870 setorigin(self, self.origin);
871 self.move_origin = self.origin;
873 self.pos1_x = ReadCoord();
874 self.pos1_y = ReadCoord();
875 self.pos1_z = ReadCoord();
876 self.pos2_x = ReadCoord();
877 self.pos2_y = ReadCoord();
878 self.pos2_z = ReadCoord();