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 int valid = (door.itemkeys & player.itemkeys);
173 door.itemkeys &= ~valid; // only some of the needed keys were given
178 play2(player, SND(TALK));
179 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
187 if(player.key_door_messagetime <= time)
189 play2(player, door.noise3);
190 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
191 player.key_door_messagetime = time + 2;
197 // door needs keys the player doesn't have
199 if(player.key_door_messagetime <= time)
201 play2(player, door.noise3);
202 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
203 player.key_door_messagetime = time + 2;
210 void door_fire(entity this, entity actor, entity trigger)
212 if (this.owner != this)
213 objerror (this, "door_fire: this.owner != this");
215 if (this.spawnflags & DOOR_TOGGLE)
217 if (this.state == STATE_UP || this.state == STATE_TOP)
221 if (e.classname == "door") {
224 door_rotating_go_down(e);
227 } while ((e != this) && (e != NULL));
232 // trigger all paired doors
235 if (e.classname == "door") {
238 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
239 if ((e.spawnflags & 2) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
240 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
241 e.pos2 = '0 0 0' - e.pos2;
243 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
244 if (!((e.spawnflags & 2) && (e.spawnflags & 8) && e.state == STATE_DOWN
245 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
247 door_rotating_go_up(e, trigger);
251 } while ((e != this) && (e != NULL));
254 void door_use(entity this, entity actor, entity trigger)
256 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
259 door_fire(this.owner, actor, trigger);
262 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
264 if(this.spawnflags & DOOR_NOSPLASH)
265 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
267 this.health = this.health - damage;
271 // don't allow opening doors through damage if keys are required
275 if (this.health <= 0)
277 this.owner.health = this.owner.max_health;
278 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
279 door_use(this.owner, NULL, NULL);
283 .float door_finished;
293 void door_touch(entity this, entity toucher)
295 if (!IS_PLAYER(toucher))
297 if (this.owner.door_finished > time)
300 this.owner.door_finished = time + 2;
303 if (!(this.owner.dmg) && (this.owner.message != ""))
305 if (IS_CLIENT(toucher))
306 centerprint(toucher, this.owner.message);
307 play2(toucher, this.owner.noise);
312 void door_generic_plat_blocked(entity this, entity blocker)
314 if((this.spawnflags & 8) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
316 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
323 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
324 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
327 //Dont chamge direction for dead or dying stuff
328 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
332 if (this.state == STATE_DOWN)
333 door_rotating_go_up (this, blocker);
335 door_rotating_go_down (this);
341 //gib dying stuff just to make sure
342 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
343 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
349 void door_rotating_hit_top(entity this)
351 if (this.noise1 != "")
352 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
353 this.state = STATE_TOP;
354 if (this.spawnflags & DOOR_TOGGLE)
355 return; // don't come down automatically
356 setthink(this, door_rotating_go_down);
357 this.nextthink = this.ltime + this.wait;
360 void door_rotating_hit_bottom(entity this)
362 if (this.noise1 != "")
363 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
364 if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
366 this.pos2 = '0 0 0' - this.pos2;
369 this.state = STATE_BOTTOM;
372 void door_rotating_go_down(entity this)
374 if (this.noise2 != "")
375 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
378 this.takedamage = DAMAGE_YES;
379 this.health = this.max_health;
382 this.state = STATE_DOWN;
383 SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
386 void door_rotating_go_up(entity this, entity oth)
388 if (this.state == STATE_UP)
389 return; // already going up
391 if (this.state == STATE_TOP)
392 { // reset top wait time
393 this.nextthink = this.ltime + this.wait;
396 if (this.noise2 != "")
397 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
398 this.state = STATE_UP;
399 SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
402 oldmessage = this.message;
404 SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here?
405 this.message = oldmessage;
410 =========================================
413 Spawned if a door lacks a real activator
414 =========================================
417 void door_trigger_touch(entity this, entity toucher)
419 if (toucher.health < 1)
421 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
423 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
427 if (time < this.door_finished)
430 // check if door is locked
431 if (!door_check_keys(this, toucher))
434 this.door_finished = time + 1;
436 door_use(this.owner, toucher, NULL);
439 void door_spawnfield(entity this, vector fmins, vector fmaxs)
442 vector t1 = fmins, t2 = fmaxs;
444 trigger = new(doortriggerfield);
445 set_movetype(trigger, MOVETYPE_NONE);
446 trigger.solid = SOLID_TRIGGER;
447 trigger.owner = this;
449 settouch(trigger, door_trigger_touch);
452 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
464 entity LinkDoors_nextent(entity cur, entity near, entity pass)
466 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & 4) || cur.enemy))
472 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
475 if((e1.absmin_x > e2.absmax_x + DELTA)
476 || (e1.absmin_y > e2.absmax_y + DELTA)
477 || (e1.absmin_z > e2.absmax_z + DELTA)
478 || (e2.absmin_x > e1.absmax_x + DELTA)
479 || (e2.absmin_y > e1.absmax_y + DELTA)
480 || (e2.absmin_z > e1.absmax_z + DELTA)
488 void LinkDoors(entity this)
498 return; // already linked by another door
499 if (this.spawnflags & 4)
501 this.owner = this.enemy = this;
510 door_spawnfield(this, this.absmin, this.absmax);
512 return; // don't want to link this door
515 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
517 // set owner, and make a loop of the chain
518 LOG_TRACE("LinkDoors: linking doors:");
519 for(t = this; ; t = t.enemy)
521 LOG_TRACE(" ", etos(t));
531 // collect health, targetname, message, size
534 for(t = this; ; t = t.enemy)
536 if(t.health && !this.health)
537 this.health = t.health;
538 if((t.targetname != "") && (this.targetname == ""))
539 this.targetname = t.targetname;
540 if((t.message != "") && (this.message == ""))
541 this.message = t.message;
542 if (t.absmin_x < cmins_x)
543 cmins_x = t.absmin_x;
544 if (t.absmin_y < cmins_y)
545 cmins_y = t.absmin_y;
546 if (t.absmin_z < cmins_z)
547 cmins_z = t.absmin_z;
548 if (t.absmax_x > cmaxs_x)
549 cmaxs_x = t.absmax_x;
550 if (t.absmax_y > cmaxs_y)
551 cmaxs_y = t.absmax_y;
552 if (t.absmax_z > cmaxs_z)
553 cmaxs_z = t.absmax_z;
558 // distribute health, targetname, message
559 for(t = this; t; t = t.enemy)
561 t.health = this.health;
562 t.targetname = this.targetname;
563 t.message = this.message;
568 // shootable, or triggered doors just needed the owner/enemy links,
569 // they don't spawn a field
578 door_spawnfield(this, cmins, cmaxs);
581 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
584 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
585 if two doors touch, they are assumed to be connected and operate as a unit.
587 TOGGLE causes the door to wait in both the start and end states for a trigger event.
589 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).
591 GOLD_KEY causes the door to open only if the activator holds a gold key.
593 SILVER_KEY causes the door to open only if the activator holds a silver key.
595 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
596 "angle" determines the opening direction
597 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
598 "health" if set, door must be shot open
599 "speed" movement speed (100 default)
600 "wait" wait before returning (3 default, -1 = never return)
601 "lip" lip remaining at end of move (8 default)
602 "dmg" damage to inflict when blocked (2 default)
609 FIXME: only one sound set available at the time being
613 float door_send(entity this, entity to, float sf)
615 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
616 WriteByte(MSG_ENTITY, sf);
618 if(sf & SF_TRIGGER_INIT)
620 WriteString(MSG_ENTITY, this.classname);
621 WriteByte(MSG_ENTITY, this.spawnflags);
623 WriteString(MSG_ENTITY, this.model);
625 trigger_common_write(this, true);
627 WriteCoord(MSG_ENTITY, this.pos1_x);
628 WriteCoord(MSG_ENTITY, this.pos1_y);
629 WriteCoord(MSG_ENTITY, this.pos1_z);
630 WriteCoord(MSG_ENTITY, this.pos2_x);
631 WriteCoord(MSG_ENTITY, this.pos2_y);
632 WriteCoord(MSG_ENTITY, this.pos2_z);
634 WriteCoord(MSG_ENTITY, this.size_x);
635 WriteCoord(MSG_ENTITY, this.size_y);
636 WriteCoord(MSG_ENTITY, this.size_z);
638 WriteShort(MSG_ENTITY, this.wait);
639 WriteShort(MSG_ENTITY, this.speed);
640 WriteByte(MSG_ENTITY, this.lip);
641 WriteByte(MSG_ENTITY, this.state);
642 WriteCoord(MSG_ENTITY, this.ltime);
645 if(sf & SF_TRIGGER_RESET)
647 // client makes use of this, we do not
650 if(sf & SF_TRIGGER_UPDATE)
652 WriteCoord(MSG_ENTITY, this.origin_x);
653 WriteCoord(MSG_ENTITY, this.origin_y);
654 WriteCoord(MSG_ENTITY, this.origin_z);
656 WriteCoord(MSG_ENTITY, this.pos1_x);
657 WriteCoord(MSG_ENTITY, this.pos1_y);
658 WriteCoord(MSG_ENTITY, this.pos1_z);
659 WriteCoord(MSG_ENTITY, this.pos2_x);
660 WriteCoord(MSG_ENTITY, this.pos2_y);
661 WriteCoord(MSG_ENTITY, this.pos2_z);
669 // set size now, as everything is loaded
671 //Net_LinkEntity(this, false, 0, door_send);
675 void door_init_startopen(entity this)
677 setorigin(this, this.pos2);
678 this.pos2 = this.pos1;
679 this.pos1 = this.origin;
682 this.SendFlags |= SF_TRIGGER_UPDATE;
686 void door_reset(entity this)
688 setorigin(this, this.pos1);
689 this.velocity = '0 0 0';
690 this.state = STATE_BOTTOM;
691 setthink(this, func_null);
695 this.SendFlags |= SF_TRIGGER_RESET;
701 // spawnflags require key (for now only func_door)
704 // Quake 1 keys compatibility
705 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
706 this.itemkeys |= ITEM_KEY_BIT(0);
707 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
708 this.itemkeys |= ITEM_KEY_BIT(1);
712 this.max_health = this.health;
713 if (!InitMovingBrushTrigger(this))
715 this.effects |= EF_LOWPRECISION;
716 this.classname = "door";
719 this.noise = "misc/talk.wav";
720 if(this.noise3 == "")
721 this.noise3 = "misc/talk.wav";
722 precache_sound(this.noise);
723 precache_sound(this.noise3);
725 setblocked(this, door_blocked);
728 if(this.dmg && (this.message == ""))
729 this.message = "was squished";
730 if(this.dmg && (this.message2 == ""))
731 this.message2 = "was squished by";
735 this.noise2 = "plats/medplat1.wav";
736 this.noise1 = "plats/medplat2.wav";
739 if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
740 if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
749 this.pos1 = this.origin;
750 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
752 if(this.spawnflags & DOOR_NONSOLID)
753 this.solid = SOLID_NOT;
755 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
756 // but spawn in the open position
757 if (this.spawnflags & DOOR_START_OPEN)
758 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
760 this.state = STATE_BOTTOM;
764 this.takedamage = DAMAGE_YES;
765 this.event_damage = door_damage;
771 settouch(this, door_touch);
773 // LinkDoors can't be done until all of the doors have been spawned, so
774 // the sizes can be detected properly.
775 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
777 this.reset = door_reset;
782 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
786 if(sf & SF_TRIGGER_INIT)
788 this.classname = strzone(ReadString());
789 this.spawnflags = ReadByte();
791 this.mdl = strzone(ReadString());
792 _setmodel(this, this.mdl);
794 trigger_common_read(this, true);
813 this.wait = ReadShort();
814 this.speed = ReadShort();
815 this.lip = ReadByte();
816 this.state = ReadByte();
817 this.ltime = ReadCoord();
819 this.solid = SOLID_BSP;
820 set_movetype(this, MOVETYPE_PUSH);
825 if(this.spawnflags & DOOR_START_OPEN)
826 door_init_startopen(this);
828 this.move_time = time;
829 set_movetype(this, MOVETYPE_PUSH);
832 if(sf & SF_TRIGGER_RESET)
837 if(sf & SF_TRIGGER_UPDATE)
839 this.origin_x = ReadCoord();
840 this.origin_y = ReadCoord();
841 this.origin_z = ReadCoord();
842 setorigin(this, this.origin);
844 this.pos1_x = ReadCoord();
845 this.pos1_y = ReadCoord();
846 this.pos1_z = ReadCoord();
847 this.pos2_x = ReadCoord();
848 this.pos2_y = ReadCoord();
849 this.pos2_z = ReadCoord();