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)
30 if((this.spawnflags & DOOR_CRUSH)
32 && (blocker.takedamage != DAMAGE_NO)
39 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
45 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
46 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
49 // don't change direction for dead or dying stuff
52 && (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');
85 void door_hit_top(entity this)
87 if (this.noise1 != "")
88 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
89 this.state = STATE_TOP;
90 if (this.spawnflags & DOOR_TOGGLE)
91 return; // don't come down automatically
92 if (this.classname == "door")
94 setthink(this, door_go_down);
97 setthink(this, door_rotating_go_down);
99 this.nextthink = this.ltime + this.wait;
102 void door_hit_bottom(entity this)
104 if (this.noise1 != "")
105 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
106 this.state = STATE_BOTTOM;
109 void door_go_down(entity this)
111 if (this.noise2 != "")
112 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
115 this.takedamage = DAMAGE_YES;
116 SetResourceExplicit(this, RES_HEALTH, this.max_health);
119 this.state = STATE_DOWN;
120 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
123 void door_go_up(entity this, entity actor, entity trigger)
125 if (this.state == STATE_UP)
126 return; // already going up
128 if (this.state == STATE_TOP)
129 { // reset top wait time
130 this.nextthink = this.ltime + this.wait;
134 if (this.noise2 != "")
135 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
136 this.state = STATE_UP;
137 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
140 oldmessage = this.message;
142 SUB_UseTargets(this, actor, trigger);
143 this.message = oldmessage;
148 =============================================================================
152 =============================================================================
155 bool door_check_keys(entity door, entity player)
164 // this door require a key
165 // only a player can have a key
166 if(!IS_PLAYER(player))
169 entity store = player;
173 int valid = (door.itemkeys & store.itemkeys);
174 door.itemkeys &= ~valid; // only some of the needed keys were given
179 play2(player, door.noise);
180 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
188 if(player.key_door_messagetime <= time)
190 play2(player, door.noise3);
191 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
192 player.key_door_messagetime = time + 2;
198 // door needs keys the player doesn't have
200 if(player.key_door_messagetime <= time)
202 play2(player, door.noise3);
203 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
204 player.key_door_messagetime = time + 2;
211 void door_fire(entity this, entity actor, entity trigger)
213 if (this.owner != this)
214 objerror (this, "door_fire: this.owner != this");
216 if (this.spawnflags & DOOR_TOGGLE)
218 if (this.state == STATE_UP || this.state == STATE_TOP)
222 if (e.classname == "door") {
225 door_rotating_go_down(e);
228 } while ((e != this) && (e != NULL));
233 // trigger all paired doors
236 if (e.classname == "door") {
237 door_go_up(e, actor, trigger);
239 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
240 if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
241 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
242 e.pos2 = '0 0 0' - e.pos2;
244 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
245 if (!((e.spawnflags & DOOR_ROTATING_BIDIR) && (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN
246 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
248 door_rotating_go_up(e, trigger);
252 } while ((e != this) && (e != NULL));
255 void door_use(entity this, entity actor, entity trigger)
257 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
260 door_fire(this.owner, actor, trigger);
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 (time < this.door_finished)
371 // check if door is locked
372 if (!door_check_keys(this, toucher))
375 this.door_finished = time + 1;
377 door_use(this.owner, toucher, NULL);
380 void door_spawnfield(entity this, vector fmins, vector fmaxs)
383 vector t1 = fmins, t2 = fmaxs;
385 trigger = new(doortriggerfield);
386 set_movetype(trigger, MOVETYPE_NONE);
387 trigger.solid = SOLID_TRIGGER;
388 trigger.owner = this;
390 settouch(trigger, door_trigger_touch);
393 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
405 entity LinkDoors_nextent(entity cur, entity near, entity pass)
407 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
413 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
416 if((e1.absmin_x > e2.absmax_x + DELTA)
417 || (e1.absmin_y > e2.absmax_y + DELTA)
418 || (e1.absmin_z > e2.absmax_z + DELTA)
419 || (e2.absmin_x > e1.absmax_x + DELTA)
420 || (e2.absmin_y > e1.absmax_y + DELTA)
421 || (e2.absmin_z > e1.absmax_z + DELTA)
429 void LinkDoors(entity this)
439 return; // already linked by another door
440 if (this.spawnflags & DOOR_DONT_LINK)
442 this.owner = this.enemy = this;
444 if (GetResource(this, RES_HEALTH))
446 if(this.targetname && this.targetname != "")
451 door_spawnfield(this, this.absmin, this.absmax);
453 return; // don't want to link this door
456 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
458 // set owner, and make a loop of the chain
459 LOG_TRACE("LinkDoors: linking doors:");
460 for(t = this; ; t = t.enemy)
462 LOG_TRACE(" ", etos(t));
472 // collect health, targetname, message, size
475 for(t = this; ; t = t.enemy)
477 if(GetResource(t, RES_HEALTH) && !GetResource(this, RES_HEALTH))
478 SetResourceExplicit(this, RES_HEALTH, GetResource(t, RES_HEALTH));
479 if((t.targetname != "") && (this.targetname == ""))
480 this.targetname = t.targetname;
481 if((t.message != "") && (this.message == ""))
482 this.message = t.message;
483 if (t.absmin_x < cmins_x)
484 cmins_x = t.absmin_x;
485 if (t.absmin_y < cmins_y)
486 cmins_y = t.absmin_y;
487 if (t.absmin_z < cmins_z)
488 cmins_z = t.absmin_z;
489 if (t.absmax_x > cmaxs_x)
490 cmaxs_x = t.absmax_x;
491 if (t.absmax_y > cmaxs_y)
492 cmaxs_y = t.absmax_y;
493 if (t.absmax_z > cmaxs_z)
494 cmaxs_z = t.absmax_z;
499 // distribute health, targetname, message
500 for(t = this; t; t = t.enemy)
502 SetResourceExplicit(t, RES_HEALTH, GetResource(this, RES_HEALTH));
503 t.targetname = this.targetname;
504 t.message = this.message;
509 // shootable, or triggered doors just needed the owner/enemy links,
510 // they don't spawn a field
512 if (GetResource(this, RES_HEALTH))
514 if(this.targetname && this.targetname != "")
519 door_spawnfield(this, cmins, cmaxs);
522 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
525 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
526 if two doors touch, they are assumed to be connected and operate as a unit.
528 TOGGLE causes the door to wait in both the start and end states for a trigger event.
530 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).
532 GOLD_KEY causes the door to open only if the activator holds a gold key.
534 SILVER_KEY causes the door to open only if the activator holds a silver key.
536 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
537 "angle" determines the opening direction
538 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
539 "health" if set, door must be shot open
540 "speed" movement speed (100 default)
541 "wait" wait before returning (3 default, -1 = never return)
542 "lip" lip remaining at end of move (8 default)
543 "dmg" damage to inflict when blocked (2 default)
550 FIXME: only one sound set available at the time being
554 float door_send(entity this, entity to, float sf)
556 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
557 WriteByte(MSG_ENTITY, sf);
559 if(sf & SF_TRIGGER_INIT)
561 WriteString(MSG_ENTITY, this.classname);
562 WriteByte(MSG_ENTITY, this.spawnflags);
564 WriteString(MSG_ENTITY, this.model);
566 trigger_common_write(this, true);
568 WriteVector(MSG_ENTITY, this.pos1);
569 WriteVector(MSG_ENTITY, this.pos2);
571 WriteVector(MSG_ENTITY, this.size);
573 WriteShort(MSG_ENTITY, this.wait);
574 WriteShort(MSG_ENTITY, this.speed);
575 WriteByte(MSG_ENTITY, this.lip);
576 WriteByte(MSG_ENTITY, this.state);
577 WriteCoord(MSG_ENTITY, this.ltime);
580 if(sf & SF_TRIGGER_RESET)
582 // client makes use of this, we do not
585 if(sf & SF_TRIGGER_UPDATE)
587 WriteVector(MSG_ENTITY, this.origin);
589 WriteVector(MSG_ENTITY, this.pos1);
590 WriteVector(MSG_ENTITY, this.pos2);
598 //Net_LinkEntity(this, false, 0, door_send);
602 void door_init_startopen(entity this)
604 setorigin(this, this.pos2);
605 this.pos2 = this.pos1;
606 this.pos1 = this.origin;
609 this.SendFlags |= SF_TRIGGER_UPDATE;
613 void door_reset(entity this)
615 setorigin(this, this.pos1);
616 this.velocity = '0 0 0';
617 this.state = STATE_BOTTOM;
618 setthink(this, func_null);
622 this.SendFlags |= SF_TRIGGER_RESET;
628 // common code for func_door and func_door_rotating spawnfuncs
629 void door_init_shared(entity this)
631 this.max_health = GetResource(this, RES_HEALTH);
636 this.noise = "misc/talk.wav";
638 // door still locked sound
639 if(this.noise3 == "")
641 this.noise3 = "misc/talk.wav";
643 precache_sound(this.noise);
644 precache_sound(this.noise3);
646 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
648 this.message = "was squished";
650 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
652 this.message2 = "was squished by";
655 // TODO: other soundpacks
656 if (this.sounds > 0 || q3compat)
658 // Doors in Q3 always have sounds (they're hard coded in Q3 engine)
659 this.noise2 = "plats/medplat1.wav";
660 this.noise1 = "plats/medplat2.wav";
665 // CPMA adds these fields for overriding the engine sounds
666 string s = GetField_fullspawndata(this, "sound_start", true);
667 string e = GetField_fullspawndata(this, "sound_end", true);
670 this.noise2 = strzone(s);
672 this.noise1 = strzone(e);
675 // sound when door stops moving
676 if(this.noise1 && this.noise1 != "")
678 precache_sound(this.noise1);
680 // sound when door is moving
681 if(this.noise2 && this.noise2 != "")
683 precache_sound(this.noise2);
686 if(autocvar_sv_doors_always_open)
700 this.state = STATE_BOTTOM;
702 if (GetResource(this, RES_HEALTH))
704 //this.canteamdamage = true; // TODO
705 this.takedamage = DAMAGE_YES;
706 this.event_damage = door_damage;
715 // spawnflags require key (for now only func_door)
718 // Quake 1 keys compatibility
719 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
720 this.itemkeys |= ITEM_KEY_BIT(0);
721 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
722 this.itemkeys |= ITEM_KEY_BIT(1);
726 if (!InitMovingBrushTrigger(this))
728 this.effects |= EF_LOWPRECISION;
729 this.classname = "door";
731 setblocked(this, door_blocked);
734 if(this.spawnflags & DOOR_NONSOLID)
735 this.solid = SOLID_NOT;
737 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
738 // but spawn in the open position
739 if (this.spawnflags & DOOR_START_OPEN)
740 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
742 door_init_shared(this);
744 this.pos1 = this.origin;
745 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
747 if(autocvar_sv_doors_always_open)
749 this.speed = max(750, this.speed);
751 else if (!this.speed)
759 settouch(this, door_touch);
761 // LinkDoors can't be done until all of the doors have been spawned, so
762 // the sizes can be detected properly.
763 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
765 this.reset = door_reset;
770 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
774 if(sf & SF_TRIGGER_INIT)
776 this.classname = strzone(ReadString());
777 this.spawnflags = ReadByte();
779 this.mdl = strzone(ReadString());
780 _setmodel(this, this.mdl);
782 trigger_common_read(this, true);
784 this.pos1 = ReadVector();
785 this.pos2 = ReadVector();
787 this.size = ReadVector();
789 this.wait = ReadShort();
790 this.speed = ReadShort();
791 this.lip = ReadByte();
792 this.state = ReadByte();
793 this.ltime = ReadCoord();
795 this.solid = SOLID_BSP;
796 set_movetype(this, MOVETYPE_PUSH);
801 if(this.spawnflags & DOOR_START_OPEN)
802 door_init_startopen(this);
804 this.move_time = time;
805 set_movetype(this, MOVETYPE_PUSH);
808 if(sf & SF_TRIGGER_RESET)
813 if(sf & SF_TRIGGER_UPDATE)
815 this.origin = ReadVector();
816 setorigin(this, this.origin);
818 this.pos1 = ReadVector();
819 this.pos2 = ReadVector();