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');
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 (time < this.door_finished)
375 // check if door is locked
376 if (!door_check_keys(this, toucher))
379 this.door_finished = time + 1;
381 door_use(this.owner, toucher, NULL);
384 void door_spawnfield(entity this, vector fmins, vector fmaxs)
387 vector t1 = fmins, t2 = fmaxs;
389 trigger = new(doortriggerfield);
390 set_movetype(trigger, MOVETYPE_NONE);
391 trigger.solid = SOLID_TRIGGER;
392 trigger.owner = this;
394 settouch(trigger, door_trigger_touch);
397 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
409 entity LinkDoors_nextent(entity cur, entity near, entity pass)
411 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
417 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
420 if((e1.absmin_x > e2.absmax_x + DELTA)
421 || (e1.absmin_y > e2.absmax_y + DELTA)
422 || (e1.absmin_z > e2.absmax_z + DELTA)
423 || (e2.absmin_x > e1.absmax_x + DELTA)
424 || (e2.absmin_y > e1.absmax_y + DELTA)
425 || (e2.absmin_z > e1.absmax_z + DELTA)
433 void LinkDoors(entity this)
443 return; // already linked by another door
444 if (this.spawnflags & DOOR_DONT_LINK)
446 this.owner = this.enemy = this;
448 if (GetResource(this, RES_HEALTH))
450 if(this.targetname && this.targetname != "")
455 door_spawnfield(this, this.absmin, this.absmax);
457 return; // don't want to link this door
460 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
462 // set owner, and make a loop of the chain
463 LOG_TRACE("LinkDoors: linking doors:");
464 for(t = this; ; t = t.enemy)
466 LOG_TRACE(" ", etos(t));
476 // collect health, targetname, message, size
479 for(t = this; ; t = t.enemy)
481 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
482 SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
483 if((t.targetname != "") && (this.targetname == ""))
484 this.targetname = t.targetname;
485 if((t.message != "") && (this.message == ""))
486 this.message = t.message;
487 if (t.absmin_x < cmins_x)
488 cmins_x = t.absmin_x;
489 if (t.absmin_y < cmins_y)
490 cmins_y = t.absmin_y;
491 if (t.absmin_z < cmins_z)
492 cmins_z = t.absmin_z;
493 if (t.absmax_x > cmaxs_x)
494 cmaxs_x = t.absmax_x;
495 if (t.absmax_y > cmaxs_y)
496 cmaxs_y = t.absmax_y;
497 if (t.absmax_z > cmaxs_z)
498 cmaxs_z = t.absmax_z;
503 // distribute health, targetname, message
504 for(t = this; t; t = t.enemy)
506 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
507 t.targetname = this.targetname;
508 t.message = this.message;
513 // shootable, or triggered doors just needed the owner/enemy links,
514 // they don't spawn a field
516 if (GetResource(this, RES_HEALTH))
518 if(this.targetname && this.targetname != "")
523 door_spawnfield(this, cmins, cmaxs);
526 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
529 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
530 if two doors touch, they are assumed to be connected and operate as a unit.
532 TOGGLE causes the door to wait in both the start and end states for a trigger event.
534 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).
536 GOLD_KEY causes the door to open only if the activator holds a gold key.
538 SILVER_KEY causes the door to open only if the activator holds a silver key.
540 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
541 "angle" determines the opening direction
542 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
543 "health" if set, door must be shot open
544 "speed" movement speed (100 default)
545 "wait" wait before returning (3 default, -1 = never return)
546 "lip" lip remaining at end of move (8 default)
547 "dmg" damage to inflict when blocked (2 default)
554 FIXME: only one sound set available at the time being
558 float door_send(entity this, entity to, float sf)
560 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
561 WriteByte(MSG_ENTITY, sf);
563 if(sf & SF_TRIGGER_INIT)
565 WriteString(MSG_ENTITY, this.classname);
566 WriteByte(MSG_ENTITY, this.spawnflags);
568 WriteString(MSG_ENTITY, this.model);
570 trigger_common_write(this, true);
572 WriteVector(MSG_ENTITY, this.pos1);
573 WriteVector(MSG_ENTITY, this.pos2);
575 WriteVector(MSG_ENTITY, this.size);
577 WriteShort(MSG_ENTITY, this.wait);
578 WriteShort(MSG_ENTITY, this.speed);
579 WriteByte(MSG_ENTITY, this.lip);
580 WriteByte(MSG_ENTITY, this.state);
581 WriteCoord(MSG_ENTITY, this.ltime);
584 if(sf & SF_TRIGGER_RESET)
586 // client makes use of this, we do not
589 if(sf & SF_TRIGGER_UPDATE)
591 WriteVector(MSG_ENTITY, this.origin);
593 WriteVector(MSG_ENTITY, this.pos1);
594 WriteVector(MSG_ENTITY, this.pos2);
602 //Net_LinkEntity(this, false, 0, door_send);
606 void door_init_startopen(entity this)
608 setorigin(this, this.pos2);
609 this.pos2 = this.pos1;
610 this.pos1 = this.origin;
613 this.SendFlags |= SF_TRIGGER_UPDATE;
617 void door_reset(entity this)
619 setorigin(this, this.pos1);
620 this.velocity = '0 0 0';
621 this.state = STATE_BOTTOM;
622 setthink(this, func_null);
626 this.SendFlags |= SF_TRIGGER_RESET;
632 // common code for func_door and func_door_rotating spawnfuncs
633 void door_init_shared(entity this)
635 this.max_health = GetResource(this, RES_HEALTH);
640 this.noise = "misc/talk.wav";
642 // door still locked sound
643 if(this.noise3 == "")
645 this.noise3 = "misc/talk.wav";
647 precache_sound(this.noise);
648 precache_sound(this.noise3);
650 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
652 this.message = _("was squished");
654 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
656 this.message2 = _("was squished by");
659 // TODO: other soundpacks
660 if (this.sounds > 0 || q3compat)
662 // Doors in Q3 always have sounds (they're hard coded)
663 this.noise2 = "plats/medplat1.wav";
664 this.noise1 = "plats/medplat2.wav";
669 // CPMA adds these fields for overriding the engine sounds
670 string s = GetField_fullspawndata(this, "sound_start", true);
671 string e = GetField_fullspawndata(this, "sound_end", true);
674 this.noise2 = strzone(s);
676 this.noise1 = strzone(e);
679 // sound when door stops moving
680 if(this.noise1 && this.noise1 != "")
682 precache_sound(this.noise1);
684 // sound when door is moving
685 if(this.noise2 && this.noise2 != "")
687 precache_sound(this.noise2);
690 if(autocvar_sv_doors_always_open)
704 this.state = STATE_BOTTOM;
706 if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
708 //this.canteamdamage = true; // TODO
709 this.takedamage = DAMAGE_YES;
710 this.event_damage = door_damage;
719 // spawnflags require key (for now only func_door)
722 // Quake 1 keys compatibility
723 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
724 this.itemkeys |= ITEM_KEY_BIT(0);
725 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
726 this.itemkeys |= ITEM_KEY_BIT(1);
730 if (!InitMovingBrushTrigger(this))
732 this.effects |= EF_LOWPRECISION;
733 this.classname = "door";
735 setblocked(this, door_blocked);
738 if(this.spawnflags & DOOR_NONSOLID)
739 this.solid = SOLID_NOT;
741 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
742 // but spawn in the open position
743 if (this.spawnflags & DOOR_START_OPEN)
744 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
746 door_init_shared(this);
748 this.pos1 = this.origin;
749 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
751 if(autocvar_sv_doors_always_open)
753 this.speed = max(750, this.speed);
755 else if (!this.speed)
763 settouch(this, door_touch);
765 // LinkDoors can't be done until all of the doors have been spawned, so
766 // the sizes can be detected properly.
767 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
769 this.reset = door_reset;
774 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
778 if(sf & SF_TRIGGER_INIT)
780 this.classname = strzone(ReadString());
781 this.spawnflags = ReadByte();
783 this.mdl = strzone(ReadString());
784 _setmodel(this, this.mdl);
786 trigger_common_read(this, true);
788 this.pos1 = ReadVector();
789 this.pos2 = ReadVector();
791 this.size = ReadVector();
793 this.wait = ReadShort();
794 this.speed = ReadShort();
795 this.lip = ReadByte();
796 this.state = ReadByte();
797 this.ltime = ReadCoord();
799 this.solid = SOLID_BSP;
800 set_movetype(this, MOVETYPE_PUSH);
805 if(this.spawnflags & DOOR_START_OPEN)
806 door_init_startopen(this);
808 this.move_time = time;
809 set_movetype(this, MOVETYPE_PUSH);
812 if(sf & SF_TRIGGER_RESET)
817 if(sf & SF_TRIGGER_UPDATE)
819 this.origin = ReadVector();
820 setorigin(this, this.origin);
822 this.pos1 = ReadVector();
823 this.pos2 = ReadVector();