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 =============================================================================
25 void() door_rotating_go_down;
26 void() door_rotating_go_up;
31 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
32 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
35 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
36 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
38 //Dont chamge direction for dead or dying stuff
39 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
42 if (self.state == STATE_DOWN)
43 if (self.classname == "door")
48 door_rotating_go_up ();
51 if (self.classname == "door")
56 door_rotating_go_down ();
60 //gib dying stuff just to make sure
61 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
62 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
69 if (self.noise1 != "")
70 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
71 self.state = STATE_TOP;
72 if (self.spawnflags & DOOR_TOGGLE)
73 return; // don't come down automatically
74 if (self.classname == "door")
76 self.think = door_go_down;
79 self.think = door_rotating_go_down;
81 self.nextthink = self.ltime + self.wait;
84 void door_hit_bottom()
86 if (self.noise1 != "")
87 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
88 self.state = STATE_BOTTOM;
93 if (self.noise2 != "")
94 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
97 self.takedamage = DAMAGE_YES;
98 self.health = self.max_health;
101 self.state = STATE_DOWN;
102 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
107 if (self.state == STATE_UP)
108 return; // already going up
110 if (self.state == STATE_TOP)
111 { // reset top wait time
112 self.nextthink = self.ltime + self.wait;
116 if (self.noise2 != "")
117 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
118 self.state = STATE_UP;
119 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
122 oldmessage = self.message;
125 self.message = oldmessage;
130 =============================================================================
134 =============================================================================
137 float door_check_keys(void) {
150 // this door require a key
151 // only a player can have a key
152 if (!IS_PLAYER(other))
155 if (item_keys_usekey(door, other)) {
156 // some keys were used
157 if (other.key_door_messagetime <= time) {
158 play2(other, "misc/talk.wav");
159 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
160 other.key_door_messagetime = time + 2;
164 if (other.key_door_messagetime <= time) {
165 play2(other, "misc/talk.wav");
166 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
167 other.key_door_messagetime = time + 2;
172 // door is now unlocked
173 play2(other, "misc/talk.wav");
174 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
185 if (self.owner != self)
186 objerror ("door_fire: self.owner != self");
190 if (self.spawnflags & DOOR_TOGGLE)
192 if (self.state == STATE_UP || self.state == STATE_TOP)
197 if (self.classname == "door")
203 door_rotating_go_down ();
206 } while ( (self != starte) && (self != world) );
212 // trigger all paired doors
216 if (self.classname == "door")
221 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
222 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
224 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
225 self.pos2 = '0 0 0' - self.pos2;
227 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
228 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
229 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
231 door_rotating_go_up ();
235 } while ( (self != starte) && (self != world) );
243 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
254 void door_trigger_touch()
256 if (other.health < 1)
257 if (!(other.iscreature && other.deadflag == DEAD_NO))
260 if (time < self.attack_finished_single)
263 // check if door is locked
264 if (!door_check_keys())
267 self.attack_finished_single = time + 1;
275 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
278 if(self.spawnflags & DOOR_NOSPLASH)
279 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
281 self.health = self.health - damage;
284 // don't allow opening doors through damage if keys are required
288 if (self.health <= 0)
292 self.health = self.max_health;
293 self.takedamage = DAMAGE_NO; // wil be reset upon return
310 if (!IS_PLAYER(other))
312 if (self.owner.attack_finished_single > time)
315 self.owner.attack_finished_single = time + 2;
317 if (!(self.owner.dmg) && (self.owner.message != ""))
319 if (IS_CLIENT(other))
320 centerprint(other, self.owner.message);
321 play2(other, "misc/talk.wav");
325 void door_generic_plat_blocked()
328 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
329 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
332 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
333 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
335 //Dont chamge direction for dead or dying stuff
336 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
339 if (self.state == STATE_DOWN)
340 door_rotating_go_up ();
342 door_rotating_go_down ();
345 //gib dying stuff just to make sure
346 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
347 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
352 void door_rotating_hit_top()
354 if (self.noise1 != "")
355 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
356 self.state = STATE_TOP;
357 if (self.spawnflags & DOOR_TOGGLE)
358 return; // don't come down automatically
359 self.think = door_rotating_go_down;
360 self.nextthink = self.ltime + self.wait;
363 void door_rotating_hit_bottom()
365 if (self.noise1 != "")
366 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
367 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
369 self.pos2 = '0 0 0' - self.pos2;
372 self.state = STATE_BOTTOM;
375 void door_rotating_go_down()
377 if (self.noise2 != "")
378 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
381 self.takedamage = DAMAGE_YES;
382 self.health = self.max_health;
385 self.state = STATE_DOWN;
386 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
389 void door_rotating_go_up()
391 if (self.state == STATE_UP)
392 return; // already going up
394 if (self.state == STATE_TOP)
395 { // reset top wait time
396 self.nextthink = self.ltime + self.wait;
399 if (self.noise2 != "")
400 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
401 self.state = STATE_UP;
402 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
405 oldmessage = self.message;
408 self.message = oldmessage;
413 =============================================================================
417 =============================================================================
420 entity spawn_field(vector fmins, vector fmaxs)
426 trigger.classname = "doortriggerfield";
427 trigger.movetype = MOVETYPE_NONE;
428 trigger.solid = SOLID_TRIGGER;
429 trigger.owner = self;
430 trigger.touch = door_trigger_touch;
434 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
438 entity LinkDoors_nextent(entity cur, entity near, entity pass)
440 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
446 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
449 if (e1.absmin_x > e2.absmax_x + DELTA)
451 if (e1.absmin_y > e2.absmax_y + DELTA)
453 if (e1.absmin_z > e2.absmax_z + DELTA)
455 if (e2.absmin_x > e1.absmax_x + DELTA)
457 if (e2.absmin_y > e1.absmax_y + DELTA)
459 if (e2.absmin_z > e1.absmax_z + DELTA)
479 return; // already linked by another door
480 if (self.spawnflags & 4)
482 self.owner = self.enemy = self;
490 self.trigger_field = spawn_field(self.absmin, self.absmax);
492 return; // don't want to link this door
495 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
497 // set owner, and make a loop of the chain
498 dprint("LinkDoors: linking doors:");
499 for(t = self; ; t = t.enemy)
501 dprint(" ", etos(t));
511 // collect health, targetname, message, size
514 for(t = self; ; t = t.enemy)
516 if(t.health && !self.health)
517 self.health = t.health;
518 if((t.targetname != "") && (self.targetname == ""))
519 self.targetname = t.targetname;
520 if((t.message != "") && (self.message == ""))
521 self.message = t.message;
522 if (t.absmin_x < cmins_x)
523 cmins_x = t.absmin_x;
524 if (t.absmin_y < cmins_y)
525 cmins_y = t.absmin_y;
526 if (t.absmin_z < cmins_z)
527 cmins_z = t.absmin_z;
528 if (t.absmax_x > cmaxs_x)
529 cmaxs_x = t.absmax_x;
530 if (t.absmax_y > cmaxs_y)
531 cmaxs_y = t.absmax_y;
532 if (t.absmax_z > cmaxs_z)
533 cmaxs_z = t.absmax_z;
538 // distribute health, targetname, message
539 for(t = self; t; t = t.enemy)
541 t.health = self.health;
542 t.targetname = self.targetname;
543 t.message = self.message;
548 // shootable, or triggered doors just needed the owner/enemy links,
549 // they don't spawn a field
558 self.trigger_field = spawn_field(cmins, cmaxs);
562 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
563 if two doors touch, they are assumed to be connected and operate as a unit.
565 TOGGLE causes the door to wait in both the start and end states for a trigger event.
567 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).
569 GOLD_KEY causes the door to open only if the activator holds a gold key.
571 SILVER_KEY causes the door to open only if the activator holds a silver key.
573 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
574 "angle" determines the opening direction
575 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
576 "health" if set, door must be shot open
577 "speed" movement speed (100 default)
578 "wait" wait before returning (3 default, -1 = never return)
579 "lip" lip remaining at end of move (8 default)
580 "dmg" damage to inflict when blocked (2 default)
587 FIXME: only one sound set available at the time being
591 void door_init_startopen()
593 setorigin (self, self.pos2);
594 self.pos2 = self.pos1;
595 self.pos1 = self.origin;
600 setorigin(self, self.pos1);
601 self.velocity = '0 0 0';
602 self.state = STATE_BOTTOM;
603 self.think = func_null;
607 // spawnflags require key (for now only func_door)
608 void spawnfunc_func_door()
610 // Quake 1 keys compatibility
611 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
612 self.itemkeys |= ITEM_KEY_BIT(0);
613 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
614 self.itemkeys |= ITEM_KEY_BIT(1);
618 self.max_health = self.health;
619 if (!InitMovingBrushTrigger())
621 self.effects |= EF_LOWPRECISION;
622 self.classname = "door";
624 self.blocked = door_blocked;
627 if(self.dmg && (self.message == ""))
628 self.message = "was squished";
629 if(self.dmg && (self.message2 == ""))
630 self.message2 = "was squished by";
634 precache_sound ("plats/medplat1.wav");
635 precache_sound ("plats/medplat2.wav");
636 self.noise2 = "plats/medplat1.wav";
637 self.noise1 = "plats/medplat2.wav";
647 self.pos1 = self.origin;
648 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
650 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
651 // but spawn in the open position
652 if (self.spawnflags & DOOR_START_OPEN)
653 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
655 self.state = STATE_BOTTOM;
659 self.takedamage = DAMAGE_YES;
660 self.event_damage = door_damage;
666 self.touch = door_touch;
668 // LinkDoors can't be done until all of the doors have been spawned, so
669 // the sizes can be detected properly.
670 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
672 self.reset = door_reset;