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 =============================================================================
23 void door_go_down(entity this);
24 void door_go_up(entity this);
25 void door_rotating_go_down(entity this);
26 void door_rotating_go_up(entity this, entity oth);
28 void door_blocked(entity this, entity blocker)
30 if((this.spawnflags & 8)
32 && (blocker.takedamage != DAMAGE_NO)
39 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
45 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
46 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
49 // don't change direction for dead or dying stuff
52 && (blocker.takedamage == DAMAGE_NO)
58 if (this.state == STATE_DOWN)
59 if (this.classname == "door")
64 door_rotating_go_up(this, blocker);
67 if (this.classname == "door")
72 door_rotating_go_down (this);
79 //gib dying stuff just to make sure
80 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
81 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
87 void door_hit_top(entity this)
89 if (this.noise1 != "")
90 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
91 this.state = STATE_TOP;
92 if (this.spawnflags & DOOR_TOGGLE)
93 return; // don't come down automatically
94 if (this.classname == "door")
96 SUB_THINK(this, door_go_down);
99 SUB_THINK(this, door_rotating_go_down);
101 this.SUB_NEXTTHINK = this.SUB_LTIME + this.wait;
104 void door_hit_bottom(entity this)
106 if (this.noise1 != "")
107 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
108 this.state = STATE_BOTTOM;
111 void door_go_down(entity this)
113 if (this.noise2 != "")
114 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
117 this.takedamage = DAMAGE_YES;
118 this.health = this.max_health;
121 this.state = STATE_DOWN;
122 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
125 void door_go_up(entity this)
127 if (this.state == STATE_UP)
128 return; // already going up
130 if (this.state == STATE_TOP)
131 { // reset top wait time
132 this.SUB_NEXTTHINK = this.SUB_LTIME + this.wait;
136 if (this.noise2 != "")
137 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
138 this.state = STATE_UP;
139 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
142 oldmessage = this.message;
144 SUB_UseTargets(this, NULL, NULL);
145 this.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 (this, "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") {
223 door_rotating_go_down(e);
226 } while ((e != this) && (e != NULL));
231 // trigger all paired doors
234 if (e.classname == "door") {
237 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
238 if ((e.spawnflags & 2) && trigger.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) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
246 door_rotating_go_up(e, trigger);
250 } while ((e != this) && (e != NULL));
253 void door_use(entity this, entity actor, entity trigger)
255 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
258 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;
292 void door_touch(entity this, entity toucher)
294 if (!IS_PLAYER(toucher))
296 if (this.owner.door_finished > time)
299 this.owner.door_finished = time + 2;
302 if (!(this.owner.dmg) && (this.owner.message != ""))
304 if (IS_CLIENT(toucher))
305 centerprint(toucher, this.owner.message);
306 play2(toucher, this.owner.noise);
311 void door_generic_plat_blocked(entity this, entity blocker)
313 if((this.spawnflags & 8) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
315 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
322 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
323 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
326 //Dont chamge direction for dead or dying stuff
327 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
331 if (this.state == STATE_DOWN)
332 door_rotating_go_up (this, blocker);
334 door_rotating_go_down (this);
340 //gib dying stuff just to make sure
341 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
342 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, blocker.origin, '0 0 0');
348 void door_rotating_hit_top(entity this)
350 if (this.noise1 != "")
351 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
352 this.state = STATE_TOP;
353 if (this.spawnflags & DOOR_TOGGLE)
354 return; // don't come down automatically
355 SUB_THINK(this, door_rotating_go_down);
356 this.SUB_NEXTTHINK = this.SUB_LTIME + this.wait;
359 void door_rotating_hit_bottom(entity this)
361 if (this.noise1 != "")
362 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
363 if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
365 this.pos2 = '0 0 0' - this.pos2;
368 this.state = STATE_BOTTOM;
371 void door_rotating_go_down(entity this)
373 if (this.noise2 != "")
374 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
377 this.takedamage = DAMAGE_YES;
378 this.health = this.max_health;
381 this.state = STATE_DOWN;
382 SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
385 void door_rotating_go_up(entity this, entity oth)
387 if (this.state == STATE_UP)
388 return; // already going up
390 if (this.state == STATE_TOP)
391 { // reset top wait time
392 this.SUB_NEXTTHINK = this.SUB_LTIME + this.wait;
395 if (this.noise2 != "")
396 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
397 this.state = STATE_UP;
398 SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
401 oldmessage = this.message;
403 SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here?
404 this.message = oldmessage;
409 =========================================
412 Spawned if a door lacks a real activator
413 =========================================
416 void door_trigger_touch(entity this, entity toucher)
418 if (toucher.health < 1)
420 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
422 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
426 if (time < this.door_finished)
429 // check if door is locked
430 if (!door_check_keys(this, toucher))
433 this.door_finished = time + 1;
435 door_use(this.owner, toucher, NULL);
438 void door_spawnfield(entity this, vector fmins, vector fmaxs)
441 vector t1 = fmins, t2 = fmaxs;
443 trigger = new(doortriggerfield);
444 set_movetype(trigger, MOVETYPE_NONE);
445 trigger.solid = SOLID_TRIGGER;
446 trigger.owner = this;
448 settouch(trigger, door_trigger_touch);
451 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
463 entity LinkDoors_nextent(entity cur, entity near, entity pass)
465 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & 4) || cur.enemy))
471 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
474 if((e1.absmin_x > e2.absmax_x + DELTA)
475 || (e1.absmin_y > e2.absmax_y + DELTA)
476 || (e1.absmin_z > e2.absmax_z + DELTA)
477 || (e2.absmin_x > e1.absmax_x + DELTA)
478 || (e2.absmin_y > e1.absmax_y + DELTA)
479 || (e2.absmin_z > e1.absmax_z + DELTA)
487 void LinkDoors(entity this)
497 return; // already linked by another door
498 if (this.spawnflags & 4)
500 this.owner = this.enemy = this;
509 door_spawnfield(this, this.absmin, this.absmax);
511 return; // don't want to link this door
514 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
516 // set owner, and make a loop of the chain
517 LOG_TRACE("LinkDoors: linking doors:");
518 for(t = this; ; t = t.enemy)
520 LOG_TRACE(" ", etos(t));
530 // collect health, targetname, message, size
533 for(t = this; ; t = t.enemy)
535 if(t.health && !this.health)
536 this.health = t.health;
537 if((t.targetname != "") && (this.targetname == ""))
538 this.targetname = t.targetname;
539 if((t.message != "") && (this.message == ""))
540 this.message = t.message;
541 if (t.absmin_x < cmins_x)
542 cmins_x = t.absmin_x;
543 if (t.absmin_y < cmins_y)
544 cmins_y = t.absmin_y;
545 if (t.absmin_z < cmins_z)
546 cmins_z = t.absmin_z;
547 if (t.absmax_x > cmaxs_x)
548 cmaxs_x = t.absmax_x;
549 if (t.absmax_y > cmaxs_y)
550 cmaxs_y = t.absmax_y;
551 if (t.absmax_z > cmaxs_z)
552 cmaxs_z = t.absmax_z;
557 // distribute health, targetname, message
558 for(t = this; t; t = t.enemy)
560 t.health = this.health;
561 t.targetname = this.targetname;
562 t.message = this.message;
567 // shootable, or triggered doors just needed the owner/enemy links,
568 // they don't spawn a field
577 door_spawnfield(this, cmins, cmaxs);
580 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
583 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
584 if two doors touch, they are assumed to be connected and operate as a unit.
586 TOGGLE causes the door to wait in both the start and end states for a trigger event.
588 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).
590 GOLD_KEY causes the door to open only if the activator holds a gold key.
592 SILVER_KEY causes the door to open only if the activator holds a silver key.
594 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
595 "angle" determines the opening direction
596 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
597 "health" if set, door must be shot open
598 "speed" movement speed (100 default)
599 "wait" wait before returning (3 default, -1 = never return)
600 "lip" lip remaining at end of move (8 default)
601 "dmg" damage to inflict when blocked (2 default)
608 FIXME: only one sound set available at the time being
612 float door_send(entity this, entity to, float sf)
614 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
615 WriteByte(MSG_ENTITY, sf);
617 if(sf & SF_TRIGGER_INIT)
619 WriteString(MSG_ENTITY, this.classname);
620 WriteByte(MSG_ENTITY, this.spawnflags);
622 WriteString(MSG_ENTITY, this.model);
624 trigger_common_write(this, true);
626 WriteCoord(MSG_ENTITY, this.pos1_x);
627 WriteCoord(MSG_ENTITY, this.pos1_y);
628 WriteCoord(MSG_ENTITY, this.pos1_z);
629 WriteCoord(MSG_ENTITY, this.pos2_x);
630 WriteCoord(MSG_ENTITY, this.pos2_y);
631 WriteCoord(MSG_ENTITY, this.pos2_z);
633 WriteCoord(MSG_ENTITY, this.size_x);
634 WriteCoord(MSG_ENTITY, this.size_y);
635 WriteCoord(MSG_ENTITY, this.size_z);
637 WriteShort(MSG_ENTITY, this.wait);
638 WriteShort(MSG_ENTITY, this.speed);
639 WriteByte(MSG_ENTITY, this.lip);
640 WriteByte(MSG_ENTITY, this.state);
641 WriteCoord(MSG_ENTITY, this.SUB_LTIME);
644 if(sf & SF_TRIGGER_RESET)
646 // client makes use of this, we do not
649 if(sf & SF_TRIGGER_UPDATE)
651 WriteCoord(MSG_ENTITY, this.origin_x);
652 WriteCoord(MSG_ENTITY, this.origin_y);
653 WriteCoord(MSG_ENTITY, this.origin_z);
655 WriteCoord(MSG_ENTITY, this.pos1_x);
656 WriteCoord(MSG_ENTITY, this.pos1_y);
657 WriteCoord(MSG_ENTITY, this.pos1_z);
658 WriteCoord(MSG_ENTITY, this.pos2_x);
659 WriteCoord(MSG_ENTITY, this.pos2_y);
660 WriteCoord(MSG_ENTITY, this.pos2_z);
668 // set size now, as everything is loaded
670 //Net_LinkEntity(this, false, 0, door_send);
674 void door_init_startopen(entity this)
676 SUB_SETORIGIN(this, this.pos2);
677 this.pos2 = this.pos1;
678 this.pos1 = this.origin;
681 this.SendFlags |= SF_TRIGGER_UPDATE;
685 void door_reset(entity this)
687 SUB_SETORIGIN(this, this.pos1);
688 this.SUB_VELOCITY = '0 0 0';
689 this.state = STATE_BOTTOM;
690 SUB_THINK(this, func_null);
691 this.SUB_NEXTTHINK = 0;
694 this.SendFlags |= SF_TRIGGER_RESET;
700 // spawnflags require key (for now only func_door)
703 // Quake 1 keys compatibility
704 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
705 this.itemkeys |= ITEM_KEY_BIT(0);
706 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
707 this.itemkeys |= ITEM_KEY_BIT(1);
711 this.max_health = this.health;
712 if (!InitMovingBrushTrigger(this))
714 this.effects |= EF_LOWPRECISION;
715 this.classname = "door";
718 this.noise = "misc/talk.wav";
719 if(this.noise3 == "")
720 this.noise3 = "misc/talk.wav";
721 precache_sound(this.noise);
722 precache_sound(this.noise3);
724 setblocked(this, door_blocked);
727 if(this.dmg && (this.message == ""))
728 this.message = "was squished";
729 if(this.dmg && (this.message2 == ""))
730 this.message2 = "was squished by";
734 precache_sound ("plats/medplat1.wav");
735 precache_sound ("plats/medplat2.wav");
736 this.noise2 = "plats/medplat1.wav";
737 this.noise1 = "plats/medplat2.wav";
747 this.pos1 = this.SUB_ORIGIN;
748 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
750 if(this.spawnflags & DOOR_NONSOLID)
751 this.solid = SOLID_NOT;
753 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
754 // but spawn in the open position
755 if (this.spawnflags & DOOR_START_OPEN)
756 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
758 this.state = STATE_BOTTOM;
762 this.takedamage = DAMAGE_YES;
763 this.event_damage = door_damage;
769 settouch(this, door_touch);
771 // LinkDoors can't be done until all of the doors have been spawned, so
772 // the sizes can be detected properly.
773 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
775 this.reset = door_reset;
780 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
784 if(sf & SF_TRIGGER_INIT)
786 this.classname = strzone(ReadString());
787 this.spawnflags = ReadByte();
789 this.mdl = strzone(ReadString());
790 _setmodel(this, this.mdl);
792 trigger_common_read(this, true);
811 this.wait = ReadShort();
812 this.speed = ReadShort();
813 this.lip = ReadByte();
814 this.state = ReadByte();
815 this.SUB_LTIME = ReadCoord();
817 this.solid = SOLID_BSP;
818 this.move_movetype = MOVETYPE_PUSH;
823 if(this.spawnflags & DOOR_START_OPEN)
824 door_init_startopen(this);
826 this.move_time = time;
827 this.move_movetype = MOVETYPE_PUSH;
830 if(sf & SF_TRIGGER_RESET)
835 if(sf & SF_TRIGGER_UPDATE)
837 this.origin_x = ReadCoord();
838 this.origin_y = ReadCoord();
839 this.origin_z = ReadCoord();
840 setorigin(this, this.origin);
842 this.pos1_x = ReadCoord();
843 this.pos1_y = ReadCoord();
844 this.pos1_z = ReadCoord();
845 this.pos2_x = ReadCoord();
846 this.pos2_y = ReadCoord();
847 this.pos2_z = ReadCoord();