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);
619 void door_init_startopen(entity this)
621 setorigin(this, this.pos2);
622 this.pos2 = this.pos1;
623 this.pos1 = this.origin;
626 this.SendFlags |= SF_TRIGGER_UPDATE;
630 void door_reset(entity this)
632 setorigin(this, this.pos1);
633 this.velocity = '0 0 0';
634 this.state = STATE_BOTTOM;
635 setthink(this, func_null);
639 this.SendFlags |= SF_TRIGGER_RESET;
645 // common code for func_door and func_door_rotating spawnfuncs
646 void door_init_shared(entity this)
648 this.max_health = GetResource(this, RES_HEALTH);
653 this.noise = "misc/talk.wav";
655 // door still locked sound
656 if(this.noise3 == "")
658 this.noise3 = "misc/talk.wav";
660 precache_sound(this.noise);
661 precache_sound(this.noise3);
663 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
665 this.message = "was squished";
667 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
669 this.message2 = "was squished by";
672 // TODO: other soundpacks
673 if (this.sounds > 0 || q3compat)
675 // Doors in Q3 always have sounds (they're hard coded)
676 this.noise2 = "plats/medplat1.wav";
677 this.noise1 = "plats/medplat2.wav";
682 // CPMA adds these fields for overriding the engine sounds
683 string s = GetField_fullspawndata(this, "sound_start", true);
684 string e = GetField_fullspawndata(this, "sound_end", true);
687 this.noise2 = strzone(s);
689 this.noise1 = strzone(e);
692 // sound when door stops moving
693 if(this.noise1 && this.noise1 != "")
695 precache_sound(this.noise1);
697 // sound when door is moving
698 if(this.noise2 && this.noise2 != "")
700 precache_sound(this.noise2);
703 if(autocvar_sv_doors_always_open)
709 this.wait = q3compat ? 2 : 3;
717 this.state = STATE_BOTTOM;
719 if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
721 //this.canteamdamage = true; // TODO
722 this.takedamage = DAMAGE_YES;
723 this.event_damage = door_damage;
732 // spawnflags require key (for now only func_door)
735 // Quake 1 keys compatibility
736 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
737 this.itemkeys |= ITEM_KEY_BIT(0);
738 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
739 this.itemkeys |= ITEM_KEY_BIT(1);
743 if (!InitMovingBrushTrigger(this))
745 this.effects |= EF_LOWPRECISION;
746 this.classname = "door";
748 setblocked(this, door_blocked);
751 if(this.spawnflags & DOOR_NONSOLID)
752 this.solid = SOLID_NOT;
754 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
755 // but spawn in the open position
756 // the tuba door on xoylent requires the delayed init
757 if (this.spawnflags & DOOR_START_OPEN)
758 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
760 door_init_shared(this);
762 this.pos1 = this.origin;
764 absmovedir.x = fabs(this.movedir.x);
765 absmovedir.y = fabs(this.movedir.y);
766 absmovedir.z = fabs(this.movedir.z);
767 this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
769 if(autocvar_sv_doors_always_open)
771 this.speed = max(750, this.speed);
773 else if (!this.speed)
788 string t = GetField_fullspawndata(this, "team");
789 // bones_was_here: same hack as used to support teamed items on Q3 maps
790 if (t) this.team = crc16(false, t);
794 settouch(this, door_touch);
796 // LinkDoors can't be done until all of the doors have been spawned, so
797 // the sizes can be detected properly.
798 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
800 this.reset = door_reset;
805 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
809 if(sf & SF_TRIGGER_INIT)
811 this.classname = strzone(ReadString());
812 this.spawnflags = ReadByte();
814 this.mdl = strzone(ReadString());
815 _setmodel(this, this.mdl);
817 trigger_common_read(this, true);
819 this.pos1 = ReadVector();
820 this.pos2 = ReadVector();
822 this.size = ReadVector();
824 this.wait = ReadShort();
825 this.speed = ReadShort();
826 this.lip = ReadByte();
827 this.state = ReadByte();
828 this.ltime = ReadCoord();
830 this.solid = SOLID_BSP;
831 set_movetype(this, MOVETYPE_PUSH);
836 if(this.spawnflags & DOOR_START_OPEN)
837 door_init_startopen(this);
839 this.move_time = time;
840 set_movetype(this, MOVETYPE_PUSH);
843 if(sf & SF_TRIGGER_RESET)
848 if(sf & SF_TRIGGER_UPDATE)
850 this.origin = ReadVector();
851 setorigin(this, this.origin);
853 this.pos1 = ReadVector();
854 this.pos2 = ReadVector();