3 Doors are similar to buttons, but can spawn a fat trigger field around them
4 to open without a touch, and they link together to form simultanious
7 Door.owner is the master door. If there is only one door, it points to itself.
8 If multiple doors, all will point to a single one.
10 Door.enemy chains from the master door through all doors linked in the chain.
16 =============================================================================
20 =============================================================================
23 void door_go_down(entity this);
24 void door_go_up(entity this);
25 void door_rotating_go_down(entity this);
26 void door_rotating_go_up(entity this);
30 if((this.spawnflags & 8)
32 && (other.takedamage != DAMAGE_NO)
39 Damage (other, this, this, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
45 if((this.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
46 Damage (other, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
49 // don't change direction for dead or dying stuff
52 && (other.takedamage == DAMAGE_NO)
58 if (this.state == STATE_DOWN)
59 if (this.classname == "door")
64 door_rotating_go_up (this);
67 if (this.classname == "door")
72 door_rotating_go_down (this);
79 //gib dying stuff just to make sure
80 if((this.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
81 Damage (other, this, this, 10000, DEATH_HURTTRIGGER.m_id, other.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 SUB_THINK(this, door_go_down);
99 SUB_THINK(this, door_rotating_go_down);
101 this.SUB_NEXTTHINK = this.SUB_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)
127 if (this.state == STATE_UP)
128 return; // already going up
130 if (this.state == STATE_TOP)
131 { // reset top wait time
132 this.SUB_NEXTTHINK = this.SUB_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, NULL, NULL);
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 int valid = (door.itemkeys & player.itemkeys);
172 door.itemkeys &= ~valid; // only some of the needed keys were given
177 play2(player, SND(TALK));
178 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
186 if(player.key_door_messagetime <= time)
188 play2(player, door.noise3);
189 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
190 player.key_door_messagetime = time + 2;
196 // door needs keys the player doesn't have
198 if(player.key_door_messagetime <= time)
200 play2(player, door.noise3);
201 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
202 player.key_door_messagetime = time + 2;
209 void door_fire(entity this, entity actor, entity trigger)
211 if (this.owner != this)
212 objerror ("door_fire: this.owner != this");
214 if (this.spawnflags & DOOR_TOGGLE)
216 if (this.state == STATE_UP || this.state == STATE_TOP)
220 if (e.classname == "door") {
223 door_rotating_go_down(e);
226 } while ((e != this) && (e != NULL));
231 // trigger all paired doors
234 if (e.classname == "door") {
237 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
238 if ((e.spawnflags & 2) && other.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) {
239 e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating
240 e.pos2 = '0 0 0' - e.pos2;
242 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
243 if (!((e.spawnflags & 2) && (e.spawnflags & 8) && e.state == STATE_DOWN
244 && (((e.lip == 666) && (other.trigger_reverse == 0)) || ((e.lip != 666) && (other.trigger_reverse != 0)))))
246 door_rotating_go_up(e);
250 } while ((e != this) && (e != NULL));
253 void door_use(entity this, entity actor, entity trigger)
255 //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
258 door_fire(this.owner, actor, trigger);
261 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
263 if(this.spawnflags & DOOR_NOSPLASH)
264 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
266 this.health = this.health - damage;
270 // don't allow opening doors through damage if keys are required
274 if (this.health <= 0)
276 this.owner.health = this.owner.max_health;
277 this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
278 door_use(this.owner, NULL, NULL);
282 .float door_finished;
292 void door_touch(entity this)
294 if (!IS_PLAYER(other))
296 if (this.owner.door_finished > time)
299 this.owner.door_finished = time + 2;
302 if (!(this.owner.dmg) && (this.owner.message != ""))
304 if (IS_CLIENT(other))
305 centerprint(other, this.owner.message);
306 play2(other, this.owner.noise);
311 void door_generic_plat_blocked(entity this)
314 if((this.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
316 Damage (other, this, this, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
323 if((this.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
324 Damage (other, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
327 //Dont chamge direction for dead or dying stuff
328 if(IS_DEAD(other) && (other.takedamage == DAMAGE_NO))
332 if (this.state == STATE_DOWN)
333 door_rotating_go_up (this);
335 door_rotating_go_down (this);
341 //gib dying stuff just to make sure
342 if((this.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
343 Damage (other, this, this, 10000, DEATH_HURTTRIGGER.m_id, other.origin, '0 0 0');
349 void door_rotating_hit_top(entity this)
351 if (this.noise1 != "")
352 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
353 this.state = STATE_TOP;
354 if (this.spawnflags & DOOR_TOGGLE)
355 return; // don't come down automatically
356 SUB_THINK(this, door_rotating_go_down);
357 this.SUB_NEXTTHINK = this.SUB_LTIME + this.wait;
360 void door_rotating_hit_bottom(entity this)
362 if (this.noise1 != "")
363 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
364 if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating
366 this.pos2 = '0 0 0' - this.pos2;
369 this.state = STATE_BOTTOM;
372 void door_rotating_go_down(entity this)
374 if (this.noise2 != "")
375 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
378 this.takedamage = DAMAGE_YES;
379 this.health = this.max_health;
382 this.state = STATE_DOWN;
383 SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom);
386 void door_rotating_go_up(entity this)
388 if (this.state == STATE_UP)
389 return; // already going up
391 if (this.state == STATE_TOP)
392 { // reset top wait time
393 this.SUB_NEXTTHINK = this.SUB_LTIME + this.wait;
396 if (this.noise2 != "")
397 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
398 this.state = STATE_UP;
399 SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top);
402 oldmessage = this.message;
404 SUB_UseTargets(this, NULL, other); // TODO: is other needed here?
405 this.message = oldmessage;
410 =========================================
413 Spawned if a door lacks a real activator
414 =========================================
417 void door_trigger_touch(entity this)
419 if (other.health < 1)
421 if (!((other.iscreature || (other.flags & FL_PROJECTILE)) && !IS_DEAD(other)))
423 if(!((IS_CLIENT(other) || other.classname == "csqcprojectile") && !IS_DEAD(other)))
427 if (time < this.door_finished)
430 // check if door is locked
431 if (!door_check_keys(this, other))
434 this.door_finished = time + 1;
436 door_use(this.owner, other, NULL);
439 void door_spawnfield(entity this, vector fmins, vector fmaxs)
442 vector t1 = fmins, t2 = fmaxs;
444 trigger = new(doortriggerfield);
445 trigger.movetype = MOVETYPE_NONE;
446 trigger.solid = SOLID_TRIGGER;
447 trigger.owner = this;
449 settouch(trigger, door_trigger_touch);
452 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
464 entity LinkDoors_nextent(entity cur, entity near, entity pass)
466 while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & 4) || cur.enemy))
472 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
475 if((e1.absmin_x > e2.absmax_x + DELTA)
476 || (e1.absmin_y > e2.absmax_y + DELTA)
477 || (e1.absmin_z > e2.absmax_z + DELTA)
478 || (e2.absmin_x > e1.absmax_x + DELTA)
479 || (e2.absmin_y > e1.absmax_y + DELTA)
480 || (e2.absmin_z > e1.absmax_z + DELTA)
488 void LinkDoors(entity this)
498 return; // already linked by another door
499 if (this.spawnflags & 4)
501 this.owner = this.enemy = this;
510 door_spawnfield(this, this.absmin, this.absmax);
512 return; // don't want to link this door
515 FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
517 // set owner, and make a loop of the chain
518 LOG_TRACE("LinkDoors: linking doors:");
519 for(t = this; ; t = t.enemy)
521 LOG_TRACE(" ", etos(t));
531 // collect health, targetname, message, size
534 for(t = this; ; t = t.enemy)
536 if(t.health && !this.health)
537 this.health = t.health;
538 if((t.targetname != "") && (this.targetname == ""))
539 this.targetname = t.targetname;
540 if((t.message != "") && (this.message == ""))
541 this.message = t.message;
542 if (t.absmin_x < cmins_x)
543 cmins_x = t.absmin_x;
544 if (t.absmin_y < cmins_y)
545 cmins_y = t.absmin_y;
546 if (t.absmin_z < cmins_z)
547 cmins_z = t.absmin_z;
548 if (t.absmax_x > cmaxs_x)
549 cmaxs_x = t.absmax_x;
550 if (t.absmax_y > cmaxs_y)
551 cmaxs_y = t.absmax_y;
552 if (t.absmax_z > cmaxs_z)
553 cmaxs_z = t.absmax_z;
558 // distribute health, targetname, message
559 for(t = this; t; t = t.enemy)
561 t.health = this.health;
562 t.targetname = this.targetname;
563 t.message = this.message;
568 // shootable, or triggered doors just needed the owner/enemy links,
569 // they don't spawn a field
578 door_spawnfield(this, cmins, cmaxs);
581 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
584 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
585 if two doors touch, they are assumed to be connected and operate as a unit.
587 TOGGLE causes the door to wait in both the start and end states for a trigger event.
589 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).
591 GOLD_KEY causes the door to open only if the activator holds a gold key.
593 SILVER_KEY causes the door to open only if the activator holds a silver key.
595 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
596 "angle" determines the opening direction
597 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
598 "health" if set, door must be shot open
599 "speed" movement speed (100 default)
600 "wait" wait before returning (3 default, -1 = never return)
601 "lip" lip remaining at end of move (8 default)
602 "dmg" damage to inflict when blocked (2 default)
609 FIXME: only one sound set available at the time being
613 float door_send(entity this, entity to, float sf)
615 WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
616 WriteByte(MSG_ENTITY, sf);
618 if(sf & SF_TRIGGER_INIT)
620 WriteString(MSG_ENTITY, this.classname);
621 WriteByte(MSG_ENTITY, this.spawnflags);
623 WriteString(MSG_ENTITY, this.model);
625 trigger_common_write(this, true);
627 WriteCoord(MSG_ENTITY, this.pos1_x);
628 WriteCoord(MSG_ENTITY, this.pos1_y);
629 WriteCoord(MSG_ENTITY, this.pos1_z);
630 WriteCoord(MSG_ENTITY, this.pos2_x);
631 WriteCoord(MSG_ENTITY, this.pos2_y);
632 WriteCoord(MSG_ENTITY, this.pos2_z);
634 WriteCoord(MSG_ENTITY, this.size_x);
635 WriteCoord(MSG_ENTITY, this.size_y);
636 WriteCoord(MSG_ENTITY, this.size_z);
638 WriteShort(MSG_ENTITY, this.wait);
639 WriteShort(MSG_ENTITY, this.speed);
640 WriteByte(MSG_ENTITY, this.lip);
641 WriteByte(MSG_ENTITY, this.state);
642 WriteCoord(MSG_ENTITY, this.SUB_LTIME);
645 if(sf & SF_TRIGGER_RESET)
647 // client makes use of this, we do not
650 if(sf & SF_TRIGGER_UPDATE)
652 WriteCoord(MSG_ENTITY, this.origin_x);
653 WriteCoord(MSG_ENTITY, this.origin_y);
654 WriteCoord(MSG_ENTITY, this.origin_z);
656 WriteCoord(MSG_ENTITY, this.pos1_x);
657 WriteCoord(MSG_ENTITY, this.pos1_y);
658 WriteCoord(MSG_ENTITY, this.pos1_z);
659 WriteCoord(MSG_ENTITY, this.pos2_x);
660 WriteCoord(MSG_ENTITY, this.pos2_y);
661 WriteCoord(MSG_ENTITY, this.pos2_z);
669 // set size now, as everything is loaded
671 //Net_LinkEntity(this, false, 0, door_send);
675 void door_init_startopen(entity this)
677 SUB_SETORIGIN(this, this.pos2);
678 this.pos2 = this.pos1;
679 this.pos1 = this.origin;
682 this.SendFlags |= SF_TRIGGER_UPDATE;
686 void door_reset(entity this)
688 SUB_SETORIGIN(this, this.pos1);
689 this.SUB_VELOCITY = '0 0 0';
690 this.state = STATE_BOTTOM;
691 SUB_THINK(this, func_null);
692 this.SUB_NEXTTHINK = 0;
695 this.SendFlags |= SF_TRIGGER_RESET;
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 this.max_health = this.health;
713 if (!InitMovingBrushTrigger(this))
715 this.effects |= EF_LOWPRECISION;
716 this.classname = "door";
719 this.noise = "misc/talk.wav";
720 if(this.noise3 == "")
721 this.noise3 = "misc/talk.wav";
722 precache_sound(this.noise);
723 precache_sound(this.noise3);
725 this.blocked = door_blocked;
728 if(this.dmg && (this.message == ""))
729 this.message = "was squished";
730 if(this.dmg && (this.message2 == ""))
731 this.message2 = "was squished by";
735 precache_sound ("plats/medplat1.wav");
736 precache_sound ("plats/medplat2.wav");
737 this.noise2 = "plats/medplat1.wav";
738 this.noise1 = "plats/medplat2.wav";
748 this.pos1 = this.SUB_ORIGIN;
749 this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
751 if(this.spawnflags & DOOR_NONSOLID)
752 this.solid = SOLID_NOT;
754 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
755 // but spawn in the open position
756 if (this.spawnflags & DOOR_START_OPEN)
757 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
759 this.state = STATE_BOTTOM;
763 this.takedamage = DAMAGE_YES;
764 this.event_damage = door_damage;
770 settouch(this, door_touch);
772 // LinkDoors can't be done until all of the doors have been spawned, so
773 // the sizes can be detected properly.
774 InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
776 this.reset = door_reset;
781 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
785 if(sf & SF_TRIGGER_INIT)
787 this.classname = strzone(ReadString());
788 this.spawnflags = ReadByte();
790 this.mdl = strzone(ReadString());
791 _setmodel(this, this.mdl);
793 trigger_common_read(this, true);
812 this.wait = ReadShort();
813 this.speed = ReadShort();
814 this.lip = ReadByte();
815 this.state = ReadByte();
816 this.SUB_LTIME = ReadCoord();
818 this.solid = SOLID_BSP;
819 this.movetype = MOVETYPE_PUSH;
824 if(this.spawnflags & DOOR_START_OPEN)
825 door_init_startopen(this);
827 this.move_time = time;
828 this.move_origin = this.origin;
829 this.move_movetype = MOVETYPE_PUSH;
830 this.move_angles = this.angles;
831 this.move_blocked = door_blocked;
834 if(sf & SF_TRIGGER_RESET)
839 if(sf & SF_TRIGGER_UPDATE)
841 this.origin_x = ReadCoord();
842 this.origin_y = ReadCoord();
843 this.origin_z = ReadCoord();
844 setorigin(this, this.origin);
845 this.move_origin = this.origin;
847 this.pos1_x = ReadCoord();
848 this.pos1_y = ReadCoord();
849 this.pos1_z = ReadCoord();
850 this.pos2_x = ReadCoord();
851 this.pos2_y = ReadCoord();
852 this.pos2_z = ReadCoord();