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_NO) // 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
58 if (this.state == STATE_DOWN)
60 if (this.classname == "door")
61 door_go_up(this, NULL, NULL);
63 door_rotating_go_up(this, blocker);
67 if (this.classname == "door")
70 door_rotating_go_down(this);
77 //gib dying stuff just to make sure
78 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
79 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
83 // if we didn't change direction and are using a non-linear movement controller, we must pause it
84 if (!reverse && this.classname == "door" && this.move_controller)
85 SUB_CalcMovePause(this);
88 void door_hit_top(entity this)
90 if (this.noise1 != "")
91 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
92 this.state = STATE_TOP;
93 if (this.spawnflags & DOOR_TOGGLE)
94 return; // don't come down automatically
95 if (this.classname == "door")
97 setthink(this, door_go_down);
100 setthink(this, door_rotating_go_down);
102 this.nextthink = this.ltime + this.wait;
105 void door_hit_bottom(entity this)
107 if (this.noise1 != "")
108 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
109 this.state = STATE_BOTTOM;
112 void door_go_down(entity this)
114 if (this.noise2 != "")
115 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
118 this.takedamage = DAMAGE_YES;
119 SetResourceExplicit(this, RES_HEALTH, this.max_health);
122 this.state = STATE_DOWN;
123 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
126 void door_go_up(entity this, entity actor, entity trigger)
128 if (this.state == STATE_UP)
129 return; // already going up
131 if (this.state == STATE_TOP)
132 { // reset top wait time
133 this.nextthink = this.ltime + this.wait;
137 if (this.noise2 != "")
138 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
139 this.state = STATE_UP;
140 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
143 oldmessage = this.message;
145 SUB_UseTargets(this, actor, trigger);
146 this.message = oldmessage;
151 =============================================================================
155 =============================================================================
158 bool door_check_keys(entity door, entity player)
167 // this door require a key
168 // only a player can have a key
169 if(!IS_PLAYER(player))
172 entity store = player;
176 int valid = (door.itemkeys & store.itemkeys);
177 door.itemkeys &= ~valid; // only some of the needed keys were given
182 play2(player, door.noise);
183 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
191 if(player.key_door_messagetime <= time)
193 play2(player, door.noise3);
194 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
195 player.key_door_messagetime = time + 2;
201 // door needs keys the player doesn't have
203 if(player.key_door_messagetime <= time)
205 play2(player, door.noise3);
206 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
207 player.key_door_messagetime = time + 2;
214 void door_fire(entity this, entity actor, entity trigger)
216 if (this.owner != this)
217 objerror (this, "door_fire: this.owner != this");
219 if (this.spawnflags & DOOR_TOGGLE)
221 if (this.state == STATE_UP || this.state == STATE_TOP)
225 if (e.classname == "door") {
228 door_rotating_go_down(e);
231 } while ((e != this) && (e != NULL));
236 // trigger all paired doors
239 if (e.classname == "door") {
240 door_go_up(e, actor, trigger);
242 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
243 if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
244 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
245 e.pos2 = '0 0 0' - e.pos2;
247 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
248 if (!((e.spawnflags & DOOR_ROTATING_BIDIR) && (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN
249 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
251 door_rotating_go_up(e, trigger);
255 } while ((e != this) && (e != NULL));
258 void door_use(entity this, entity actor, entity trigger)
260 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
263 door_fire(this.owner, actor, trigger);
266 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
268 if(this.spawnflags & NOSPLASH)
269 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
271 TakeResource(this, RES_HEALTH, damage);
275 // don't allow opening doors through damage if keys are required
279 if (GetResource(this, RES_HEALTH) <= 0)
281 SetResourceExplicit(this.owner, RES_HEALTH, this.owner.max_health);
282 this.owner.takedamage = DAMAGE_NO; // will be reset upon return
283 door_use(this.owner, attacker, NULL);
287 .float door_finished;
297 void door_touch(entity this, entity toucher)
299 if (!IS_PLAYER(toucher))
301 if (this.owner.door_finished > time)
304 this.owner.door_finished = time + 2;
307 if (!(this.owner.dmg) && (this.owner.message != ""))
309 if (IS_CLIENT(toucher))
310 centerprint(toucher, this.owner.message);
311 play2(toucher, this.owner.noise);
316 void door_generic_plat_blocked(entity this, entity blocker)
318 if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
320 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
327 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
328 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
331 //Dont chamge direction for dead or dying stuff
332 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
336 if (this.state == STATE_DOWN)
337 door_rotating_go_up (this, blocker);
339 door_rotating_go_down (this);
345 //gib dying stuff just to make sure
346 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
347 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
354 =========================================
357 Spawned if a door lacks a real activator
358 =========================================
361 void door_trigger_touch(entity this, entity toucher)
363 if (GetResource(toucher, RES_HEALTH) < 1)
365 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
367 if(!((IS_CLIENT(toucher) || toucher.classname == "ENT_CLIENT_PROJECTILE") && !IS_DEAD(toucher)))
371 if (this.owner.state == STATE_UP)
374 // check if door is locked
375 if (!door_check_keys(this, toucher))
378 if (this.owner.state == STATE_TOP)
380 if (this.owner.nextthink < this.owner.ltime + this.owner.wait)
382 entity e = this.owner;
384 e.nextthink = e.ltime + e.wait;
386 } while (e != this.owner);
391 door_use(this.owner, toucher, NULL);
394 void door_spawnfield(entity this, vector fmins, vector fmaxs)
397 vector t1 = fmins, t2 = fmaxs;
399 trigger = new(doortriggerfield);
400 set_movetype(trigger, MOVETYPE_NONE);
401 trigger.solid = SOLID_TRIGGER;
402 trigger.owner = this;
404 settouch(trigger, door_trigger_touch);
407 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
419 entity LinkDoors_nextent(entity cur, entity near, entity pass)
421 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
427 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
430 if((e1.absmin_x > e2.absmax_x + DELTA)
431 || (e1.absmin_y > e2.absmax_y + DELTA)
432 || (e1.absmin_z > e2.absmax_z + DELTA)
433 || (e2.absmin_x > e1.absmax_x + DELTA)
434 || (e2.absmin_y > e1.absmax_y + DELTA)
435 || (e2.absmin_z > e1.absmax_z + DELTA)
443 void LinkDoors(entity this)
453 return; // already linked by another door
454 if (this.spawnflags & DOOR_DONT_LINK)
456 this.owner = this.enemy = this;
458 if (GetResource(this, RES_HEALTH))
460 if(this.targetname && this.targetname != "")
465 door_spawnfield(this, this.absmin, this.absmax);
467 return; // don't want to link this door
470 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
472 // set owner, and make a loop of the chain
473 LOG_TRACE("LinkDoors: linking doors:");
474 for(t = this; ; t = t.enemy)
476 LOG_TRACE(" ", etos(t));
486 // collect health, targetname, message, size
489 for(t = this; ; t = t.enemy)
491 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
492 SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
493 if((t.targetname != "") && (this.targetname == ""))
494 this.targetname = t.targetname;
495 if((t.message != "") && (this.message == ""))
496 this.message = t.message;
497 if (t.absmin_x < cmins_x)
498 cmins_x = t.absmin_x;
499 if (t.absmin_y < cmins_y)
500 cmins_y = t.absmin_y;
501 if (t.absmin_z < cmins_z)
502 cmins_z = t.absmin_z;
503 if (t.absmax_x > cmaxs_x)
504 cmaxs_x = t.absmax_x;
505 if (t.absmax_y > cmaxs_y)
506 cmaxs_y = t.absmax_y;
507 if (t.absmax_z > cmaxs_z)
508 cmaxs_z = t.absmax_z;
513 // distribute health, targetname, message
514 for(t = this; t; t = t.enemy)
516 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
517 t.targetname = this.targetname;
518 t.message = this.message;
523 // shootable, or triggered doors just needed the owner/enemy links,
524 // they don't spawn a field
526 if (GetResource(this, RES_HEALTH))
528 if(this.targetname && this.targetname != "")
533 door_spawnfield(this, cmins, cmaxs);
536 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
539 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
540 if two doors touch, they are assumed to be connected and operate as a unit.
542 TOGGLE causes the door to wait in both the start and end states for a trigger event.
544 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).
546 GOLD_KEY causes the door to open only if the activator holds a gold key.
548 SILVER_KEY causes the door to open only if the activator holds a silver key.
550 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
551 "angle" determines the opening direction
552 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
553 "health" if set, door must be shot open
554 "speed" movement speed (100 default)
555 "wait" wait before returning (3 default, -1 = never return)
556 "lip" lip remaining at end of move (8 default)
557 "dmg" damage to inflict when blocked (0 default)
564 FIXME: only one sound set available at the time being
568 float door_send(entity this, entity to, float sf)
570 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
571 WriteByte(MSG_ENTITY, sf);
573 if(sf & SF_TRIGGER_INIT)
575 WriteString(MSG_ENTITY, this.classname);
576 WriteByte(MSG_ENTITY, this.spawnflags);
578 WriteString(MSG_ENTITY, this.model);
580 trigger_common_write(this, true);
582 WriteVector(MSG_ENTITY, this.pos1);
583 WriteVector(MSG_ENTITY, this.pos2);
585 WriteVector(MSG_ENTITY, this.size);
587 WriteShort(MSG_ENTITY, this.wait);
588 WriteShort(MSG_ENTITY, this.speed);
589 WriteByte(MSG_ENTITY, this.lip);
590 WriteByte(MSG_ENTITY, this.state);
591 WriteCoord(MSG_ENTITY, this.ltime);
594 if(sf & SF_TRIGGER_RESET)
596 // client makes use of this, we do not
599 if(sf & SF_TRIGGER_UPDATE)
601 WriteVector(MSG_ENTITY, this.origin);
603 WriteVector(MSG_ENTITY, this.pos1);
604 WriteVector(MSG_ENTITY, this.pos2);
612 //Net_LinkEntity(this, false, 0, door_send);
616 void door_init_startopen(entity this)
618 setorigin(this, this.pos2);
619 this.pos2 = this.pos1;
620 this.pos1 = this.origin;
623 this.SendFlags |= SF_TRIGGER_UPDATE;
627 void door_reset(entity this)
629 setorigin(this, this.pos1);
630 this.velocity = '0 0 0';
631 this.state = STATE_BOTTOM;
632 setthink(this, func_null);
636 this.SendFlags |= SF_TRIGGER_RESET;
642 // common code for func_door and func_door_rotating spawnfuncs
643 void door_init_shared(entity this)
645 this.max_health = GetResource(this, RES_HEALTH);
650 this.noise = "misc/talk.wav";
652 // door still locked sound
653 if(this.noise3 == "")
655 this.noise3 = "misc/talk.wav";
657 precache_sound(this.noise);
658 precache_sound(this.noise3);
660 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
662 this.message = "was squished";
664 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
666 this.message2 = "was squished by";
669 // TODO: other soundpacks
670 if (this.sounds > 0 || q3compat)
672 // Doors in Q3 always have sounds (they're hard coded)
673 this.noise2 = "plats/medplat1.wav";
674 this.noise1 = "plats/medplat2.wav";
679 // CPMA adds these fields for overriding the engine sounds
680 string s = GetField_fullspawndata(this, "sound_start", true);
681 string e = GetField_fullspawndata(this, "sound_end", true);
684 this.noise2 = strzone(s);
686 this.noise1 = strzone(e);
689 // sound when door stops moving
690 if(this.noise1 && this.noise1 != "")
692 precache_sound(this.noise1);
694 // sound when door is moving
695 if(this.noise2 && this.noise2 != "")
697 precache_sound(this.noise2);
700 if(autocvar_sv_doors_always_open)
706 this.wait = q3compat ? 2 : 3;
714 this.state = STATE_BOTTOM;
716 if (GetResource(this, RES_HEALTH) || (q3compat && this.targetname == ""))
718 //this.canteamdamage = true; // TODO
719 this.takedamage = DAMAGE_YES;
720 this.event_damage = door_damage;
729 // spawnflags require key (for now only func_door)
732 // Quake 1 keys compatibility
733 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
734 this.itemkeys |= ITEM_KEY_BIT(0);
735 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
736 this.itemkeys |= ITEM_KEY_BIT(1);
740 if (!InitMovingBrushTrigger(this))
742 this.effects |= EF_LOWPRECISION;
743 this.classname = "door";
745 setblocked(this, door_blocked);
748 if(this.spawnflags & DOOR_NONSOLID)
749 this.solid = SOLID_NOT;
751 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
752 // but spawn in the open position
753 // the tuba door on xoylent requires the delayed init
754 if (this.spawnflags & DOOR_START_OPEN)
755 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
757 door_init_shared(this);
759 this.pos1 = this.origin;
761 absmovedir.x = fabs(this.movedir.x);
762 absmovedir.y = fabs(this.movedir.y);
763 absmovedir.z = fabs(this.movedir.z);
764 this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip);
766 if(autocvar_sv_doors_always_open)
768 this.speed = max(750, this.speed);
770 else if (!this.speed)
778 if (q3compat && !this.dmg)
781 settouch(this, door_touch);
783 // LinkDoors can't be done until all of the doors have been spawned, so
784 // the sizes can be detected properly.
785 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
787 this.reset = door_reset;
792 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
796 if(sf & SF_TRIGGER_INIT)
798 this.classname = strzone(ReadString());
799 this.spawnflags = ReadByte();
801 this.mdl = strzone(ReadString());
802 _setmodel(this, this.mdl);
804 trigger_common_read(this, true);
806 this.pos1 = ReadVector();
807 this.pos2 = ReadVector();
809 this.size = ReadVector();
811 this.wait = ReadShort();
812 this.speed = ReadShort();
813 this.lip = ReadByte();
814 this.state = ReadByte();
815 this.ltime = ReadCoord();
817 this.solid = SOLID_BSP;
818 set_movetype(this, MOVETYPE_PUSH);
823 if(this.spawnflags & DOOR_START_OPEN)
824 door_init_startopen(this);
826 this.move_time = time;
827 set_movetype(this, MOVETYPE_PUSH);
830 if(sf & SF_TRIGGER_RESET)
835 if(sf & SF_TRIGGER_UPDATE)
837 this.origin = ReadVector();
838 setorigin(this, this.origin);
840 this.pos1 = ReadVector();
841 this.pos2 = ReadVector();