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)
59 if (this.classname == "door")
61 door_go_up (this, NULL, NULL);
64 door_rotating_go_up(this, blocker);
67 if (this.classname == "door")
72 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');
87 void door_hit_top(entity this)
89 if (this.noise1 != "")
90 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
91 this.state = STATE_TOP;
92 if (this.spawnflags & DOOR_TOGGLE)
93 return; // don't come down automatically
94 if (this.classname == "door")
96 setthink(this, door_go_down);
99 setthink(this, door_rotating_go_down);
101 this.nextthink = this.ltime + this.wait;
104 void door_hit_bottom(entity this)
106 if (this.noise1 != "")
107 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
108 this.state = STATE_BOTTOM;
111 void door_go_down(entity this)
113 if (this.noise2 != "")
114 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
117 this.takedamage = DAMAGE_YES;
118 this.health = this.max_health;
121 this.state = STATE_DOWN;
122 SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
125 void door_go_up(entity this, entity actor, entity trigger)
127 if (this.state == STATE_UP)
128 return; // already going up
130 if (this.state == STATE_TOP)
131 { // reset top wait time
132 this.nextthink = this.ltime + this.wait;
136 if (this.noise2 != "")
137 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
138 this.state = STATE_UP;
139 SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top);
142 oldmessage = this.message;
144 SUB_UseTargets(this, actor, trigger);
145 this.message = oldmessage;
150 =============================================================================
154 =============================================================================
157 bool door_check_keys(entity door, entity player)
166 // this door require a key
167 // only a player can have a key
168 if(!IS_PLAYER(player))
171 entity store = player;
175 int valid = (door.itemkeys & store.itemkeys);
176 door.itemkeys &= ~valid; // only some of the needed keys were given
181 play2(player, door.noise);
182 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
190 if(player.key_door_messagetime <= time)
192 play2(player, door.noise3);
193 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
194 player.key_door_messagetime = time + 2;
200 // door needs keys the player doesn't have
202 if(player.key_door_messagetime <= time)
204 play2(player, door.noise3);
205 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
206 player.key_door_messagetime = time + 2;
213 void door_fire(entity this, entity actor, entity trigger)
215 if (this.owner != this)
216 objerror (this, "door_fire: this.owner != this");
218 if (this.spawnflags & DOOR_TOGGLE)
220 if (this.state == STATE_UP || this.state == STATE_TOP)
224 if (e.classname == "door") {
227 door_rotating_go_down(e);
230 } while ((e != this) && (e != NULL));
235 // trigger all paired doors
238 if (e.classname == "door") {
239 door_go_up(e, actor, trigger);
241 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
242 if ((e.spawnflags & BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
243 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
244 e.pos2 = '0 0 0' - e.pos2;
246 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
247 if (!((e.spawnflags & BIDIR) && (e.spawnflags & BIDIR_IN_DOWN) && e.state == STATE_DOWN
248 && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0)))))
250 door_rotating_go_up(e, trigger);
254 } while ((e != this) && (e != NULL));
257 void door_use(entity this, entity actor, entity trigger)
259 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
262 door_fire(this.owner, actor, trigger);
265 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
267 if(this.spawnflags & DOOR_NOSPLASH)
268 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
270 this.health = this.health - damage;
274 // don't allow opening doors through damage if keys are required
278 if (this.health <= 0)
280 this.owner.health = this.owner.max_health;
281 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
282 door_use(this.owner, NULL, NULL);
286 .float door_finished;
296 void door_touch(entity this, entity toucher)
298 if (!IS_PLAYER(toucher))
300 if (this.owner.door_finished > time)
303 this.owner.door_finished = time + 2;
306 if (!(this.owner.dmg) && (this.owner.message != ""))
308 if (IS_CLIENT(toucher))
309 centerprint(toucher, this.owner.message);
310 play2(toucher, this.owner.noise);
315 void door_generic_plat_blocked(entity this, entity blocker)
317 if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
319 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
326 if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite?
327 Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
330 //Dont chamge direction for dead or dying stuff
331 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
335 if (this.state == STATE_DOWN)
336 door_rotating_go_up (this, blocker);
338 door_rotating_go_down (this);
344 //gib dying stuff just to make sure
345 if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite?
346 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
353 =========================================
356 Spawned if a door lacks a real activator
357 =========================================
360 void door_trigger_touch(entity this, entity toucher)
362 if (toucher.health < 1)
364 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
366 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
370 if (time < this.door_finished)
373 // check if door is locked
374 if (!door_check_keys(this, toucher))
377 this.door_finished = time + 1;
379 door_use(this.owner, toucher, NULL);
382 void door_spawnfield(entity this, vector fmins, vector fmaxs)
385 vector t1 = fmins, t2 = fmaxs;
387 trigger = new(doortriggerfield);
388 set_movetype(trigger, MOVETYPE_NONE);
389 trigger.solid = SOLID_TRIGGER;
390 trigger.owner = this;
392 settouch(trigger, door_trigger_touch);
395 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
407 entity LinkDoors_nextent(entity cur, entity near, entity pass)
409 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
415 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
418 if((e1.absmin_x > e2.absmax_x + DELTA)
419 || (e1.absmin_y > e2.absmax_y + DELTA)
420 || (e1.absmin_z > e2.absmax_z + DELTA)
421 || (e2.absmin_x > e1.absmax_x + DELTA)
422 || (e2.absmin_y > e1.absmax_y + DELTA)
423 || (e2.absmin_z > e1.absmax_z + DELTA)
431 void LinkDoors(entity this)
441 return; // already linked by another door
442 if (this.spawnflags & DOOR_DONT_LINK)
444 this.owner = this.enemy = this;
453 door_spawnfield(this, this.absmin, this.absmax);
455 return; // don't want to link this door
458 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
460 // set owner, and make a loop of the chain
461 LOG_TRACE("LinkDoors: linking doors:");
462 for(t = this; ; t = t.enemy)
464 LOG_TRACE(" ", etos(t));
474 // collect health, targetname, message, size
477 for(t = this; ; t = t.enemy)
479 if(t.health && !this.health)
480 this.health = t.health;
481 if((t.targetname != "") && (this.targetname == ""))
482 this.targetname = t.targetname;
483 if((t.message != "") && (this.message == ""))
484 this.message = t.message;
485 if (t.absmin_x < cmins_x)
486 cmins_x = t.absmin_x;
487 if (t.absmin_y < cmins_y)
488 cmins_y = t.absmin_y;
489 if (t.absmin_z < cmins_z)
490 cmins_z = t.absmin_z;
491 if (t.absmax_x > cmaxs_x)
492 cmaxs_x = t.absmax_x;
493 if (t.absmax_y > cmaxs_y)
494 cmaxs_y = t.absmax_y;
495 if (t.absmax_z > cmaxs_z)
496 cmaxs_z = t.absmax_z;
501 // distribute health, targetname, message
502 for(t = this; t; t = t.enemy)
504 t.health = this.health;
505 t.targetname = this.targetname;
506 t.message = this.message;
511 // shootable, or triggered doors just needed the owner/enemy links,
512 // they don't spawn a field
521 door_spawnfield(this, cmins, cmaxs);
524 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
527 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
528 if two doors touch, they are assumed to be connected and operate as a unit.
530 TOGGLE causes the door to wait in both the start and end states for a trigger event.
532 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).
534 GOLD_KEY causes the door to open only if the activator holds a gold key.
536 SILVER_KEY causes the door to open only if the activator holds a silver key.
538 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
539 "angle" determines the opening direction
540 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
541 "health" if set, door must be shot open
542 "speed" movement speed (100 default)
543 "wait" wait before returning (3 default, -1 = never return)
544 "lip" lip remaining at end of move (8 default)
545 "dmg" damage to inflict when blocked (2 default)
552 FIXME: only one sound set available at the time being
556 float door_send(entity this, entity to, float sf)
558 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
559 WriteByte(MSG_ENTITY, sf);
561 if(sf & SF_TRIGGER_INIT)
563 WriteString(MSG_ENTITY, this.classname);
564 WriteByte(MSG_ENTITY, this.spawnflags);
566 WriteString(MSG_ENTITY, this.model);
568 trigger_common_write(this, true);
570 WriteVector(MSG_ENTITY, this.pos1);
571 WriteVector(MSG_ENTITY, this.pos2);
573 WriteVector(MSG_ENTITY, this.size);
575 WriteShort(MSG_ENTITY, this.wait);
576 WriteShort(MSG_ENTITY, this.speed);
577 WriteByte(MSG_ENTITY, this.lip);
578 WriteByte(MSG_ENTITY, this.state);
579 WriteCoord(MSG_ENTITY, this.ltime);
582 if(sf & SF_TRIGGER_RESET)
584 // client makes use of this, we do not
587 if(sf & SF_TRIGGER_UPDATE)
589 WriteVector(MSG_ENTITY, this.origin);
591 WriteVector(MSG_ENTITY, this.pos1);
592 WriteVector(MSG_ENTITY, this.pos2);
600 // set size now, as everything is loaded
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 = this.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
662 this.noise2 = "plats/medplat1.wav";
663 this.noise1 = "plats/medplat2.wav";
666 // sound when door stops moving
667 if(this.noise1 && this.noise1 != "")
669 precache_sound(this.noise1);
671 // sound when door is moving
672 if(this.noise2 && this.noise2 != "")
674 precache_sound(this.noise2);
686 this.state = STATE_BOTTOM;
690 //this.canteamdamage = true; // TODO
691 this.takedamage = DAMAGE_YES;
692 this.event_damage = door_damage;
701 // spawnflags require key (for now only func_door)
704 // Quake 1 keys compatibility
705 if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
706 this.itemkeys |= ITEM_KEY_BIT(0);
707 if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
708 this.itemkeys |= ITEM_KEY_BIT(1);
712 if (!InitMovingBrushTrigger(this))
714 this.effects |= EF_LOWPRECISION;
715 this.classname = "door";
717 setblocked(this, door_blocked);
720 this.pos1 = this.origin;
721 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
723 if(this.spawnflags & DOOR_NONSOLID)
724 this.solid = SOLID_NOT;
726 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
727 // but spawn in the open position
728 if (this.spawnflags & DOOR_START_OPEN)
729 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
731 door_init_shared(this);
738 settouch(this, door_touch);
740 // LinkDoors can't be done until all of the doors have been spawned, so
741 // the sizes can be detected properly.
742 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
744 this.reset = door_reset;
749 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
753 if(sf & SF_TRIGGER_INIT)
755 this.classname = strzone(ReadString());
756 this.spawnflags = ReadByte();
758 this.mdl = strzone(ReadString());
759 _setmodel(this, this.mdl);
761 trigger_common_read(this, true);
763 this.pos1 = ReadVector();
764 this.pos2 = ReadVector();
766 this.size = ReadVector();
768 this.wait = ReadShort();
769 this.speed = ReadShort();
770 this.lip = ReadByte();
771 this.state = ReadByte();
772 this.ltime = ReadCoord();
774 this.solid = SOLID_BSP;
775 set_movetype(this, MOVETYPE_PUSH);
780 if(this.spawnflags & DOOR_START_OPEN)
781 door_init_startopen(this);
783 this.move_time = time;
784 set_movetype(this, MOVETYPE_PUSH);
787 if(sf & SF_TRIGGER_RESET)
792 if(sf & SF_TRIGGER_UPDATE)
794 this.origin = ReadVector();
795 setorigin(this, this.origin);
797 this.pos1 = ReadVector();
798 this.pos2 = ReadVector();