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, entity actor, entity trigger);
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, DMG_NOWEP, 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, DMG_NOWEP, 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)
61 if (this.classname == "door")
62 door_go_up(this, NULL, NULL);
64 door_rotating_go_up(this, blocker);
68 if (this.classname == "door")
71 door_rotating_go_down(this);
78 //gib dying stuff just to make sure
79 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
80 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
86 void door_hit_top(entity this)
88 if (this.noise1 != "")
89 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
90 this.state = STATE_TOP;
91 if (this.spawnflags & DOOR_TOGGLE)
92 return; // don't come down automatically
93 if (this.classname == "door")
95 setthink(this, door_go_down);
98 setthink(this, door_rotating_go_down);
100 this.nextthink = this.ltime + this.wait;
103 void door_hit_bottom(entity this)
105 if (this.noise1 != "")
106 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
107 this.state = STATE_BOTTOM;
110 void door_go_down(entity this)
112 if (this.noise2 != "")
113 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
116 this.takedamage = DAMAGE_YES;
117 this.health = this.max_health;
120 this.state = STATE_DOWN;
121 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
124 void door_go_up(entity this, entity actor, entity trigger)
126 if (this.state == STATE_UP)
127 return; // already going up
129 if (this.state == STATE_TOP)
130 { // reset top wait time
131 this.nextthink = this.ltime + this.wait;
135 if (this.noise2 != "")
136 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
137 this.state = STATE_UP;
138 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
141 oldmessage = this.message;
143 SUB_UseTargets(this, actor, trigger);
144 this.message = oldmessage;
149 =============================================================================
153 =============================================================================
156 bool door_check_keys(entity door, entity player)
165 // this door require a key
166 // only a player can have a key
167 if(!IS_PLAYER(player))
170 entity store = player;
174 int valid = (door.itemkeys & store.itemkeys);
175 door.itemkeys &= ~valid; // only some of the needed keys were given
180 play2(player, SND(TALK));
181 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
189 if(player.key_door_messagetime <= time)
191 play2(player, door.noise3);
192 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
193 player.key_door_messagetime = time + 2;
199 // door needs keys the player doesn't have
201 if(player.key_door_messagetime <= time)
203 play2(player, door.noise3);
204 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
205 player.key_door_messagetime = time + 2;
212 void door_fire(entity this, entity actor, entity trigger)
214 if (this.owner != this)
215 objerror (this, "door_fire: this.owner != this");
217 if (this.spawnflags & DOOR_TOGGLE)
219 if (this.state == STATE_UP || this.state == STATE_TOP)
223 if (e.classname == "door") {
226 door_rotating_go_down(e);
229 } while ((e != this) && (e != NULL));
234 // trigger all paired doors
237 if (e.classname == "door") {
238 door_go_up(e, actor, trigger);
240 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
241 if ((e.spawnflags & 2) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
242 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
243 e.pos2 = '0 0 0' - e.pos2;
245 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
246 if (!((e.spawnflags & 2) && (e.spawnflags & 8) && e.state == STATE_DOWN
247 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
249 door_rotating_go_up(e, trigger);
253 } while ((e != this) && (e != NULL));
256 void door_use(entity this, entity actor, entity trigger)
258 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
261 door_fire(this.owner, actor, trigger);
264 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
266 if(this.spawnflags & DOOR_NOSPLASH)
267 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
269 this.health = this.health - damage;
273 // don't allow opening doors through damage if keys are required
277 if (this.health <= 0)
279 this.owner.health = this.owner.max_health;
280 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
281 door_use(this.owner, NULL, NULL);
285 .float door_finished;
295 void door_touch(entity this, entity toucher)
297 if (!IS_PLAYER(toucher))
299 if (this.owner.door_finished > time)
302 this.owner.door_finished = time + 2;
305 if (!(this.owner.dmg) && (this.owner.message != ""))
307 if (IS_CLIENT(toucher))
308 centerprint(toucher, this.owner.message);
309 play2(toucher, this.owner.noise);
314 void door_generic_plat_blocked(entity this, entity blocker)
316 if((this.spawnflags & 8) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
318 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
325 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
326 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
329 //Dont chamge direction for dead or dying stuff
330 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
334 if (this.state == STATE_DOWN)
335 door_rotating_go_up (this, blocker);
337 door_rotating_go_down (this);
343 //gib dying stuff just to make sure
344 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
345 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
351 void door_rotating_hit_top(entity this)
353 if (this.noise1 != "")
354 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
355 this.state = STATE_TOP;
356 if (this.spawnflags & DOOR_TOGGLE)
357 return; // don't come down automatically
358 setthink(this, door_rotating_go_down);
359 this.nextthink = this.ltime + this.wait;
362 void door_rotating_hit_bottom(entity this)
364 if (this.noise1 != "")
365 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
366 if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
368 this.pos2 = '0 0 0' - this.pos2;
371 this.state = STATE_BOTTOM;
374 void door_rotating_go_down(entity this)
376 if (this.noise2 != "")
377 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
380 this.takedamage = DAMAGE_YES;
381 this.health = this.max_health;
384 this.state = STATE_DOWN;
385 SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
388 void door_rotating_go_up(entity this, entity oth)
390 if (this.state == STATE_UP)
391 return; // already going up
393 if (this.state == STATE_TOP)
394 { // reset top wait time
395 this.nextthink = this.ltime + this.wait;
398 if (this.noise2 != "")
399 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
400 this.state = STATE_UP;
401 SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
404 oldmessage = this.message;
406 SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here?
407 this.message = oldmessage;
412 =========================================
415 Spawned if a door lacks a real activator
416 =========================================
419 void door_trigger_touch(entity this, entity toucher)
421 if (toucher.health < 1)
423 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
425 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
429 if (time < this.door_finished)
432 // check if door is locked
433 if (!door_check_keys(this, toucher))
436 this.door_finished = time + 1;
438 door_use(this.owner, toucher, NULL);
441 void door_spawnfield(entity this, vector fmins, vector fmaxs)
444 vector t1 = fmins, t2 = fmaxs;
446 trigger = new(doortriggerfield);
447 set_movetype(trigger, MOVETYPE_NONE);
448 trigger.solid = SOLID_TRIGGER;
449 trigger.owner = this;
451 settouch(trigger, door_trigger_touch);
454 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
466 entity LinkDoors_nextent(entity cur, entity near, entity pass)
468 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & 4) || cur.enemy))
474 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
477 if((e1.absmin_x > e2.absmax_x + DELTA)
478 || (e1.absmin_y > e2.absmax_y + DELTA)
479 || (e1.absmin_z > e2.absmax_z + DELTA)
480 || (e2.absmin_x > e1.absmax_x + DELTA)
481 || (e2.absmin_y > e1.absmax_y + DELTA)
482 || (e2.absmin_z > e1.absmax_z + DELTA)
490 void LinkDoors(entity this)
500 return; // already linked by another door
501 if (this.spawnflags & 4)
503 this.owner = this.enemy = this;
512 door_spawnfield(this, this.absmin, this.absmax);
514 return; // don't want to link this door
517 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
519 // set owner, and make a loop of the chain
520 LOG_TRACE("LinkDoors: linking doors:");
521 for(t = this; ; t = t.enemy)
523 LOG_TRACE(" ", etos(t));
533 // collect health, targetname, message, size
536 for(t = this; ; t = t.enemy)
538 if(t.health && !this.health)
539 this.health = t.health;
540 if((t.targetname != "") && (this.targetname == ""))
541 this.targetname = t.targetname;
542 if((t.message != "") && (this.message == ""))
543 this.message = t.message;
544 if (t.absmin_x < cmins_x)
545 cmins_x = t.absmin_x;
546 if (t.absmin_y < cmins_y)
547 cmins_y = t.absmin_y;
548 if (t.absmin_z < cmins_z)
549 cmins_z = t.absmin_z;
550 if (t.absmax_x > cmaxs_x)
551 cmaxs_x = t.absmax_x;
552 if (t.absmax_y > cmaxs_y)
553 cmaxs_y = t.absmax_y;
554 if (t.absmax_z > cmaxs_z)
555 cmaxs_z = t.absmax_z;
560 // distribute health, targetname, message
561 for(t = this; t; t = t.enemy)
563 t.health = this.health;
564 t.targetname = this.targetname;
565 t.message = this.message;
570 // shootable, or triggered doors just needed the owner/enemy links,
571 // they don't spawn a field
580 door_spawnfield(this, cmins, cmaxs);
583 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
586 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
587 if two doors touch, they are assumed to be connected and operate as a unit.
589 TOGGLE causes the door to wait in both the start and end states for a trigger event.
591 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).
593 GOLD_KEY causes the door to open only if the activator holds a gold key.
595 SILVER_KEY causes the door to open only if the activator holds a silver key.
597 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
598 "angle" determines the opening direction
599 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
600 "health" if set, door must be shot open
601 "speed" movement speed (100 default)
602 "wait" wait before returning (3 default, -1 = never return)
603 "lip" lip remaining at end of move (8 default)
604 "dmg" damage to inflict when blocked (2 default)
611 FIXME: only one sound set available at the time being
615 float door_send(entity this, entity to, float sf)
617 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
618 WriteByte(MSG_ENTITY, sf);
620 if(sf & SF_TRIGGER_INIT)
622 WriteString(MSG_ENTITY, this.classname);
623 WriteByte(MSG_ENTITY, this.spawnflags);
625 WriteString(MSG_ENTITY, this.model);
627 trigger_common_write(this, true);
629 WriteVector(MSG_ENTITY, this.pos1);
630 WriteVector(MSG_ENTITY, this.pos2);
632 WriteVector(MSG_ENTITY, this.size);
634 WriteShort(MSG_ENTITY, this.wait);
635 WriteShort(MSG_ENTITY, this.speed);
636 WriteByte(MSG_ENTITY, this.lip);
637 WriteByte(MSG_ENTITY, this.state);
638 WriteCoord(MSG_ENTITY, this.ltime);
641 if(sf & SF_TRIGGER_RESET)
643 // client makes use of this, we do not
646 if(sf & SF_TRIGGER_UPDATE)
648 WriteVector(MSG_ENTITY, this.origin);
650 WriteVector(MSG_ENTITY, this.pos1);
651 WriteVector(MSG_ENTITY, this.pos2);
659 // set size now, as everything is loaded
661 //Net_LinkEntity(this, false, 0, door_send);
665 void door_init_startopen(entity this)
667 setorigin(this, this.pos2);
668 this.pos2 = this.pos1;
669 this.pos1 = this.origin;
672 this.SendFlags |= SF_TRIGGER_UPDATE;
676 void door_reset(entity this)
678 setorigin(this, this.pos1);
679 this.velocity = '0 0 0';
680 this.state = STATE_BOTTOM;
681 setthink(this, func_null);
685 this.SendFlags |= SF_TRIGGER_RESET;
691 // spawnflags require key (for now only func_door)
694 // Quake 1 keys compatibility
695 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
696 this.itemkeys |= ITEM_KEY_BIT(0);
697 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
698 this.itemkeys |= ITEM_KEY_BIT(1);
702 this.max_health = this.health;
703 if (!InitMovingBrushTrigger(this))
705 this.effects |= EF_LOWPRECISION;
706 this.classname = "door";
709 this.noise = "misc/talk.wav";
710 if(this.noise3 == "")
711 this.noise3 = "misc/talk.wav";
712 precache_sound(this.noise);
713 precache_sound(this.noise3);
715 setblocked(this, door_blocked);
718 if(this.dmg && (this.message == ""))
719 this.message = "was squished";
720 if(this.dmg && (this.message2 == ""))
721 this.message2 = "was squished by";
725 this.noise2 = "plats/medplat1.wav";
726 this.noise1 = "plats/medplat2.wav";
729 if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
730 if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
739 this.pos1 = this.origin;
740 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
742 if(this.spawnflags & DOOR_NONSOLID)
743 this.solid = SOLID_NOT;
745 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
746 // but spawn in the open position
747 if (this.spawnflags & DOOR_START_OPEN)
748 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
750 this.state = STATE_BOTTOM;
754 //this.canteamdamage = true; // TODO
755 this.takedamage = DAMAGE_YES;
756 this.event_damage = door_damage;
762 settouch(this, door_touch);
764 // LinkDoors can't be done until all of the doors have been spawned, so
765 // the sizes can be detected properly.
766 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
768 this.reset = door_reset;
773 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
777 if(sf & SF_TRIGGER_INIT)
779 this.classname = strzone(ReadString());
780 this.spawnflags = ReadByte();
782 this.mdl = strzone(ReadString());
783 _setmodel(this, this.mdl);
785 trigger_common_read(this, true);
787 this.pos1 = ReadVector();
788 this.pos2 = ReadVector();
790 this.size = ReadVector();
792 this.wait = ReadShort();
793 this.speed = ReadShort();
794 this.lip = ReadByte();
795 this.state = ReadByte();
796 this.ltime = ReadCoord();
798 this.solid = SOLID_BSP;
799 set_movetype(this, MOVETYPE_PUSH);
804 if(this.spawnflags & DOOR_START_OPEN)
805 door_init_startopen(this);
807 this.move_time = time;
808 set_movetype(this, MOVETYPE_PUSH);
811 if(sf & SF_TRIGGER_RESET)
816 if(sf & SF_TRIGGER_UPDATE)
818 this.origin = ReadVector();
819 setorigin(this, this.origin);
821 this.pos1 = ReadVector();
822 this.pos2 = ReadVector();