]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/f_door.qc
Begin making triggers common code
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / f_door.qc
1 /*
2
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
5 double/quad doors.
6
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.
9
10 Door.enemy chains from the master door through all doors linked in the chain.
11
12 */
13
14
15 /*
16 =============================================================================
17
18 THINK FUNCTIONS
19
20 =============================================================================
21 */
22
23 void() door_go_down;
24 void() door_go_up;
25 void() door_rotating_go_down;
26 void() door_rotating_go_up;
27
28 void door_blocked()
29 {
30
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');
33     } else {
34
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');
37
38          //Dont chamge direction for dead or dying stuff
39         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
40             if (self.wait >= 0)
41             {
42                 if (self.state == STATE_DOWN)
43                         if (self.classname == "door")
44                         {
45                                 door_go_up ();
46                         } else
47                         {
48                                 door_rotating_go_up ();
49                         }
50                 else
51                         if (self.classname == "door")
52                         {
53                                 door_go_down ();
54                         } else
55                         {
56                                 door_rotating_go_down ();
57                         }
58             }
59         } else {
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');
63         }
64     }
65 }
66
67 void door_hit_top()
68 {
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")
75         {
76                 self.think = door_go_down;
77         } else
78         {
79                 self.think = door_rotating_go_down;
80         }
81         self.nextthink = self.ltime + self.wait;
82 }
83
84 void door_hit_bottom()
85 {
86         if (self.noise1 != "")
87                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
88         self.state = STATE_BOTTOM;
89 }
90
91 void door_go_down()
92 {
93         if (self.noise2 != "")
94                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
95         if (self.max_health)
96         {
97                 self.takedamage = DAMAGE_YES;
98                 self.health = self.max_health;
99         }
100
101         self.state = STATE_DOWN;
102         SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
103 }
104
105 void door_go_up()
106 {
107         if (self.state == STATE_UP)
108                 return;         // already going up
109
110         if (self.state == STATE_TOP)
111         {       // reset top wait time
112                 self.nextthink = self.ltime + self.wait;
113                 return;
114         }
115
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);
120
121         string oldmessage;
122         oldmessage = self.message;
123         self.message = "";
124         SUB_UseTargets();
125         self.message = oldmessage;
126 }
127
128
129 /*
130 =============================================================================
131
132 ACTIVATION FUNCTIONS
133
134 =============================================================================
135 */
136
137 float door_check_keys(void) {
138         local entity door;
139
140
141         if (self.owner)
142                 door = self.owner;
143         else
144                 door = self;
145
146         // no key needed
147         if (!door.itemkeys)
148                 return TRUE;
149
150         // this door require a key
151         // only a player can have a key
152         if (!IS_PLAYER(other))
153                 return FALSE;
154
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;
161                 }
162         } else {
163                 // no keys were used
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;
168                 }
169         }
170
171         if (door.itemkeys) {
172                 // door is now unlocked
173                 play2(other, "misc/talk.wav");
174                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
175                 return TRUE;
176         } else
177                 return FALSE;
178 }
179
180 void door_fire()
181 {
182         entity  oself;
183         entity  starte;
184
185         if (self.owner != self)
186                 objerror ("door_fire: self.owner != self");
187
188         oself = self;
189
190         if (self.spawnflags & DOOR_TOGGLE)
191         {
192                 if (self.state == STATE_UP || self.state == STATE_TOP)
193                 {
194                         starte = self;
195                         do
196                         {
197                                 if (self.classname == "door")
198                                 {
199                                         door_go_down ();
200                                 }
201                                 else
202                                 {
203                                         door_rotating_go_down ();
204                                 }
205                                 self = self.enemy;
206                         } while ( (self != starte) && (self != world) );
207                         self = oself;
208                         return;
209                 }
210         }
211
212 // trigger all paired doors
213         starte = self;
214         do
215         {
216                 if (self.classname == "door")
217                 {
218                         door_go_up ();
219                 } else
220                 {
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)
223                         {
224                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
225                                 self.pos2 = '0 0 0' - self.pos2;
226                         }
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)))))
230                         {
231                                 door_rotating_go_up ();
232                         }
233                 }
234                 self = self.enemy;
235         } while ( (self != starte) && (self != world) );
236         self = oself;
237 }
238
239 void door_use()
240 {
241         entity oself;
242
243         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
244
245         if (self.owner)
246         {
247                 oself = self;
248                 self = self.owner;
249                 door_fire ();
250                 self = oself;
251         }
252 }
253
254 void door_trigger_touch()
255 {
256         if (other.health < 1)
257                 if (!(other.iscreature && other.deadflag == DEAD_NO))
258                         return;
259
260         if (time < self.attack_finished_single)
261                 return;
262
263         // check if door is locked
264         if (!door_check_keys())
265                 return;
266
267         self.attack_finished_single = time + 1;
268
269         activator = other;
270
271         self = self.owner;
272         door_use ();
273 }
274
275 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
276 {
277         entity oself;
278         if(self.spawnflags & DOOR_NOSPLASH)
279                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
280                         return;
281         self.health = self.health - damage;
282
283         if (self.itemkeys) {
284                 // don't allow opening doors through damage if keys are required
285                 return;
286         }
287
288         if (self.health <= 0)
289         {
290                 oself = self;
291                 self = self.owner;
292                 self.health = self.max_health;
293                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
294                 door_use ();
295                 self = oself;
296         }
297 }
298
299
300 /*
301 ================
302 door_touch
303
304 Prints messages
305 ================
306 */
307
308 void door_touch()
309 {
310         if (!IS_PLAYER(other))
311                 return;
312         if (self.owner.attack_finished_single > time)
313                 return;
314
315         self.owner.attack_finished_single = time + 2;
316
317         if (!(self.owner.dmg) && (self.owner.message != ""))
318         {
319                 if (IS_CLIENT(other))
320                         centerprint(other, self.owner.message);
321                 play2(other, "misc/talk.wav");
322         }
323 }
324
325 void door_generic_plat_blocked()
326 {
327
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');
330     } else {
331
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');
334
335          //Dont chamge direction for dead or dying stuff
336         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
337             if (self.wait >= 0)
338             {
339                 if (self.state == STATE_DOWN)
340                     door_rotating_go_up ();
341                 else
342                     door_rotating_go_down ();
343             }
344         } else {
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');
348         }
349     }
350 }
351
352 void door_rotating_hit_top()
353 {
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;
361 }
362
363 void door_rotating_hit_bottom()
364 {
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
368         {
369                 self.pos2 = '0 0 0' - self.pos2;
370                 self.lip = 0;
371         }
372         self.state = STATE_BOTTOM;
373 }
374
375 void door_rotating_go_down()
376 {
377         if (self.noise2 != "")
378                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
379         if (self.max_health)
380         {
381                 self.takedamage = DAMAGE_YES;
382                 self.health = self.max_health;
383         }
384
385         self.state = STATE_DOWN;
386         SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
387 }
388
389 void door_rotating_go_up()
390 {
391         if (self.state == STATE_UP)
392                 return;         // already going up
393
394         if (self.state == STATE_TOP)
395         {       // reset top wait time
396                 self.nextthink = self.ltime + self.wait;
397                 return;
398         }
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);
403
404         string oldmessage;
405         oldmessage = self.message;
406         self.message = "";
407         SUB_UseTargets();
408         self.message = oldmessage;
409 }
410
411
412 /*
413 =============================================================================
414
415 SPAWNING FUNCTIONS
416
417 =============================================================================
418 */
419
420 entity spawn_field(vector fmins, vector fmaxs)
421 {
422         entity  trigger;
423         vector  t1, t2;
424
425         trigger = spawn();
426         trigger.classname = "doortriggerfield";
427         trigger.movetype = MOVETYPE_NONE;
428         trigger.solid = SOLID_TRIGGER;
429         trigger.owner = self;
430         trigger.touch = door_trigger_touch;
431
432         t1 = fmins;
433         t2 = fmaxs;
434         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
435         return (trigger);
436 }
437
438 entity LinkDoors_nextent(entity cur, entity near, entity pass)
439 {
440         while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
441         {
442         }
443         return cur;
444 }
445
446 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
447 {
448         float DELTA = 4;
449         if (e1.absmin_x > e2.absmax_x + DELTA)
450                 return FALSE;
451         if (e1.absmin_y > e2.absmax_y + DELTA)
452                 return FALSE;
453         if (e1.absmin_z > e2.absmax_z + DELTA)
454                 return FALSE;
455         if (e2.absmin_x > e1.absmax_x + DELTA)
456                 return FALSE;
457         if (e2.absmin_y > e1.absmax_y + DELTA)
458                 return FALSE;
459         if (e2.absmin_z > e1.absmax_z + DELTA)
460                 return FALSE;
461         return TRUE;
462 }
463
464
465 /*
466 =============
467 LinkDoors
468
469
470 =============
471 */
472
473 void LinkDoors()
474 {
475         entity  t;
476         vector  cmins, cmaxs;
477
478         if (self.enemy)
479                 return;         // already linked by another door
480         if (self.spawnflags & 4)
481         {
482                 self.owner = self.enemy = self;
483
484                 if (self.health)
485                         return;
486                 IFTARGETED
487                         return;
488                 if (self.items)
489                         return;
490                 self.trigger_field = spawn_field(self.absmin, self.absmax);
491
492                 return;         // don't want to link this door
493         }
494
495         FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
496
497         // set owner, and make a loop of the chain
498         dprint("LinkDoors: linking doors:");
499         for(t = self; ; t = t.enemy)
500         {
501                 dprint(" ", etos(t));
502                 t.owner = self;
503                 if(t.enemy == world)
504                 {
505                         t.enemy = self;
506                         break;
507                 }
508         }
509         dprint("\n");
510
511         // collect health, targetname, message, size
512         cmins = self.absmin;
513         cmaxs = self.absmax;
514         for(t = self; ; t = t.enemy)
515         {
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;
534                 if(t.enemy == self)
535                         break;
536         }
537
538         // distribute health, targetname, message
539         for(t = self; t; t = t.enemy)
540         {
541                 t.health = self.health;
542                 t.targetname = self.targetname;
543                 t.message = self.message;
544                 if(t.enemy == self)
545                         break;
546         }
547
548         // shootable, or triggered doors just needed the owner/enemy links,
549         // they don't spawn a field
550
551         if (self.health)
552                 return;
553         IFTARGETED
554                 return;
555         if (self.items)
556                 return;
557
558         self.trigger_field = spawn_field(cmins, cmaxs);
559 }
560
561
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.
564
565 TOGGLE causes the door to wait in both the start and end states for a trigger event.
566
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).
568
569 GOLD_KEY causes the door to open only if the activator holds a gold key.
570
571 SILVER_KEY causes the door to open only if the activator holds a silver key.
572
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)
581 "sounds"
582 0)      no sound
583 1)      stone
584 2)      base
585 3)      stone chain
586 4)      screechy metal
587 FIXME: only one sound set available at the time being
588
589 */
590
591 void door_init_startopen()
592 {
593         setorigin (self, self.pos2);
594         self.pos2 = self.pos1;
595         self.pos1 = self.origin;
596 }
597
598 void door_reset()
599 {
600         setorigin(self, self.pos1);
601         self.velocity = '0 0 0';
602         self.state = STATE_BOTTOM;
603         self.think = func_null;
604         self.nextthink = 0;
605 }
606
607 // spawnflags require key (for now only func_door)
608 void spawnfunc_func_door()
609 {
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);
615
616         SetMovedir ();
617
618         self.max_health = self.health;
619         if (!InitMovingBrushTrigger())
620                 return;
621         self.effects |= EF_LOWPRECISION;
622         self.classname = "door";
623
624         self.blocked = door_blocked;
625         self.use = door_use;
626
627     if(self.dmg && (self.message == ""))
628                 self.message = "was squished";
629     if(self.dmg && (self.message2 == ""))
630                 self.message2 = "was squished by";
631
632         if (self.sounds > 0)
633         {
634                 precache_sound ("plats/medplat1.wav");
635                 precache_sound ("plats/medplat2.wav");
636                 self.noise2 = "plats/medplat1.wav";
637                 self.noise1 = "plats/medplat2.wav";
638         }
639
640         if (!self.speed)
641                 self.speed = 100;
642         if (!self.wait)
643                 self.wait = 3;
644         if (!self.lip)
645                 self.lip = 8;
646
647         self.pos1 = self.origin;
648         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
649
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);
654
655         self.state = STATE_BOTTOM;
656
657         if (self.health)
658         {
659                 self.takedamage = DAMAGE_YES;
660                 self.event_damage = door_damage;
661         }
662
663         if (self.items)
664                 self.wait = -1;
665
666         self.touch = door_touch;
667
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);
671
672         self.reset = door_reset;
673 }