]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/func/door.qc
Don't use PLAT_CRUSH for doors (same as DOOR_DONT_LINK)
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / func / door.qc
1 #include "door.qh"
2 #include "door_rotating.qh"
3 /*
4
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
7 double/quad doors.
8
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.
11
12 Door.enemy chains from the master door through all doors linked in the chain.
13
14 */
15
16
17 /*
18 =============================================================================
19
20 THINK FUNCTIONS
21
22 =============================================================================
23 */
24
25 void door_go_down(entity this);
26 void door_go_up(entity this, entity actor, entity trigger);
27
28 void door_blocked(entity this, entity blocker)
29 {
30         if((this.spawnflags & DOOR_CRUSH)
31 #ifdef SVQC
32                 && (blocker.takedamage != DAMAGE_NO)
33 #elif defined(CSQC)
34                 && !IS_DEAD(blocker)
35 #endif
36         )
37         { // KIll Kill Kill!!
38 #ifdef SVQC
39                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
40 #endif
41         }
42         else
43         {
44 #ifdef SVQC
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');
47 #endif
48
49                  // don't change direction for dead or dying stuff
50                 if(IS_DEAD(blocker)
51 #ifdef SVQC
52                         && (blocker.takedamage == DAMAGE_NO)
53 #endif
54                 )
55                 {
56                         if (this.wait >= 0)
57                         {
58                                 if (this.state == STATE_DOWN)
59                         if (this.classname == "door")
60                         {
61                                 door_go_up (this, NULL, NULL);
62                         } else
63                         {
64                                 door_rotating_go_up(this, blocker);
65                         }
66                                 else
67                         if (this.classname == "door")
68                         {
69                                 door_go_down (this);
70                         } else
71                         {
72                                 door_rotating_go_down (this);
73                         }
74                         }
75                 }
76 #ifdef SVQC
77                 else
78                 {
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');
82                 }
83 #endif
84         }
85 }
86
87 void door_hit_top(entity this)
88 {
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")
95         {
96                 setthink(this, door_go_down);
97         } else
98         {
99                 setthink(this, door_rotating_go_down);
100         }
101         this.nextthink = this.ltime + this.wait;
102 }
103
104 void door_hit_bottom(entity this)
105 {
106         if (this.noise1 != "")
107                 _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM);
108         this.state = STATE_BOTTOM;
109 }
110
111 void door_go_down(entity this)
112 {
113         if (this.noise2 != "")
114                 _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM);
115         if (this.max_health)
116         {
117                 this.takedamage = DAMAGE_YES;
118                 this.health = this.max_health;
119         }
120
121         this.state = STATE_DOWN;
122         SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom);
123 }
124
125 void door_go_up(entity this, entity actor, entity trigger)
126 {
127         if (this.state == STATE_UP)
128                 return;         // already going up
129
130         if (this.state == STATE_TOP)
131         {       // reset top wait time
132                 this.nextthink = this.ltime + this.wait;
133                 return;
134         }
135
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);
140
141         string oldmessage;
142         oldmessage = this.message;
143         this.message = "";
144         SUB_UseTargets(this, actor, trigger);
145         this.message = oldmessage;
146 }
147
148
149 /*
150 =============================================================================
151
152 ACTIVATION FUNCTIONS
153
154 =============================================================================
155 */
156
157 bool door_check_keys(entity door, entity player)
158 {
159         if(door.owner)
160                 door = door.owner;
161
162         // no key needed
163         if(!door.itemkeys)
164                 return true;
165
166         // this door require a key
167         // only a player can have a key
168         if(!IS_PLAYER(player))
169                 return false;
170
171         entity store = player;
172 #ifdef SVQC
173         store = PS(player);
174 #endif
175         int valid = (door.itemkeys & store.itemkeys);
176         door.itemkeys &= ~valid; // only some of the needed keys were given
177
178         if(!door.itemkeys)
179         {
180 #ifdef SVQC
181                 play2(player, SND(TALK));
182                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED);
183 #endif
184                 return true;
185         }
186
187         if(!valid)
188         {
189 #ifdef SVQC
190                 if(player.key_door_messagetime <= time)
191                 {
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;
195                 }
196 #endif
197                 return false;
198         }
199
200         // door needs keys the player doesn't have
201 #ifdef SVQC
202         if(player.key_door_messagetime <= time)
203         {
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;
207         }
208 #endif
209
210         return false;
211 }
212
213 void door_fire(entity this, entity actor, entity trigger)
214 {
215         if (this.owner != this)
216                 objerror (this, "door_fire: this.owner != this");
217
218         if (this.spawnflags & DOOR_TOGGLE)
219         {
220                 if (this.state == STATE_UP || this.state == STATE_TOP)
221                 {
222                         entity e = this;
223                         do {
224                                 if (e.classname == "door") {
225                                         door_go_down(e);
226                                 } else {
227                                         door_rotating_go_down(e);
228                                 }
229                                 e = e.enemy;
230                         } while ((e != this) && (e != NULL));
231                         return;
232                 }
233         }
234
235 // trigger all paired doors
236         entity e = this;
237         do {
238                 if (e.classname == "door") {
239                         door_go_up(e, actor, trigger);
240                 } else {
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;
245                         }
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)))))
249                         {
250                                 door_rotating_go_up(e, trigger);
251                         }
252                 }
253                 e = e.enemy;
254         } while ((e != this) && (e != NULL));
255 }
256
257 void door_use(entity this, entity actor, entity trigger)
258 {
259         //dprint("door_use (model: ");dprint(this.model);dprint(")\n");
260
261         if (this.owner)
262                 door_fire(this.owner, actor, trigger);
263 }
264
265 void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
266 {
267         if(this.spawnflags & DOOR_NOSPLASH)
268                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
269                         return;
270         this.health = this.health - damage;
271
272         if (this.itemkeys)
273         {
274                 // don't allow opening doors through damage if keys are required
275                 return;
276         }
277
278         if (this.health <= 0)
279         {
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);
283         }
284 }
285
286 .float door_finished;
287
288 /*
289 ================
290 door_touch
291
292 Prints messages
293 ================
294 */
295
296 void door_touch(entity this, entity toucher)
297 {
298         if (!IS_PLAYER(toucher))
299                 return;
300         if (this.owner.door_finished > time)
301                 return;
302
303         this.owner.door_finished = time + 2;
304
305 #ifdef SVQC
306         if (!(this.owner.dmg) && (this.owner.message != ""))
307         {
308                 if (IS_CLIENT(toucher))
309                         centerprint(toucher, this.owner.message);
310                 play2(toucher, this.owner.noise);
311         }
312 #endif
313 }
314
315 void door_generic_plat_blocked(entity this, entity blocker)
316 {
317         if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!!
318 #ifdef SVQC
319                 Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0');
320 #endif
321         }
322         else
323         {
324
325 #ifdef SVQC
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');
328 #endif
329
330                  //Dont chamge direction for dead or dying stuff
331                 if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO))
332                 {
333                         if (this.wait >= 0)
334                         {
335                                 if (this.state == STATE_DOWN)
336                                         door_rotating_go_up (this, blocker);
337                                 else
338                                         door_rotating_go_down (this);
339                         }
340                 }
341 #ifdef SVQC
342                 else
343                 {
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');
347                 }
348 #endif
349         }
350 }
351
352 /*
353 =========================================
354 door trigger
355
356 Spawned if a door lacks a real activator
357 =========================================
358 */
359
360 void door_trigger_touch(entity this, entity toucher)
361 {
362         if (toucher.health < 1)
363 #ifdef SVQC
364                 if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
365 #elif defined(CSQC)
366                 if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher)))
367 #endif
368                         return;
369
370         if (time < this.door_finished)
371                 return;
372
373         // check if door is locked
374         if (!door_check_keys(this, toucher))
375                 return;
376
377         this.door_finished = time + 1;
378
379         door_use(this.owner, toucher, NULL);
380 }
381
382 void door_spawnfield(entity this, vector fmins, vector fmaxs)
383 {
384         entity  trigger;
385         vector  t1 = fmins, t2 = fmaxs;
386
387         trigger = new(doortriggerfield);
388         set_movetype(trigger, MOVETYPE_NONE);
389         trigger.solid = SOLID_TRIGGER;
390         trigger.owner = this;
391 #ifdef SVQC
392         settouch(trigger, door_trigger_touch);
393 #endif
394
395         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
396 }
397
398
399 /*
400 =============
401 LinkDoors
402
403
404 =============
405 */
406
407 entity LinkDoors_nextent(entity cur, entity near, entity pass)
408 {
409         while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy))
410         {
411         }
412         return cur;
413 }
414
415 bool LinkDoors_isconnected(entity e1, entity e2, entity pass)
416 {
417         float DELTA = 4;
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)
424         ) { return false; }
425         return true;
426 }
427
428 #ifdef SVQC
429 void door_link();
430 #endif
431 void LinkDoors(entity this)
432 {
433         entity  t;
434         vector  cmins, cmaxs;
435
436 #ifdef SVQC
437         door_link();
438 #endif
439
440         if (this.enemy)
441                 return;         // already linked by another door
442         if (this.spawnflags & DOOR_DONT_LINK)
443         {
444                 this.owner = this.enemy = this;
445
446                 if (this.health)
447                         return;
448                 IFTARGETED
449                         return;
450                 if (this.items)
451                         return;
452
453                 door_spawnfield(this, this.absmin, this.absmax);
454
455                 return;         // don't want to link this door
456         }
457
458         FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this);
459
460         // set owner, and make a loop of the chain
461         LOG_TRACE("LinkDoors: linking doors:");
462         for(t = this; ; t = t.enemy)
463         {
464                 LOG_TRACE(" ", etos(t));
465                 t.owner = this;
466                 if(t.enemy == NULL)
467                 {
468                         t.enemy = this;
469                         break;
470                 }
471         }
472         LOG_TRACE("");
473
474         // collect health, targetname, message, size
475         cmins = this.absmin;
476         cmaxs = this.absmax;
477         for(t = this; ; t = t.enemy)
478         {
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;
497                 if(t.enemy == this)
498                         break;
499         }
500
501         // distribute health, targetname, message
502         for(t = this; t; t = t.enemy)
503         {
504                 t.health = this.health;
505                 t.targetname = this.targetname;
506                 t.message = this.message;
507                 if(t.enemy == this)
508                         break;
509         }
510
511         // shootable, or triggered doors just needed the owner/enemy links,
512         // they don't spawn a field
513
514         if (this.health)
515                 return;
516         IFTARGETED
517                 return;
518         if (this.items)
519                 return;
520
521         door_spawnfield(this, cmins, cmaxs);
522 }
523
524 REGISTER_NET_LINKED(ENT_CLIENT_DOOR)
525
526 #ifdef SVQC
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.
529
530 TOGGLE causes the door to wait in both the start and end states for a trigger event.
531
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).
533
534 GOLD_KEY causes the door to open only if the activator holds a gold key.
535
536 SILVER_KEY causes the door to open only if the activator holds a silver key.
537
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)
546 "sounds"
547 0)      no sound
548 1)      stone
549 2)      base
550 3)      stone chain
551 4)      screechy metal
552 FIXME: only one sound set available at the time being
553
554 */
555
556 float door_send(entity this, entity to, float sf)
557 {
558         WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR);
559         WriteByte(MSG_ENTITY, sf);
560
561         if(sf & SF_TRIGGER_INIT)
562         {
563                 WriteString(MSG_ENTITY, this.classname);
564                 WriteByte(MSG_ENTITY, this.spawnflags);
565
566                 WriteString(MSG_ENTITY, this.model);
567
568                 trigger_common_write(this, true);
569
570                 WriteVector(MSG_ENTITY, this.pos1);
571                 WriteVector(MSG_ENTITY, this.pos2);
572
573                 WriteVector(MSG_ENTITY, this.size);
574
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);
580         }
581
582         if(sf & SF_TRIGGER_RESET)
583         {
584                 // client makes use of this, we do not
585         }
586
587         if(sf & SF_TRIGGER_UPDATE)
588         {
589                 WriteVector(MSG_ENTITY, this.origin);
590
591                 WriteVector(MSG_ENTITY, this.pos1);
592                 WriteVector(MSG_ENTITY, this.pos2);
593         }
594
595         return true;
596 }
597
598 void door_link()
599 {
600         // set size now, as everything is loaded
601         //FixSize(this);
602         //Net_LinkEntity(this, false, 0, door_send);
603 }
604 #endif
605
606 void door_init_startopen(entity this)
607 {
608         setorigin(this, this.pos2);
609         this.pos2 = this.pos1;
610         this.pos1 = this.origin;
611
612 #ifdef SVQC
613         this.SendFlags |= SF_TRIGGER_UPDATE;
614 #endif
615 }
616
617 void door_reset(entity this)
618 {
619         setorigin(this, this.pos1);
620         this.velocity = '0 0 0';
621         this.state = STATE_BOTTOM;
622         setthink(this, func_null);
623         this.nextthink = 0;
624
625 #ifdef SVQC
626         this.SendFlags |= SF_TRIGGER_RESET;
627 #endif
628 }
629
630 #ifdef SVQC
631
632 // spawnflags require key (for now only func_door)
633 spawnfunc(func_door)
634 {
635         // Quake 1 keys compatibility
636         if (this.spawnflags & SPAWNFLAGS_GOLD_KEY)
637                 this.itemkeys |= ITEM_KEY_BIT(0);
638         if (this.spawnflags & SPAWNFLAGS_SILVER_KEY)
639                 this.itemkeys |= ITEM_KEY_BIT(1);
640
641         SetMovedir(this);
642
643         this.max_health = this.health;
644         if (!InitMovingBrushTrigger(this))
645                 return;
646         this.effects |= EF_LOWPRECISION;
647         this.classname = "door";
648
649         if(this.noise == "")
650                 this.noise = "misc/talk.wav";
651         if(this.noise3 == "")
652                 this.noise3 = "misc/talk.wav";
653         precache_sound(this.noise);
654         precache_sound(this.noise3);
655
656         setblocked(this, door_blocked);
657         this.use = door_use;
658
659         if(this.dmg && (this.message == ""))
660                 this.message = "was squished";
661         if(this.dmg && (this.message2 == ""))
662                 this.message2 = "was squished by";
663
664         if (this.sounds > 0)
665         {
666                 this.noise2 = "plats/medplat1.wav";
667                 this.noise1 = "plats/medplat2.wav";
668         }
669
670         if(this.noise1 && this.noise1 != "") { precache_sound(this.noise1); }
671         if(this.noise2 && this.noise2 != "") { precache_sound(this.noise2); }
672
673         if (!this.speed)
674                 this.speed = 100;
675         if (!this.wait)
676                 this.wait = 3;
677         if (!this.lip)
678                 this.lip = 8;
679
680         this.pos1 = this.origin;
681         this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip);
682
683         if(this.spawnflags & DOOR_NONSOLID)
684                 this.solid = SOLID_NOT;
685
686 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
687 // but spawn in the open position
688         if (this.spawnflags & DOOR_START_OPEN)
689                 InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION);
690
691         this.state = STATE_BOTTOM;
692
693         if (this.health)
694         {
695                 //this.canteamdamage = true; // TODO
696                 this.takedamage = DAMAGE_YES;
697                 this.event_damage = door_damage;
698         }
699
700         if (this.items)
701                 this.wait = -1;
702
703         settouch(this, door_touch);
704
705 // LinkDoors can't be done until all of the doors have been spawned, so
706 // the sizes can be detected properly.
707         InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS);
708
709         this.reset = door_reset;
710 }
711
712 #elif defined(CSQC)
713
714 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
715 {
716         int sf = ReadByte();
717
718         if(sf & SF_TRIGGER_INIT)
719         {
720                 this.classname = strzone(ReadString());
721                 this.spawnflags = ReadByte();
722
723                 this.mdl = strzone(ReadString());
724                 _setmodel(this, this.mdl);
725
726                 trigger_common_read(this, true);
727
728                 this.pos1 = ReadVector();
729                 this.pos2 = ReadVector();
730
731                 this.size = ReadVector();
732
733                 this.wait = ReadShort();
734                 this.speed = ReadShort();
735                 this.lip = ReadByte();
736                 this.state = ReadByte();
737                 this.ltime = ReadCoord();
738
739                 this.solid = SOLID_BSP;
740                 set_movetype(this, MOVETYPE_PUSH);
741                 this.use = door_use;
742
743                 LinkDoors(this);
744
745                 if(this.spawnflags & DOOR_START_OPEN)
746                         door_init_startopen(this);
747
748                 this.move_time = time;
749                 set_movetype(this, MOVETYPE_PUSH);
750         }
751
752         if(sf & SF_TRIGGER_RESET)
753         {
754                 door_reset(this);
755         }
756
757         if(sf & SF_TRIGGER_UPDATE)
758         {
759                 this.origin = ReadVector();
760                 setorigin(this, this.origin);
761
762                 this.pos1 = ReadVector();
763                 this.pos2 = ReadVector();
764         }
765         return true;
766 }
767
768 #endif