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;
117 SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
119 this.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))
273 SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH) - damage);
275 this.health -= damage;
280 // don't allow opening doors through damage if keys are required
285 if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
287 SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
288 this.owner.takedamage = DAMAGE_NO; // will be reset upon return
289 door_use(this.owner, NULL, NULL);
294 this.owner.health = this.owner.max_health;
295 this.owner.takedamage = DAMAGE_NO;
296 door_use(this.owner, NULL, NULL);
301 .float door_finished;
311 void door_touch(entity this, entity toucher)
313 if (!IS_PLAYER(toucher))
315 if (this.owner.door_finished > time)
318 this.owner.door_finished = time + 2;
321 if (!(this.owner.dmg) && (this.owner.message != ""))
323 if (IS_CLIENT(toucher))
324 centerprint(toucher, this.owner.message);
325 play2(toucher, this.owner.noise);
330 void door_generic_plat_blocked(entity this, entity blocker)
332 if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
334 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
341 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
342 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
345 //Dont chamge direction for dead or dying stuff
346 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
350 if (this.state == STATE_DOWN)
351 door_rotating_go_up (this, blocker);
353 door_rotating_go_down (this);
359 //gib dying stuff just to make sure
360 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
361 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
368 =========================================
371 Spawned if a door lacks a real activator
372 =========================================
375 void door_trigger_touch(entity this, entity toucher)
378 if (GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
379 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
381 if (toucher.health < 1)
382 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
386 if (time < this.door_finished)
389 // check if door is locked
390 if (!door_check_keys(this, toucher))
393 this.door_finished = time + 1;
395 door_use(this.owner, toucher, NULL);
398 void door_spawnfield(entity this, vector fmins, vector fmaxs)
401 vector t1 = fmins, t2 = fmaxs;
403 trigger = new(doortriggerfield);
404 set_movetype(trigger, MOVETYPE_NONE);
405 trigger.solid = SOLID_TRIGGER;
406 trigger.owner = this;
408 settouch(trigger, door_trigger_touch);
411 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
423 entity LinkDoors_nextent(entity cur, entity near, entity pass)
425 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
431 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
434 if((e1.absmin_x > e2.absmax_x + DELTA)
435 || (e1.absmin_y > e2.absmax_y + DELTA)
436 || (e1.absmin_z > e2.absmax_z + DELTA)
437 || (e2.absmin_x > e1.absmax_x + DELTA)
438 || (e2.absmin_y > e1.absmax_y + DELTA)
439 || (e2.absmin_z > e1.absmax_z + DELTA)
447 void LinkDoors(entity this)
457 return; // already linked by another door
458 if (this.spawnflags & DOOR_DONT_LINK)
460 this.owner = this.enemy = this;
463 if (GetResourceAmount(this, RESOURCE_HEALTH))
473 door_spawnfield(this, this.absmin, this.absmax);
475 return; // don't want to link this door
478 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
480 // set owner, and make a loop of the chain
481 LOG_TRACE("LinkDoors: linking doors:");
482 for(t = this; ; t = t.enemy)
484 LOG_TRACE(" ", etos(t));
494 // collect health, targetname, message, size
497 for(t = this; ; t = t.enemy)
500 if(GetResourceAmount(t, RESOURCE_HEALTH) && !GetResourceAmount(this, RESOURCE_HEALTH))
501 SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(t, RESOURCE_HEALTH));
503 if(t.health && !this.health)
504 this.health = t.health;
506 if((t.targetname != "") && (this.targetname == ""))
507 this.targetname = t.targetname;
508 if((t.message != "") && (this.message == ""))
509 this.message = t.message;
510 if (t.absmin_x < cmins_x)
511 cmins_x = t.absmin_x;
512 if (t.absmin_y < cmins_y)
513 cmins_y = t.absmin_y;
514 if (t.absmin_z < cmins_z)
515 cmins_z = t.absmin_z;
516 if (t.absmax_x > cmaxs_x)
517 cmaxs_x = t.absmax_x;
518 if (t.absmax_y > cmaxs_y)
519 cmaxs_y = t.absmax_y;
520 if (t.absmax_z > cmaxs_z)
521 cmaxs_z = t.absmax_z;
526 // distribute health, targetname, message
527 for(t = this; t; t = t.enemy)
530 SetResourceAmountExplicit(t, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
532 t.health = this.health;
534 t.targetname = this.targetname;
535 t.message = this.message;
540 // shootable, or triggered doors just needed the owner/enemy links,
541 // they don't spawn a field
544 if (GetResourceAmount(this, RESOURCE_HEALTH))
554 door_spawnfield(this, cmins, cmaxs);
557 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
560 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
561 if two doors touch, they are assumed to be connected and operate as a unit.
563 TOGGLE causes the door to wait in both the start and end states for a trigger event.
565 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).
567 GOLD_KEY causes the door to open only if the activator holds a gold key.
569 SILVER_KEY causes the door to open only if the activator holds a silver key.
571 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
572 "angle" determines the opening direction
573 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
574 "health" if set, door must be shot open
575 "speed" movement speed (100 default)
576 "wait" wait before returning (3 default, -1 = never return)
577 "lip" lip remaining at end of move (8 default)
578 "dmg" damage to inflict when blocked (2 default)
585 FIXME: only one sound set available at the time being
589 float door_send(entity this, entity to, float sf)
591 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
592 WriteByte(MSG_ENTITY, sf);
594 if(sf & SF_TRIGGER_INIT)
596 WriteString(MSG_ENTITY, this.classname);
597 WriteByte(MSG_ENTITY, this.spawnflags);
599 WriteString(MSG_ENTITY, this.model);
601 trigger_common_write(this, true);
603 WriteVector(MSG_ENTITY, this.pos1);
604 WriteVector(MSG_ENTITY, this.pos2);
606 WriteVector(MSG_ENTITY, this.size);
608 WriteShort(MSG_ENTITY, this.wait);
609 WriteShort(MSG_ENTITY, this.speed);
610 WriteByte(MSG_ENTITY, this.lip);
611 WriteByte(MSG_ENTITY, this.state);
612 WriteCoord(MSG_ENTITY, this.ltime);
615 if(sf & SF_TRIGGER_RESET)
617 // client makes use of this, we do not
620 if(sf & SF_TRIGGER_UPDATE)
622 WriteVector(MSG_ENTITY, this.origin);
624 WriteVector(MSG_ENTITY, this.pos1);
625 WriteVector(MSG_ENTITY, this.pos2);
633 // set size now, as everything is loaded
635 //Net_LinkEntity(this, false, 0, door_send);
639 void door_init_startopen(entity this)
641 setorigin(this, this.pos2);
642 this.pos2 = this.pos1;
643 this.pos1 = this.origin;
646 this.SendFlags |= SF_TRIGGER_UPDATE;
650 void door_reset(entity this)
652 setorigin(this, this.pos1);
653 this.velocity = '0 0 0';
654 this.state = STATE_BOTTOM;
655 setthink(this, func_null);
659 this.SendFlags |= SF_TRIGGER_RESET;
665 // common code for func_door and func_door_rotating spawnfuncs
666 void door_init_shared(entity this)
668 this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
673 this.noise = "misc/talk.wav";
675 // door still locked sound
676 if(this.noise3 == "")
678 this.noise3 = "misc/talk.wav";
680 precache_sound(this.noise);
681 precache_sound(this.noise3);
683 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == ""))
685 this.message = "was squished";
687 if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == ""))
689 this.message2 = "was squished by";
692 // TODO: other soundpacks
695 this.noise2 = "plats/medplat1.wav";
696 this.noise1 = "plats/medplat2.wav";
699 // sound when door stops moving
700 if(this.noise1 && this.noise1 != "")
702 precache_sound(this.noise1);
704 // sound when door is moving
705 if(this.noise2 && this.noise2 != "")
707 precache_sound(this.noise2);
719 this.state = STATE_BOTTOM;
721 if (GetResourceAmount(this, RESOURCE_HEALTH))
723 //this.canteamdamage = true; // TODO
724 this.takedamage = DAMAGE_YES;
725 this.event_damage = door_damage;
734 // spawnflags require key (for now only func_door)
737 // Quake 1 keys compatibility
738 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
739 this.itemkeys |= ITEM_KEY_BIT(0);
740 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
741 this.itemkeys |= ITEM_KEY_BIT(1);
745 if (!InitMovingBrushTrigger(this))
747 this.effects |= EF_LOWPRECISION;
748 this.classname = "door";
750 setblocked(this, door_blocked);
753 this.pos1 = this.origin;
754 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
756 if(this.spawnflags & DOOR_NONSOLID)
757 this.solid = SOLID_NOT;
759 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
760 // but spawn in the open position
761 if (this.spawnflags & DOOR_START_OPEN)
762 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
764 door_init_shared(this);
771 settouch(this, door_touch);
773 // LinkDoors can't be done until all of the doors have been spawned, so
774 // the sizes can be detected properly.
775 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
777 this.reset = door_reset;
782 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
786 if(sf & SF_TRIGGER_INIT)
788 this.classname = strzone(ReadString());
789 this.spawnflags = ReadByte();
791 this.mdl = strzone(ReadString());
792 _setmodel(this, this.mdl);
794 trigger_common_read(this, true);
796 this.pos1 = ReadVector();
797 this.pos2 = ReadVector();
799 this.size = ReadVector();
801 this.wait = ReadShort();
802 this.speed = ReadShort();
803 this.lip = ReadByte();
804 this.state = ReadByte();
805 this.ltime = ReadCoord();
807 this.solid = SOLID_BSP;
808 set_movetype(this, MOVETYPE_PUSH);
813 if(this.spawnflags & DOOR_START_OPEN)
814 door_init_startopen(this);
816 this.move_time = time;
817 set_movetype(this, MOVETYPE_PUSH);
820 if(sf & SF_TRIGGER_RESET)
825 if(sf & SF_TRIGGER_UPDATE)
827 this.origin = ReadVector();
828 setorigin(this, this.origin);
830 this.pos1 = ReadVector();
831 this.pos2 = ReadVector();