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)
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);
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, DMG_NOWEP, blocker.origin, '0 0 0');
85 if (!reverse && this.classname == "door")
86 SUB_CalcMovePause(this);
89 void door_hit_top(entity this)
91 if (this.noise1 != "")
92 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
93 this.state = STATE_TOP;
94 if (this.spawnflags & DOOR_TOGGLE)
95 return; // don't come down automatically
96 if (this.classname == "door")
98 setthink(this, door_go_down);
101 setthink(this, door_rotating_go_down);
103 this.nextthink = this.ltime + this.wait;
106 void door_hit_bottom(entity this)
108 if (this.noise1 != "")
109 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
110 this.state = STATE_BOTTOM;
113 void door_go_down(entity this)
115 if (this.noise2 != "")
116 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
119 this.takedamage = DAMAGE_YES;
120 SetResourceExplicit(this, RES_HEALTH, this.max_health);
123 this.state = STATE_DOWN;
124 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
127 void door_go_up(entity this, entity actor, entity trigger)
129 if (this.state == STATE_UP)
130 return; // already going up
132 if (this.state == STATE_TOP)
133 { // reset top wait time
134 this.nextthink = this.ltime + this.wait;
138 if (this.noise2 != "")
139 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
140 this.state = STATE_UP;
141 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
144 oldmessage = this.message;
146 SUB_UseTargets(this, actor, trigger);
147 this.message = oldmessage;
152 =============================================================================
156 =============================================================================
159 bool door_check_keys(entity door, entity player)
168 // this door require a key
169 // only a player can have a key
170 if(!IS_PLAYER(player))
173 entity store = player;
177 int valid = (door.itemkeys & store.itemkeys);
178 door.itemkeys &= ~valid; // only some of the needed keys were given
183 play2(player, door.noise);
184 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
192 if(player.key_door_messagetime <= time)
194 play2(player, door.noise3);
195 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
196 player.key_door_messagetime = time + 2;
202 // door needs keys the player doesn't have
204 if(player.key_door_messagetime <= time)
206 play2(player, door.noise3);
207 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
208 player.key_door_messagetime = time + 2;
215 void door_fire(entity this, entity actor, entity trigger)
217 if (this.owner != this)
218 objerror (this, "door_fire: this.owner != this");
220 if (this.spawnflags & DOOR_TOGGLE)
222 if (this.state == STATE_UP || this.state == STATE_TOP)
226 if (e.classname == "door") {
229 door_rotating_go_down(e);
232 } while ((e != this) && (e != NULL));
237 // trigger all paired doors
240 if (e.classname == "door") {
241 door_go_up(e, actor, trigger);
243 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
244 if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
245 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
246 e.pos2 = '0 0 0' - e.pos2;
248 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
249 if (!((e.spawnflags & DOOR_ROTATING_BIDIR) && (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN
250 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
252 door_rotating_go_up(e, trigger);
256 } while ((e != this) && (e != NULL));
259 void door_use(entity this, entity actor, entity trigger)
261 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
264 door_fire(this.owner, actor, trigger);
267 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
269 if(this.spawnflags & NOSPLASH)
270 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
272 TakeResource(this, RES_HEALTH, damage);
276 // don't allow opening doors through damage if keys are required
280 if (GetResource(this, RES_HEALTH) <= 0)
282 SetResourceExplicit(this.owner, RES_HEALTH, this.owner.max_health);
283 this.owner.takedamage = DAMAGE_NO; // will be reset upon return
284 door_use(this.owner, attacker, NULL);
288 .float door_finished;
298 void door_touch(entity this, entity toucher)
300 if (!IS_PLAYER(toucher))
302 if (this.owner.door_finished > time)
305 this.owner.door_finished = time + 2;
308 if (!(this.owner.dmg) && (this.owner.message != ""))
310 if (IS_CLIENT(toucher))
311 centerprint(toucher, this.owner.message);
312 play2(toucher, this.owner.noise);
317 void door_generic_plat_blocked(entity this, entity blocker)
319 if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
321 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
328 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
329 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
332 //Dont chamge direction for dead or dying stuff
333 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
337 if (this.state == STATE_DOWN)
338 door_rotating_go_up (this, blocker);
340 door_rotating_go_down (this);
346 //gib dying stuff just to make sure
347 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
348 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
355 =========================================
358 Spawned if a door lacks a real activator
359 =========================================
362 void door_trigger_touch(entity this, entity toucher)
364 if (GetResource(toucher, RES_HEALTH) < 1)
366 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
368 if(!((IS_CLIENT(toucher) || toucher.classname == "ENT_CLIENT_PROJECTILE") && !IS_DEAD(toucher)))
372 if (this.owner.state == STATE_UP)
375 // check if door is locked
376 if (!door_check_keys(this, toucher))
379 if (this.owner.state == STATE_TOP)
381 if (this.owner.nextthink < this.owner.ltime + this.owner.wait)
383 entity e = this.owner;
385 e.nextthink = e.ltime + e.wait;
387 } while (e != this.owner);
392 door_use(this.owner, toucher, NULL);
395 void door_spawnfield(entity this, vector fmins, vector fmaxs)
398 vector t1 = fmins, t2 = fmaxs;
400 trigger = new(doortriggerfield);
401 set_movetype(trigger, MOVETYPE_NONE);
402 trigger.solid = SOLID_TRIGGER;
403 trigger.owner = this;
405 settouch(trigger, door_trigger_touch);
408 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
420 entity LinkDoors_nextent(entity cur, entity near, entity pass)
422 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
428 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
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
455 if (this.spawnflags & DOOR_DONT_LINK)
457 this.owner = this.enemy = this;
459 if (GetResource(this, RES_HEALTH))
461 if(this.targetname && this.targetname != "")
466 door_spawnfield(this, this.absmin, this.absmax);
468 return; // don't want to link this door
471 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
473 // set owner, and make a loop of the chain
474 LOG_TRACE("LinkDoors: linking doors:");
475 for(t = this; ; t = t.enemy)
477 LOG_TRACE(" ", etos(t));
487 // collect health, targetname, message, size
490 for(t = this; ; t = t.enemy)
492 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
493 SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
494 if((t.targetname != "") && (this.targetname == ""))
495 this.targetname = t.targetname;
496 if((t.message != "") && (this.message == ""))
497 this.message = t.message;
498 if (t.absmin_x < cmins_x)
499 cmins_x = t.absmin_x;
500 if (t.absmin_y < cmins_y)
501 cmins_y = t.absmin_y;
502 if (t.absmin_z < cmins_z)
503 cmins_z = t.absmin_z;
504 if (t.absmax_x > cmaxs_x)
505 cmaxs_x = t.absmax_x;
506 if (t.absmax_y > cmaxs_y)
507 cmaxs_y = t.absmax_y;
508 if (t.absmax_z > cmaxs_z)
509 cmaxs_z = t.absmax_z;
514 // distribute health, targetname, message
515 for(t = this; t; t = t.enemy)
517 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
518 t.targetname = this.targetname;
519 t.message = this.message;
524 // shootable, or triggered doors just needed the owner/enemy links,
525 // they don't spawn a field
527 if (GetResource(this, RES_HEALTH))
529 if(this.targetname && this.targetname != "")
534 door_spawnfield(this, cmins, cmaxs);
537 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
540 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
541 if two doors touch, they are assumed to be connected and operate as a unit.
543 TOGGLE causes the door to wait in both the start and end states for a trigger event.
545 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).
547 GOLD_KEY causes the door to open only if the activator holds a gold key.
549 SILVER_KEY causes the door to open only if the activator holds a silver key.
551 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
552 "angle" determines the opening direction
553 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
554 "health" if set, door must be shot open
555 "speed" movement speed (100 default)
556 "wait" wait before returning (3 default, -1 = never return)
557 "lip" lip remaining at end of move (8 default)
558 "dmg" damage to inflict when blocked (2 default)
565 FIXME: only one sound set available at the time being
569 float door_send(entity this, entity to, float sf)
571 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
572 WriteByte(MSG_ENTITY, sf);
574 if(sf & SF_TRIGGER_INIT)
576 WriteString(MSG_ENTITY, this.classname);
577 WriteByte(MSG_ENTITY, this.spawnflags);
579 WriteString(MSG_ENTITY, this.model);
581 trigger_common_write(this, true);
583 WriteVector(MSG_ENTITY, this.pos1);
584 WriteVector(MSG_ENTITY, this.pos2);
586 WriteVector(MSG_ENTITY, this.size);
588 WriteShort(MSG_ENTITY, this.wait);
589 WriteShort(MSG_ENTITY, this.speed);
590 WriteByte(MSG_ENTITY, this.lip);
591 WriteByte(MSG_ENTITY, this.state);
592 WriteCoord(MSG_ENTITY, this.ltime);
595 if(sf & SF_TRIGGER_RESET)
597 // client makes use of this, we do not
600 if(sf & SF_TRIGGER_UPDATE)
602 WriteVector(MSG_ENTITY, this.origin);
604 WriteVector(MSG_ENTITY, this.pos1);
605 WriteVector(MSG_ENTITY, this.pos2);
613 //Net_LinkEntity(this, false, 0, door_send);
617 void door_init_startopen(entity this)
619 setorigin(this, this.pos2);
620 this.pos2 = this.pos1;
621 this.pos1 = this.origin;
624 this.SendFlags |= SF_TRIGGER_UPDATE;
628 void door_reset(entity this)
630 setorigin(this, this.pos1);
631 this.velocity = '0 0 0';
632 this.state = STATE_BOTTOM;
633 setthink(this, func_null);
637 this.SendFlags |= SF_TRIGGER_RESET;
643 // common code for func_door and func_door_rotating spawnfuncs
644 void door_init_shared(entity this)
646 this.max_health = GetResource(this, RES_HEALTH);
651 this.noise = "misc/talk.wav";
653 // door still locked sound
654 if(this.noise3 == "")
656 this.noise3 = "misc/talk.wav";
658 precache_sound(this.noise);
659 precache_sound(this.noise3);
661 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
663 this.message = "was squished";
665 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
667 this.message2 = "was squished by";
670 // TODO: other soundpacks
671 if (this.sounds > 0 || q3compat)
673 // Doors in Q3 always have sounds (they're hard coded)
674 this.noise2 = "plats/medplat1.wav";
675 this.noise1 = "plats/medplat2.wav";
680 // CPMA adds these fields for overriding the engine sounds
681 string s = GetField_fullspawndata(this, "sound_start", true);
682 string e = GetField_fullspawndata(this, "sound_end", true);
685 this.noise2 = strzone(s);
687 this.noise1 = strzone(e);
690 // sound when door stops moving
691 if(this.noise1 && this.noise1 != "")
693 precache_sound(this.noise1);
695 // sound when door is moving
696 if(this.noise2 && this.noise2 != "")
698 precache_sound(this.noise2);
701 if(autocvar_sv_doors_always_open)
707 this.wait = q3compat ? 2 : 3;
715 this.state = STATE_BOTTOM;
717 if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
719 //this.canteamdamage = true; // TODO
720 this.takedamage = DAMAGE_YES;
721 this.event_damage = door_damage;
730 // spawnflags require key (for now only func_door)
733 // Quake 1 keys compatibility
734 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
735 this.itemkeys |= ITEM_KEY_BIT(0);
736 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
737 this.itemkeys |= ITEM_KEY_BIT(1);
741 if (!InitMovingBrushTrigger(this))
743 this.effects |= EF_LOWPRECISION;
744 this.classname = "door";
746 setblocked(this, door_blocked);
749 if(this.spawnflags & DOOR_NONSOLID)
750 this.solid = SOLID_NOT;
752 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
753 // but spawn in the open position
754 // the tuba door on xoylent requires the delayed init
755 if (this.spawnflags & DOOR_START_OPEN)
756 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
758 door_init_shared(this);
760 this.pos1 = this.origin;
762 absmovedir.x = fabs(this.movedir.x);
763 absmovedir.y = fabs(this.movedir.y);
764 absmovedir.z = fabs(this.movedir.z);
765 this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
767 if(autocvar_sv_doors_always_open)
769 this.speed = max(750, this.speed);
771 else if (!this.speed)
779 settouch(this, door_touch);
781 // LinkDoors can't be done until all of the doors have been spawned, so
782 // the sizes can be detected properly.
783 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
785 this.reset = door_reset;
790 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
794 if(sf & SF_TRIGGER_INIT)
796 this.classname = strzone(ReadString());
797 this.spawnflags = ReadByte();
799 this.mdl = strzone(ReadString());
800 _setmodel(this, this.mdl);
802 trigger_common_read(this, true);
804 this.pos1 = ReadVector();
805 this.pos2 = ReadVector();
807 this.size = ReadVector();
809 this.wait = ReadShort();
810 this.speed = ReadShort();
811 this.lip = ReadByte();
812 this.state = ReadByte();
813 this.ltime = ReadCoord();
815 this.solid = SOLID_BSP;
816 set_movetype(this, MOVETYPE_PUSH);
821 if(this.spawnflags & DOOR_START_OPEN)
822 door_init_startopen(this);
824 this.move_time = time;
825 set_movetype(this, MOVETYPE_PUSH);
828 if(sf & SF_TRIGGER_RESET)
833 if(sf & SF_TRIGGER_UPDATE)
835 this.origin = ReadVector();
836 setorigin(this, this.origin);
838 this.pos1 = ReadVector();
839 this.pos2 = ReadVector();