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.m_id, 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.m_id, 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.m_id, 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;
144 SUB_UseTargets(self, NULL, NULL);
145 self.message = oldmessage;
150 =============================================================================
154 =============================================================================
157 bool door_check_keys(entity door, entity player)
166 // this door require a key
167 // only a player can have a key
168 if(!IS_PLAYER(player))
171 int valid = (door.itemkeys & player.itemkeys);
172 door.itemkeys &= ~valid; // only some of the needed keys were given
177 play2(player, SND(TALK));
178 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
186 if(player.key_door_messagetime <= time)
188 play2(player, door.noise3);
189 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
190 player.key_door_messagetime = time + 2;
196 // door needs keys the player doesn't have
198 if(player.key_door_messagetime <= time)
200 play2(player, door.noise3);
201 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
202 player.key_door_messagetime = time + 2;
209 void door_fire(entity this, entity actor, entity trigger)
211 if (this.owner != this)
212 objerror ("door_fire: this.owner != this");
214 if (this.spawnflags & DOOR_TOGGLE)
216 if (this.state == STATE_UP || this.state == STATE_TOP)
220 if (e.classname == "door") {
221 WITHSELF(e, door_go_down());
223 WITHSELF(e, door_rotating_go_down());
226 } while ((e != this) && (e != NULL));
231 // trigger all paired doors
234 if (e.classname == "door") {
235 WITHSELF(e, door_go_up());
237 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
238 if ((e.spawnflags & 2) && other.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
239 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
240 e.pos2 = '0 0 0' - e.pos2;
242 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
243 if (!((e.spawnflags & 2) && (e.spawnflags & 8) && e.state == STATE_DOWN
244 && (((e.lip == 666) && (other.trigger_reverse == 0)) || ((e.lip != 666) && (other.trigger_reverse != 0)))))
246 WITHSELF(e, door_rotating_go_up());
250 } while ((e != this) && (e != NULL));
253 void door_use(entity this, entity actor, entity trigger)
255 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
258 WITHSELF(this.owner, door_fire(this.owner, actor, trigger));
261 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
263 if(this.spawnflags & DOOR_NOSPLASH)
264 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
266 this.health = this.health - damage;
270 // don't allow opening doors through damage if keys are required
274 if (this.health <= 0)
276 this.owner.health = this.owner.max_health;
277 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
278 door_use(this.owner, NULL, NULL);
282 .float door_finished;
294 if (!IS_PLAYER(other))
296 if (self.owner.door_finished > time)
299 self.owner.door_finished = time + 2;
302 if (!(self.owner.dmg) && (self.owner.message != ""))
304 if (IS_CLIENT(other))
305 centerprint(other, self.owner.message);
306 play2(other, self.owner.noise);
311 void door_generic_plat_blocked()
314 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
316 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
323 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
324 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
327 //Dont chamge direction for dead or dying stuff
328 if(IS_DEAD(other) && (other.takedamage == DAMAGE_NO))
332 if (self.state == STATE_DOWN)
333 door_rotating_go_up ();
335 door_rotating_go_down ();
341 //gib dying stuff just to make sure
342 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
343 Damage (other, self, self, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
349 void door_rotating_hit_top()
351 if (self.noise1 != "")
352 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
353 self.state = STATE_TOP;
354 if (self.spawnflags & DOOR_TOGGLE)
355 return; // don't come down automatically
356 self.SUB_THINK = door_rotating_go_down;
357 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
360 void door_rotating_hit_bottom()
362 if (self.noise1 != "")
363 _sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
364 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
366 self.pos2 = '0 0 0' - self.pos2;
369 self.state = STATE_BOTTOM;
372 void door_rotating_go_down()
374 if (self.noise2 != "")
375 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
378 self.takedamage = DAMAGE_YES;
379 self.health = self.max_health;
382 self.state = STATE_DOWN;
383 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
386 void door_rotating_go_up()
388 if (self.state == STATE_UP)
389 return; // already going up
391 if (self.state == STATE_TOP)
392 { // reset top wait time
393 self.SUB_NEXTTHINK = self.SUB_LTIME + self.wait;
396 if (self.noise2 != "")
397 _sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
398 self.state = STATE_UP;
399 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
402 oldmessage = self.message;
404 SUB_UseTargets(self, NULL, other); // TODO: is other needed here?
405 self.message = oldmessage;
410 =========================================
413 Spawned if a door lacks a real activator
414 =========================================
417 void door_trigger_touch()
419 if (other.health < 1)
421 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !IS_DEAD(other)))
423 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !IS_DEAD(other)))
427 if (time < self.door_finished)
430 // check if door is locked
431 if (!door_check_keys(self, other))
434 self.door_finished = time + 1;
436 door_use(this.owner, other, NULL);
439 void door_spawnfield(vector fmins, vector fmaxs)
442 vector t1 = fmins, t2 = fmaxs;
444 trigger = new(doortriggerfield);
445 trigger.movetype = MOVETYPE_NONE;
446 trigger.solid = SOLID_TRIGGER;
447 trigger.owner = self;
449 settouch(trigger, door_trigger_touch);
451 trigger.trigger_touch = door_trigger_touch;
452 trigger.draw = trigger_draw_generic;
453 trigger.drawmask = MASK_NORMAL;
456 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
468 entity LinkDoors_nextent(entity cur, entity near, entity pass)
470 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
476 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
479 if((e1.absmin_x > e2.absmax_x + DELTA)
480 || (e1.absmin_y > e2.absmax_y + DELTA)
481 || (e1.absmin_z > e2.absmax_z + DELTA)
482 || (e2.absmin_x > e1.absmax_x + DELTA)
483 || (e2.absmin_y > e1.absmax_y + DELTA)
484 || (e2.absmin_z > e1.absmax_z + DELTA)
492 void LinkDoors(entity this)
502 return; // already linked by another door
503 if (self.spawnflags & 4)
505 self.owner = self.enemy = self;
514 door_spawnfield(self.absmin, self.absmax);
516 return; // don't want to link this door
519 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
521 // set owner, and make a loop of the chain
522 LOG_TRACE("LinkDoors: linking doors:");
523 for(t = self; ; t = t.enemy)
525 LOG_TRACE(" ", etos(t));
535 // collect health, targetname, message, size
538 for(t = self; ; t = t.enemy)
540 if(t.health && !self.health)
541 self.health = t.health;
542 if((t.targetname != "") && (self.targetname == ""))
543 self.targetname = t.targetname;
544 if((t.message != "") && (self.message == ""))
545 self.message = t.message;
546 if (t.absmin_x < cmins_x)
547 cmins_x = t.absmin_x;
548 if (t.absmin_y < cmins_y)
549 cmins_y = t.absmin_y;
550 if (t.absmin_z < cmins_z)
551 cmins_z = t.absmin_z;
552 if (t.absmax_x > cmaxs_x)
553 cmaxs_x = t.absmax_x;
554 if (t.absmax_y > cmaxs_y)
555 cmaxs_y = t.absmax_y;
556 if (t.absmax_z > cmaxs_z)
557 cmaxs_z = t.absmax_z;
562 // distribute health, targetname, message
563 for(t = self; t; t = t.enemy)
565 t.health = self.health;
566 t.targetname = self.targetname;
567 t.message = self.message;
572 // shootable, or triggered doors just needed the owner/enemy links,
573 // they don't spawn a field
582 door_spawnfield(cmins, cmaxs);
585 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
588 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
589 if two doors touch, they are assumed to be connected and operate as a unit.
591 TOGGLE causes the door to wait in both the start and end states for a trigger event.
593 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).
595 GOLD_KEY causes the door to open only if the activator holds a gold key.
597 SILVER_KEY causes the door to open only if the activator holds a silver key.
599 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
600 "angle" determines the opening direction
601 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
602 "health" if set, door must be shot open
603 "speed" movement speed (100 default)
604 "wait" wait before returning (3 default, -1 = never return)
605 "lip" lip remaining at end of move (8 default)
606 "dmg" damage to inflict when blocked (2 default)
613 FIXME: only one sound set available at the time being
617 float door_send(entity to, float sf)
619 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
620 WriteByte(MSG_ENTITY, sf);
622 if(sf & SF_TRIGGER_INIT)
624 WriteString(MSG_ENTITY, self.classname);
625 WriteByte(MSG_ENTITY, self.spawnflags);
627 WriteString(MSG_ENTITY, self.model);
629 trigger_common_write(self, true);
631 WriteCoord(MSG_ENTITY, self.pos1_x);
632 WriteCoord(MSG_ENTITY, self.pos1_y);
633 WriteCoord(MSG_ENTITY, self.pos1_z);
634 WriteCoord(MSG_ENTITY, self.pos2_x);
635 WriteCoord(MSG_ENTITY, self.pos2_y);
636 WriteCoord(MSG_ENTITY, self.pos2_z);
638 WriteCoord(MSG_ENTITY, self.size_x);
639 WriteCoord(MSG_ENTITY, self.size_y);
640 WriteCoord(MSG_ENTITY, self.size_z);
642 WriteShort(MSG_ENTITY, self.wait);
643 WriteShort(MSG_ENTITY, self.speed);
644 WriteByte(MSG_ENTITY, self.lip);
645 WriteByte(MSG_ENTITY, self.state);
646 WriteCoord(MSG_ENTITY, self.SUB_LTIME);
649 if(sf & SF_TRIGGER_RESET)
651 // client makes use of this, we do not
654 if(sf & SF_TRIGGER_UPDATE)
656 WriteCoord(MSG_ENTITY, self.origin_x);
657 WriteCoord(MSG_ENTITY, self.origin_y);
658 WriteCoord(MSG_ENTITY, self.origin_z);
660 WriteCoord(MSG_ENTITY, self.pos1_x);
661 WriteCoord(MSG_ENTITY, self.pos1_y);
662 WriteCoord(MSG_ENTITY, self.pos1_z);
663 WriteCoord(MSG_ENTITY, self.pos2_x);
664 WriteCoord(MSG_ENTITY, self.pos2_y);
665 WriteCoord(MSG_ENTITY, self.pos2_z);
673 // set size now, as everything is loaded
675 //Net_LinkEntity(self, false, 0, door_send);
679 void door_init_startopen(entity this)
681 SUB_SETORIGIN(self, self.pos2);
682 self.pos2 = self.pos1;
683 self.pos1 = self.origin;
686 self.SendFlags |= SF_TRIGGER_UPDATE;
690 void door_reset(entity this)
692 SUB_SETORIGIN(this, this.pos1);
693 this.SUB_VELOCITY = '0 0 0';
694 this.state = STATE_BOTTOM;
695 this.SUB_THINK = func_null;
696 this.SUB_NEXTTHINK = 0;
699 this.SendFlags |= SF_TRIGGER_RESET;
705 // spawnflags require key (for now only func_door)
708 // Quake 1 keys compatibility
709 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
710 this.itemkeys |= ITEM_KEY_BIT(0);
711 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
712 this.itemkeys |= ITEM_KEY_BIT(1);
716 this.max_health = this.health;
717 if (!InitMovingBrushTrigger(this))
719 this.effects |= EF_LOWPRECISION;
720 this.classname = "door";
723 this.noise = "misc/talk.wav";
724 if(this.noise3 == "")
725 this.noise3 = "misc/talk.wav";
726 precache_sound(this.noise);
727 precache_sound(this.noise3);
729 this.blocked = door_blocked;
732 if(this.dmg && (this.message == ""))
733 this.message = "was squished";
734 if(this.dmg && (this.message2 == ""))
735 this.message2 = "was squished by";
739 precache_sound ("plats/medplat1.wav");
740 precache_sound ("plats/medplat2.wav");
741 this.noise2 = "plats/medplat1.wav";
742 this.noise1 = "plats/medplat2.wav";
752 this.pos1 = this.SUB_ORIGIN;
753 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
755 if(this.spawnflags & DOOR_NONSOLID)
756 this.solid = SOLID_NOT;
758 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
759 // but spawn in the open position
760 if (this.spawnflags & DOOR_START_OPEN)
761 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
763 this.state = STATE_BOTTOM;
767 this.takedamage = DAMAGE_YES;
768 this.event_damage = door_damage;
774 settouch(this, door_touch);
776 // LinkDoors can't be done until all of the doors have been spawned, so
777 // the sizes can be detected properly.
778 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
780 this.reset = door_reset;
785 void door_draw(entity this)
787 Movetype_Physics_NoMatchServer(this);
789 trigger_draw_generic(this);
792 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
796 if(sf & SF_TRIGGER_INIT)
798 this.classname = strzone(ReadString());
799 this.spawnflags = ReadByte();
801 this.mdl = strzone(ReadString());
802 _setmodel(this, this.mdl);
804 trigger_common_read(this, true);
823 this.wait = ReadShort();
824 this.speed = ReadShort();
825 this.lip = ReadByte();
826 this.state = ReadByte();
827 this.SUB_LTIME = ReadCoord();
829 this.solid = SOLID_BSP;
830 this.movetype = MOVETYPE_PUSH;
831 this.trigger_touch = door_touch;
832 this.draw = door_draw;
833 this.drawmask = MASK_NORMAL;
838 if(this.spawnflags & DOOR_START_OPEN)
839 door_init_startopen(this);
841 this.move_time = time;
842 this.move_origin = this.origin;
843 this.move_movetype = MOVETYPE_PUSH;
844 this.move_angles = this.angles;
845 this.move_blocked = door_blocked;
848 if(sf & SF_TRIGGER_RESET)
853 if(sf & SF_TRIGGER_UPDATE)
855 this.origin_x = ReadCoord();
856 this.origin_y = ReadCoord();
857 this.origin_z = ReadCoord();
858 setorigin(this, this.origin);
859 this.move_origin = this.origin;
861 this.pos1_x = ReadCoord();
862 this.pos1_y = ReadCoord();
863 this.pos1_z = ReadCoord();
864 this.pos2_x = ReadCoord();
865 this.pos2_y = ReadCoord();
866 this.pos2_z = ReadCoord();