4 Doors are similar to buttons, but can spawn a fat trigger field around them
5 to open without a touch, and they link together to form simultanious
8 Door.owner is the master door. If there is only one door, it points to itself.
9 If multiple doors, all will point to a single one.
11 Door.enemy chains from the master door through all doors linked in the chain.
17 =============================================================================
21 =============================================================================
24 void door_go_down(entity this);
25 void door_go_up(entity this);
26 void door_rotating_go_down(entity this);
27 void door_rotating_go_up(entity this, entity oth);
29 void door_blocked(entity this, entity blocker)
31 if((this.spawnflags & 8)
33 && (blocker.takedamage != DAMAGE_NO)
40 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
46 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
47 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
50 // don't change direction for dead or dying stuff
53 && (blocker.takedamage == DAMAGE_NO)
59 if (this.state == STATE_DOWN)
60 if (this.classname == "door")
65 door_rotating_go_up(this, blocker);
68 if (this.classname == "door")
73 door_rotating_go_down (this);
80 //gib dying stuff just to make sure
81 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
82 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
88 void door_hit_top(entity this)
90 if (this.noise1 != "")
91 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
92 this.state = STATE_TOP;
93 if (this.spawnflags & DOOR_TOGGLE)
94 return; // don't come down automatically
95 if (this.classname == "door")
97 setthink(this, door_go_down);
100 setthink(this, door_rotating_go_down);
102 this.nextthink = this.ltime + this.wait;
105 void door_hit_bottom(entity this)
107 if (this.noise1 != "")
108 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
109 this.state = STATE_BOTTOM;
112 void door_go_down(entity this)
114 if (this.noise2 != "")
115 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
118 this.takedamage = DAMAGE_YES;
119 this.health = this.max_health;
122 this.state = STATE_DOWN;
123 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
126 void door_go_up(entity this)
128 if (this.state == STATE_UP)
129 return; // already going up
131 if (this.state == STATE_TOP)
132 { // reset top wait time
133 this.nextthink = this.ltime + this.wait;
137 if (this.noise2 != "")
138 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
139 this.state = STATE_UP;
140 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
143 oldmessage = this.message;
145 SUB_UseTargets(this, NULL, NULL);
146 this.message = oldmessage;
151 =============================================================================
155 =============================================================================
158 bool door_check_keys(entity door, entity player)
167 // this door require a key
168 // only a player can have a key
169 if(!IS_PLAYER(player))
172 entity store = player;
176 int valid = (door.itemkeys & store.itemkeys);
177 door.itemkeys &= ~valid; // only some of the needed keys were given
182 play2(player, SND(TALK));
183 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
191 if(player.key_door_messagetime <= time)
193 play2(player, door.noise3);
194 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
195 player.key_door_messagetime = time + 2;
201 // door needs keys the player doesn't have
203 if(player.key_door_messagetime <= time)
205 play2(player, door.noise3);
206 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
207 player.key_door_messagetime = time + 2;
214 void door_fire(entity this, entity actor, entity trigger)
216 if (this.owner != this)
217 objerror (this, "door_fire: this.owner != this");
219 if (this.spawnflags & DOOR_TOGGLE)
221 if (this.state == STATE_UP || this.state == STATE_TOP)
225 if (e.classname == "door") {
228 door_rotating_go_down(e);
231 } while ((e != this) && (e != NULL));
236 // trigger all paired doors
239 if (e.classname == "door") {
242 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
243 if ((e.spawnflags & 2) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
244 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
245 e.pos2 = '0 0 0' - e.pos2;
247 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
248 if (!((e.spawnflags & 2) && (e.spawnflags & 8) && e.state == STATE_DOWN
249 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
251 door_rotating_go_up(e, trigger);
255 } while ((e != this) && (e != NULL));
258 void door_use(entity this, entity actor, entity trigger)
260 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
263 door_fire(this.owner, actor, trigger);
266 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
268 if(this.spawnflags & DOOR_NOSPLASH)
269 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
271 this.health = this.health - damage;
275 // don't allow opening doors through damage if keys are required
279 if (this.health <= 0)
281 this.owner.health = this.owner.max_health;
282 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
283 door_use(this.owner, NULL, NULL);
287 .float door_finished;
297 void door_touch(entity this, entity toucher)
299 if (!IS_PLAYER(toucher))
301 if (this.owner.door_finished > time)
304 this.owner.door_finished = time + 2;
307 if (!(this.owner.dmg) && (this.owner.message != ""))
309 if (IS_CLIENT(toucher))
310 centerprint(toucher, this.owner.message);
311 play2(toucher, this.owner.noise);
316 void door_generic_plat_blocked(entity this, entity blocker)
318 if((this.spawnflags & 8) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
320 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
327 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
328 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
331 //Dont chamge direction for dead or dying stuff
332 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
336 if (this.state == STATE_DOWN)
337 door_rotating_go_up (this, blocker);
339 door_rotating_go_down (this);
345 //gib dying stuff just to make sure
346 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
347 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
353 void door_rotating_hit_top(entity this)
355 if (this.noise1 != "")
356 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
357 this.state = STATE_TOP;
358 if (this.spawnflags & DOOR_TOGGLE)
359 return; // don't come down automatically
360 setthink(this, door_rotating_go_down);
361 this.nextthink = this.ltime + this.wait;
364 void door_rotating_hit_bottom(entity this)
366 if (this.noise1 != "")
367 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
368 if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
370 this.pos2 = '0 0 0' - this.pos2;
373 this.state = STATE_BOTTOM;
376 void door_rotating_go_down(entity this)
378 if (this.noise2 != "")
379 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
382 this.takedamage = DAMAGE_YES;
383 this.health = this.max_health;
386 this.state = STATE_DOWN;
387 SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
390 void door_rotating_go_up(entity this, entity oth)
392 if (this.state == STATE_UP)
393 return; // already going up
395 if (this.state == STATE_TOP)
396 { // reset top wait time
397 this.nextthink = this.ltime + this.wait;
400 if (this.noise2 != "")
401 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
402 this.state = STATE_UP;
403 SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
406 oldmessage = this.message;
408 SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here?
409 this.message = oldmessage;
414 =========================================
417 Spawned if a door lacks a real activator
418 =========================================
421 void door_trigger_touch(entity this, entity toucher)
423 if (toucher.health < 1)
425 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
427 if(!((IS_CLIENT(toucher) || toucher.classname == "ENT_CLIENT_PROJECTILE") && !IS_DEAD(toucher)))
431 if (time < this.door_finished)
434 // check if door is locked
435 if (!door_check_keys(this, toucher))
438 this.door_finished = time + 1;
440 door_use(this.owner, toucher, NULL);
443 void door_spawnfield(entity this, vector fmins, vector fmaxs)
446 vector t1 = fmins, t2 = fmaxs;
448 trigger = new(doortriggerfield);
449 set_movetype(trigger, MOVETYPE_NONE);
450 trigger.solid = SOLID_TRIGGER;
451 trigger.owner = this;
453 settouch(trigger, door_trigger_touch);
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, pass.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 (this.spawnflags & 4)
505 this.owner = this.enemy = this;
514 door_spawnfield(this, this.absmin, this.absmax);
516 return; // don't want to link this door
519 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
521 // set owner, and make a loop of the chain
522 LOG_TRACE("LinkDoors: linking doors:");
523 for(t = this; ; t = t.enemy)
525 LOG_TRACE(" ", etos(t));
535 // collect health, targetname, message, size
538 for(t = this; ; t = t.enemy)
540 if(t.health && !this.health)
541 this.health = t.health;
542 if((t.targetname != "") && (this.targetname == ""))
543 this.targetname = t.targetname;
544 if((t.message != "") && (this.message == ""))
545 this.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 = this; t; t = t.enemy)
565 t.health = this.health;
566 t.targetname = this.targetname;
567 t.message = this.message;
572 // shootable, or triggered doors just needed the owner/enemy links,
573 // they don't spawn a field
582 door_spawnfield(this, 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 this, 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, this.classname);
625 WriteByte(MSG_ENTITY, this.spawnflags);
627 WriteString(MSG_ENTITY, this.model);
629 trigger_common_write(this, true);
631 WriteCoord(MSG_ENTITY, this.pos1_x);
632 WriteCoord(MSG_ENTITY, this.pos1_y);
633 WriteCoord(MSG_ENTITY, this.pos1_z);
634 WriteCoord(MSG_ENTITY, this.pos2_x);
635 WriteCoord(MSG_ENTITY, this.pos2_y);
636 WriteCoord(MSG_ENTITY, this.pos2_z);
638 WriteCoord(MSG_ENTITY, this.size_x);
639 WriteCoord(MSG_ENTITY, this.size_y);
640 WriteCoord(MSG_ENTITY, this.size_z);
642 WriteShort(MSG_ENTITY, this.wait);
643 WriteShort(MSG_ENTITY, this.speed);
644 WriteByte(MSG_ENTITY, this.lip);
645 WriteByte(MSG_ENTITY, this.state);
646 WriteCoord(MSG_ENTITY, this.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, this.origin_x);
657 WriteCoord(MSG_ENTITY, this.origin_y);
658 WriteCoord(MSG_ENTITY, this.origin_z);
660 WriteCoord(MSG_ENTITY, this.pos1_x);
661 WriteCoord(MSG_ENTITY, this.pos1_y);
662 WriteCoord(MSG_ENTITY, this.pos1_z);
663 WriteCoord(MSG_ENTITY, this.pos2_x);
664 WriteCoord(MSG_ENTITY, this.pos2_y);
665 WriteCoord(MSG_ENTITY, this.pos2_z);
673 // set size now, as everything is loaded
675 //Net_LinkEntity(this, false, 0, door_send);
679 void door_init_startopen(entity this)
681 setorigin(this, this.pos2);
682 this.pos2 = this.pos1;
683 this.pos1 = this.origin;
686 this.SendFlags |= SF_TRIGGER_UPDATE;
690 void door_reset(entity this)
692 setorigin(this, this.pos1);
693 this.velocity = '0 0 0';
694 this.state = STATE_BOTTOM;
695 setthink(this, func_null);
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 setblocked(this, 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 this.noise2 = "plats/medplat1.wav";
740 this.noise1 = "plats/medplat2.wav";
743 if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
744 if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
753 this.pos1 = this.origin;
754 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
756 if(this.spawnflags & DOOR_NONSOLID)
757 this.solid = SOLID_NOT;
759 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
760 // but spawn in the open position
761 if (this.spawnflags & DOOR_START_OPEN)
762 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
764 this.state = STATE_BOTTOM;
768 this.takedamage = DAMAGE_YES;
769 this.event_damage = door_damage;
775 settouch(this, door_touch);
777 // LinkDoors can't be done until all of the doors have been spawned, so
778 // the sizes can be detected properly.
779 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
781 this.reset = door_reset;
786 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
790 if(sf & SF_TRIGGER_INIT)
792 this.classname = strzone(ReadString());
793 this.spawnflags = ReadByte();
795 this.mdl = strzone(ReadString());
796 _setmodel(this, this.mdl);
798 trigger_common_read(this, true);
817 this.wait = ReadShort();
818 this.speed = ReadShort();
819 this.lip = ReadByte();
820 this.state = ReadByte();
821 this.ltime = ReadCoord();
823 this.solid = SOLID_BSP;
824 set_movetype(this, MOVETYPE_PUSH);
829 if(this.spawnflags & DOOR_START_OPEN)
830 door_init_startopen(this);
832 this.move_time = time;
833 set_movetype(this, MOVETYPE_PUSH);
836 if(sf & SF_TRIGGER_RESET)
841 if(sf & SF_TRIGGER_UPDATE)
843 this.origin_x = ReadCoord();
844 this.origin_y = ReadCoord();
845 this.origin_z = ReadCoord();
846 setorigin(this, this.origin);
848 this.pos1_x = ReadCoord();
849 this.pos1_y = ReadCoord();
850 this.pos1_z = ReadCoord();
851 this.pos2_x = ReadCoord();
852 this.pos2_y = ReadCoord();
853 this.pos2_z = ReadCoord();