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 && (((cur.spawnflags & DOOR_DONT_LINK) && !Q3COMPAT_COMMON)
426 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
429 return e1.team == e2.team;
432 if((e1.absmin_x > e2.absmax_x + DELTA)
433 || (e1.absmin_y > e2.absmax_y + DELTA)
434 || (e1.absmin_z > e2.absmax_z + DELTA)
435 || (e2.absmin_x > e1.absmax_x + DELTA)
436 || (e2.absmin_y > e1.absmax_y + DELTA)
437 || (e2.absmin_z > e1.absmax_z + DELTA)
445 void LinkDoors(entity this)
455 return; // already linked by another door
457 // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity
458 if (((this.spawnflags & DOOR_DONT_LINK) && !Q3COMPAT_COMMON)
459 || (Q3COMPAT_COMMON && !this.team))
461 this.owner = this.enemy = this;
463 if (GetResource(this, RES_HEALTH))
465 if(this.targetname && this.targetname != "")
470 door_spawnfield(this, this.absmin, this.absmax);
472 return; // don't want to link this door
475 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
477 // set owner, and make a loop of the chain
478 LOG_TRACE("LinkDoors: linking doors:");
479 for(t = this; ; t = t.enemy)
481 LOG_TRACE(" ", etos(t));
491 // collect health, targetname, message, size
494 for(t = this; ; t = t.enemy)
496 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
497 SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
498 if((t.targetname != "") && (this.targetname == ""))
499 this.targetname = t.targetname;
500 if((t.message != "") && (this.message == ""))
501 this.message = t.message;
502 if (t.absmin_x < cmins_x)
503 cmins_x = t.absmin_x;
504 if (t.absmin_y < cmins_y)
505 cmins_y = t.absmin_y;
506 if (t.absmin_z < cmins_z)
507 cmins_z = t.absmin_z;
508 if (t.absmax_x > cmaxs_x)
509 cmaxs_x = t.absmax_x;
510 if (t.absmax_y > cmaxs_y)
511 cmaxs_y = t.absmax_y;
512 if (t.absmax_z > cmaxs_z)
513 cmaxs_z = t.absmax_z;
518 // distribute health, targetname, message
519 for(t = this; t; t = t.enemy)
521 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
522 t.targetname = this.targetname;
523 t.message = this.message;
528 // shootable, or triggered doors just needed the owner/enemy links,
529 // they don't spawn a field
531 if (GetResource(this, RES_HEALTH))
533 if(this.targetname && this.targetname != "")
538 door_spawnfield(this, cmins, cmaxs);
541 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
544 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
545 if two doors touch, they are assumed to be connected and operate as a unit.
547 TOGGLE causes the door to wait in both the start and end states for a trigger event.
549 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).
551 GOLD_KEY causes the door to open only if the activator holds a gold key.
553 SILVER_KEY causes the door to open only if the activator holds a silver key.
555 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
556 "angle" determines the opening direction
557 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
558 "health" if set, door must be shot open
559 "speed" movement speed (100 default)
560 "wait" wait before returning (3 default, -1 = never return)
561 "lip" lip remaining at end of move (8 default)
562 "dmg" damage to inflict when blocked (0 default)
569 FIXME: only one sound set available at the time being
573 float door_send(entity this, entity to, float sf)
575 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
576 WriteByte(MSG_ENTITY, sf);
578 if(sf & SF_TRIGGER_INIT)
580 WriteString(MSG_ENTITY, this.classname);
581 WriteByte(MSG_ENTITY, this.spawnflags);
583 WriteString(MSG_ENTITY, this.model);
585 trigger_common_write(this, true);
587 WriteVector(MSG_ENTITY, this.pos1);
588 WriteVector(MSG_ENTITY, this.pos2);
590 WriteVector(MSG_ENTITY, this.size);
592 WriteShort(MSG_ENTITY, this.wait);
593 WriteShort(MSG_ENTITY, this.speed);
594 WriteByte(MSG_ENTITY, this.lip);
595 WriteByte(MSG_ENTITY, this.state);
596 WriteCoord(MSG_ENTITY, this.ltime);
599 if(sf & SF_TRIGGER_RESET)
601 // client makes use of this, we do not
604 if(sf & SF_TRIGGER_UPDATE)
606 WriteVector(MSG_ENTITY, this.origin);
608 WriteVector(MSG_ENTITY, this.pos1);
609 WriteVector(MSG_ENTITY, this.pos2);
617 //Net_LinkEntity(this, false, 0, door_send);
621 void door_init_startopen(entity this)
623 setorigin(this, this.pos2);
624 this.pos2 = this.pos1;
625 this.pos1 = this.origin;
627 // no longer needed: not using delayed initialisation for door_init_startopen()
630 this.SendFlags |= SF_TRIGGER_UPDATE;
635 void door_reset(entity this)
637 setorigin(this, this.pos1);
638 this.velocity = '0 0 0';
639 this.state = STATE_BOTTOM;
640 setthink(this, func_null);
644 this.SendFlags |= SF_TRIGGER_RESET;
650 // common code for func_door and func_door_rotating spawnfuncs
651 void door_init_shared(entity this)
653 this.max_health = GetResource(this, RES_HEALTH);
658 this.noise = "misc/talk.wav";
660 // door still locked sound
661 if(this.noise3 == "")
663 this.noise3 = "misc/talk.wav";
665 precache_sound(this.noise);
666 precache_sound(this.noise3);
668 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
670 this.message = "was squished";
672 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
674 this.message2 = "was squished by";
677 // TODO: other soundpacks
678 if (this.sounds > 0 || q3compat)
680 // Doors in Q3 always have sounds (they're hard coded)
681 this.noise2 = "plats/medplat1.wav";
682 this.noise1 = "plats/medplat2.wav";
687 // CPMA adds these fields for overriding the Q3 default sounds
688 string s = GetField_fullspawndata(this, "sound_start", true);
689 string e = GetField_fullspawndata(this, "sound_end", true);
692 this.noise2 = strzone(s);
695 // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths
696 s = "sound/movers/doors/dr1_strt.wav";
697 if (FindFileInMapPack(s))
702 this.noise1 = strzone(e);
705 e = "sound/movers/doors/dr1_end.wav";
706 if (FindFileInMapPack(e))
711 // sound when door stops moving
712 if(this.noise1 && this.noise1 != "")
714 precache_sound(this.noise1);
716 // sound when door is moving
717 if(this.noise2 && this.noise2 != "")
719 precache_sound(this.noise2);
722 if(autocvar_sv_doors_always_open)
728 this.wait = q3compat ? 2 : 3;
736 this.state = STATE_BOTTOM;
738 if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
740 //this.canteamdamage = true; // TODO
741 this.takedamage = DAMAGE_YES;
742 this.event_damage = door_damage;
751 // spawnflags require key (for now only func_door)
754 // Quake 1 keys compatibility
755 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
756 this.itemkeys |= ITEM_KEY_BIT(0);
757 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
758 this.itemkeys |= ITEM_KEY_BIT(1);
762 if (!InitMovingBrushTrigger(this))
764 this.effects |= EF_LOWPRECISION;
765 this.classname = "door";
767 setblocked(this, door_blocked);
770 if(this.spawnflags & DOOR_NONSOLID)
771 this.solid = SOLID_NOT;
773 door_init_shared(this);
775 this.pos1 = this.origin;
777 absmovedir.x = fabs(this.movedir.x);
778 absmovedir.y = fabs(this.movedir.y);
779 absmovedir.z = fabs(this.movedir.z);
780 this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
782 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
783 // but spawn in the open position
784 if (this.spawnflags & DOOR_START_OPEN)
785 door_init_startopen(this);
787 if(autocvar_sv_doors_always_open)
789 this.speed = max(750, this.speed);
791 else if (!this.speed)
806 string t = GetField_fullspawndata(this, "team");
807 // bones_was_here: same hack as used to support teamed items on Q3 maps
808 if(t) this.team = crc16(false, t);
812 settouch(this, door_touch);
814 // LinkDoors can't be done until all of the doors have been spawned, so
815 // the sizes can be detected properly.
816 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
818 this.reset = door_reset;
823 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
827 if(sf & SF_TRIGGER_INIT)
829 this.classname = strzone(ReadString());
830 this.spawnflags = ReadByte();
832 this.mdl = strzone(ReadString());
833 _setmodel(this, this.mdl);
835 trigger_common_read(this, true);
837 this.pos1 = ReadVector();
838 this.pos2 = ReadVector();
840 this.size = ReadVector();
842 this.wait = ReadShort();
843 this.speed = ReadShort();
844 this.lip = ReadByte();
845 this.state = ReadByte();
846 this.ltime = ReadCoord();
848 this.solid = SOLID_BSP;
849 set_movetype(this, MOVETYPE_PUSH);
854 if(this.spawnflags & DOOR_START_OPEN)
855 door_init_startopen(this);
857 this.move_time = time;
858 set_movetype(this, MOVETYPE_PUSH);
861 if(sf & SF_TRIGGER_RESET)
866 if(sf & SF_TRIGGER_UPDATE)
868 this.origin = ReadVector();
869 setorigin(this, this.origin);
871 this.pos1 = ReadVector();
872 this.pos2 = ReadVector();