2 #include "door_rotating.qh"
5 Doors are similar to buttons, but can spawn a fat trigger field around them
6 to open without a touch, and they link together to form simultanious
9 Door.owner is the master door. If there is only one door, it points to itself.
10 If multiple doors, all will point to a single one.
12 Door.enemy chains from the master door through all doors linked in the chain.
18 =============================================================================
22 =============================================================================
25 void door_go_down(entity this);
26 void door_go_up(entity this, entity actor, entity trigger);
28 void door_blocked(entity this, entity blocker)
31 if((this.spawnflags & DOOR_CRUSH)
34 && (blocker.takedamage != DAMAGE_NO)
41 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
47 if (this.dmg && blocker.takedamage != DAMAGE_NO) // Shall we bite?
48 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
51 // don't change direction for dead or dying stuff
54 && blocker.takedamage != DAMAGE_NO
57 && !(Q3COMPAT_COMMON && (this.spawnflags & Q3_DOOR_CRUSHER))
60 if (this.state == STATE_DOWN)
62 if (this.classname == "door")
63 door_go_up(this, NULL, NULL);
65 door_rotating_go_up(this, blocker);
69 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 && IS_DEAD(blocker)) // Shall we bite?
81 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
85 // if we didn't change direction and are using a non-linear movement controller, we must pause it
86 if (!reverse && this.classname == "door" && this.move_controller)
87 SUB_CalcMovePause(this);
90 void door_hit_top(entity this)
92 if (this.noise1 != "")
93 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
94 this.state = STATE_TOP;
95 if (this.spawnflags & DOOR_TOGGLE)
96 return; // don't come down automatically
97 if (this.classname == "door")
99 setthink(this, door_go_down);
102 setthink(this, door_rotating_go_down);
104 this.nextthink = this.ltime + this.wait;
107 void door_hit_bottom(entity this)
109 if (this.noise1 != "")
110 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
111 this.state = STATE_BOTTOM;
114 void door_go_down(entity this)
116 if (this.noise2 != "")
117 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
120 this.takedamage = DAMAGE_YES;
121 SetResourceExplicit(this, RES_HEALTH, this.max_health);
124 this.state = STATE_DOWN;
125 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
128 void door_go_up(entity this, entity actor, entity trigger)
130 if (this.state == STATE_UP)
131 return; // already going up
133 if (this.state == STATE_TOP)
134 { // reset top wait time
135 this.nextthink = this.ltime + this.wait;
139 if (this.noise2 != "")
140 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
141 this.state = STATE_UP;
142 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
145 oldmessage = this.message;
147 SUB_UseTargets(this, actor, trigger);
148 this.message = oldmessage;
153 =============================================================================
157 =============================================================================
160 bool door_check_keys(entity door, entity player)
169 // this door require a key
170 // only a player can have a key
171 if(!IS_PLAYER(player))
174 entity store = player;
178 int valid = (door.itemkeys & store.itemkeys);
179 door.itemkeys &= ~valid; // only some of the needed keys were given
184 play2(player, door.noise);
185 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
193 if(player.key_door_messagetime <= time)
195 play2(player, door.noise3);
196 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
197 player.key_door_messagetime = time + 2;
203 // door needs keys the player doesn't have
205 if(player.key_door_messagetime <= time)
207 play2(player, door.noise3);
208 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
209 player.key_door_messagetime = time + 2;
216 void door_use(entity this, entity actor, entity trigger)
218 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
224 if (this.spawnflags & DOOR_TOGGLE)
226 if (this.state == STATE_UP || this.state == STATE_TOP)
230 if (e.classname == "door") {
233 door_rotating_go_down(e);
236 } while ((e != this) && (e != NULL));
241 // trigger all paired doors
244 if (e.classname == "door") {
245 door_go_up(e, actor, trigger);
247 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
248 if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
249 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
250 e.pos2 = '0 0 0' - e.pos2;
252 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
253 if (!((e.spawnflags & DOOR_ROTATING_BIDIR) && (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN
254 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
256 door_rotating_go_up(e, trigger);
260 } while ((e != this) && (e != NULL));
263 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
265 if(this.spawnflags & NOSPLASH)
266 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
268 TakeResource(this, RES_HEALTH, damage);
272 // don't allow opening doors through damage if keys are required
276 if (GetResource(this, RES_HEALTH) <= 0)
278 SetResourceExplicit(this.owner, RES_HEALTH, this.owner.max_health);
279 this.owner.takedamage = DAMAGE_NO; // will be reset upon return
280 door_use(this.owner, attacker, NULL);
284 .float door_finished;
294 void door_touch(entity this, entity toucher)
296 if (!IS_PLAYER(toucher))
298 if (this.owner.door_finished > time)
301 this.owner.door_finished = time + 2;
304 if (!(this.owner.dmg) && (this.owner.message != ""))
306 if (IS_CLIENT(toucher))
307 centerprint(toucher, this.owner.message);
308 play2(toucher, this.owner.noise);
313 void door_generic_plat_blocked(entity this, entity blocker)
315 if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
317 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
324 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
325 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
328 //Dont chamge direction for dead or dying stuff
329 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
333 if (this.state == STATE_DOWN)
334 door_rotating_go_up (this, blocker);
336 door_rotating_go_down (this);
342 //gib dying stuff just to make sure
343 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
344 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
351 =========================================
354 Spawned if a door lacks a real activator
355 =========================================
358 void door_trigger_touch(entity this, entity toucher)
360 if (GetResource(toucher, RES_HEALTH) < 1)
362 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
364 if(!((IS_CLIENT(toucher) || toucher.classname == "ENT_CLIENT_PROJECTILE") && !IS_DEAD(toucher)))
368 if (this.owner.state == STATE_UP)
371 // check if door is locked
372 if (!door_check_keys(this, toucher))
375 if (this.owner.state == STATE_TOP)
377 if (this.owner.nextthink < this.owner.ltime + this.owner.wait)
379 entity e = this.owner;
381 e.nextthink = e.ltime + e.wait;
383 } while (e != this.owner);
388 door_use(this.owner, toucher, NULL);
391 void door_spawnfield(entity this, vector fmins, vector fmaxs)
394 vector t1 = fmins, t2 = fmaxs;
396 trigger = new(doortriggerfield);
397 set_movetype(trigger, MOVETYPE_NONE);
398 trigger.solid = SOLID_TRIGGER;
399 trigger.owner = this;
401 settouch(trigger, door_trigger_touch);
404 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
416 entity LinkDoors_nextent(entity cur, entity near, entity pass)
418 while ((cur = find(cur, classname, pass.classname))
419 && ((!Q3COMPAT_COMMON && (cur.spawnflags & DOOR_DONT_LINK)) || cur.enemy))
425 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
428 return e1.team == e2.team;
431 if((e1.absmin_x > e2.absmax_x + DELTA)
432 || (e1.absmin_y > e2.absmax_y + DELTA)
433 || (e1.absmin_z > e2.absmax_z + DELTA)
434 || (e2.absmin_x > e1.absmax_x + DELTA)
435 || (e2.absmin_y > e1.absmax_y + DELTA)
436 || (e2.absmin_z > e1.absmax_z + DELTA)
444 void LinkDoors(entity this)
454 return; // already linked by another door
456 // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity
457 if ((!Q3COMPAT_COMMON && (this.spawnflags & DOOR_DONT_LINK)) || (Q3COMPAT_COMMON && !this.team))
459 this.owner = this.enemy = this;
461 if (GetResource(this, RES_HEALTH))
463 if(this.targetname && this.targetname != "")
468 door_spawnfield(this, this.absmin, this.absmax);
470 return; // don't want to link this door
473 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
475 // set owner, and make a loop of the chain
476 LOG_TRACE("LinkDoors: linking doors:");
477 for(t = this; ; t = t.enemy)
479 LOG_TRACE(" ", etos(t));
489 // collect health, targetname, message, size
492 for(t = this; ; t = t.enemy)
494 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
495 SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
496 if((t.targetname != "") && (this.targetname == ""))
497 this.targetname = t.targetname;
498 if((t.message != "") && (this.message == ""))
499 this.message = t.message;
500 if (t.absmin_x < cmins_x)
501 cmins_x = t.absmin_x;
502 if (t.absmin_y < cmins_y)
503 cmins_y = t.absmin_y;
504 if (t.absmin_z < cmins_z)
505 cmins_z = t.absmin_z;
506 if (t.absmax_x > cmaxs_x)
507 cmaxs_x = t.absmax_x;
508 if (t.absmax_y > cmaxs_y)
509 cmaxs_y = t.absmax_y;
510 if (t.absmax_z > cmaxs_z)
511 cmaxs_z = t.absmax_z;
516 // distribute health, targetname, message
517 for(t = this; t; t = t.enemy)
519 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
520 t.targetname = this.targetname;
521 t.message = this.message;
526 // shootable, or triggered doors just needed the owner/enemy links,
527 // they don't spawn a field
529 if (GetResource(this, RES_HEALTH))
531 if(this.targetname && this.targetname != "")
536 door_spawnfield(this, cmins, cmaxs);
539 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
542 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
543 if two doors touch, they are assumed to be connected and operate as a unit.
545 TOGGLE causes the door to wait in both the start and end states for a trigger event.
547 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).
549 GOLD_KEY causes the door to open only if the activator holds a gold key.
551 SILVER_KEY causes the door to open only if the activator holds a silver key.
553 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
554 "angle" determines the opening direction
555 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
556 "health" if set, door must be shot open
557 "speed" movement speed (100 default)
558 "wait" wait before returning (3 default, -1 = never return)
559 "lip" lip remaining at end of move (8 default)
560 "dmg" damage to inflict when blocked (0 default)
567 FIXME: only one sound set available at the time being
571 float door_send(entity this, entity to, float sf)
573 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
574 WriteByte(MSG_ENTITY, sf);
576 if(sf & SF_TRIGGER_INIT)
578 WriteString(MSG_ENTITY, this.classname);
579 WriteByte(MSG_ENTITY, this.spawnflags);
581 WriteString(MSG_ENTITY, this.model);
583 trigger_common_write(this, true);
585 WriteVector(MSG_ENTITY, this.pos1);
586 WriteVector(MSG_ENTITY, this.pos2);
588 WriteVector(MSG_ENTITY, this.size);
590 WriteShort(MSG_ENTITY, this.wait);
591 WriteShort(MSG_ENTITY, this.speed);
592 WriteByte(MSG_ENTITY, this.lip);
593 WriteByte(MSG_ENTITY, this.state);
594 WriteCoord(MSG_ENTITY, this.ltime);
597 if(sf & SF_TRIGGER_RESET)
599 // client makes use of this, we do not
602 if(sf & SF_TRIGGER_UPDATE)
604 WriteVector(MSG_ENTITY, this.origin);
606 WriteVector(MSG_ENTITY, this.pos1);
607 WriteVector(MSG_ENTITY, this.pos2);
615 //Net_LinkEntity(this, false, 0, door_send);
618 void door_init_keys(entity this)
620 // Quake 1 and QL keys compatibility
621 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
622 this.itemkeys |= BIT(0);
623 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
624 this.itemkeys |= BIT(1);
628 void door_init_startopen(entity this)
630 setorigin(this, this.pos2);
631 this.pos2 = this.pos1;
632 this.pos1 = this.origin;
635 this.SendFlags |= SF_TRIGGER_UPDATE;
639 void door_reset(entity this)
641 setorigin(this, this.pos1);
642 this.velocity = '0 0 0';
643 this.state = STATE_BOTTOM;
644 setthink(this, func_null);
648 this.SendFlags |= SF_TRIGGER_RESET;
649 door_init_keys(this);
655 // common code for func_door and func_door_rotating spawnfuncs
656 void door_init_shared(entity this)
658 this.max_health = GetResource(this, RES_HEALTH);
663 this.noise = "misc/talk.wav";
665 // door still locked sound
666 if(this.noise3 == "")
668 this.noise3 = "misc/talk.wav";
670 precache_sound(this.noise);
671 precache_sound(this.noise3);
673 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
675 this.message = "was squished";
677 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
679 this.message2 = "was squished by";
682 // TODO: other soundpacks
683 if (this.sounds > 0 || q3compat)
685 // Doors in Q3 always have sounds (they're hard coded)
686 this.noise2 = "plats/medplat1.wav";
687 this.noise1 = "plats/medplat2.wav";
692 // CPMA adds these fields for overriding the Q3 default sounds
693 string s = GetField_fullspawndata(this, "sound_start", true);
694 string e = GetField_fullspawndata(this, "sound_end", true);
696 // Quake Live adds these ones, because of course it had to be different from CPMA
697 if (!s) s = GetField_fullspawndata(this, "startsound", true);
698 if (!e) e = GetField_fullspawndata(this, "endsound", true);
701 this.noise2 = strzone(s);
704 // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
705 s = "sound/movers/doors/dr1_strt.wav";
706 if (FindFileInMapPack(s))
711 this.noise1 = strzone(e);
714 e = "sound/movers/doors/dr1_end.wav";
715 if (FindFileInMapPack(e))
720 // sound when door stops moving
721 if(this.noise1 && this.noise1 != "")
723 precache_sound(this.noise1);
725 // sound when door is moving
726 if(this.noise2 && this.noise2 != "")
728 precache_sound(this.noise2);
731 if(autocvar_sv_doors_always_open)
737 this.wait = q3compat ? 2 : 3;
745 this.state = STATE_BOTTOM;
747 if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
749 //this.canteamdamage = true; // TODO
750 this.takedamage = DAMAGE_YES;
751 this.event_damage = door_damage;
760 // spawnflags require key (for now only func_door)
763 door_init_keys(this);
767 if (!InitMovingBrushTrigger(this))
769 this.effects |= EF_LOWPRECISION;
770 this.classname = "door";
772 setblocked(this, door_blocked);
775 if(this.spawnflags & DOOR_NONSOLID)
776 this.solid = SOLID_NOT;
778 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
779 // but spawn in the open position
780 // the tuba door on xoylent requires the delayed init
781 if (this.spawnflags & DOOR_START_OPEN)
782 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
784 door_init_shared(this);
786 this.pos1 = this.origin;
788 absmovedir.x = fabs(this.movedir.x);
789 absmovedir.y = fabs(this.movedir.y);
790 absmovedir.z = fabs(this.movedir.z);
791 this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
793 if(autocvar_sv_doors_always_open)
795 this.speed = max(750, this.speed);
797 else if (!this.speed)
812 string t = GetField_fullspawndata(this, "team");
813 // bones_was_here: same hack as used to support teamed items on Q3 maps
814 if (t) this.team = crc16(false, t);
818 settouch(this, door_touch);
820 // LinkDoors can't be done until all of the doors have been spawned, so
821 // the sizes can be detected properly.
822 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
824 this.reset = door_reset;
829 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
833 if(sf & SF_TRIGGER_INIT)
835 this.classname = strzone(ReadString());
836 this.spawnflags = ReadByte();
838 this.mdl = strzone(ReadString());
839 _setmodel(this, this.mdl);
841 trigger_common_read(this, true);
843 this.pos1 = ReadVector();
844 this.pos2 = ReadVector();
846 this.size = ReadVector();
848 this.wait = ReadShort();
849 this.speed = ReadShort();
850 this.lip = ReadByte();
851 this.state = ReadByte();
852 this.ltime = ReadCoord();
854 this.solid = SOLID_BSP;
855 set_movetype(this, MOVETYPE_PUSH);
860 if(this.spawnflags & DOOR_START_OPEN)
861 door_init_startopen(this);
863 this.move_time = time;
864 set_movetype(this, MOVETYPE_PUSH);
867 if(sf & SF_TRIGGER_RESET)
872 if(sf & SF_TRIGGER_UPDATE)
874 this.origin = ReadVector();
875 setorigin(this, this.origin);
877 this.pos1 = ReadVector();
878 this.pos2 = ReadVector();