]> icculus.org git repositories - divverent/nexuiz.git/blob - qcsrc/gamec/g_triggers.c
make trigger_hurt able to damage multiple things in one frame
[divverent/nexuiz.git] / qcsrc / gamec / g_triggers.c
1
2 void() SUB_UseTargets;
3
4 void() DelayThink =
5 {
6         activator = self.enemy;
7         SUB_UseTargets ();
8         remove(self);
9 };
10
11 /*
12 ==============================
13 SUB_UseTargets
14
15 the global "activator" should be set to the entity that initiated the firing.
16
17 If self.delay is set, a DelayedUse entity will be created that will actually
18 do the SUB_UseTargets after that many seconds have passed.
19
20 Centerprints any self.message to the activator.
21
22 Removes all entities with a targetname that match self.killtarget,
23 and removes them, so some events can remove other triggers.
24
25 Search for (string)targetname in all entities that
26 match (string)self.target and call their .use function
27
28 ==============================
29 */
30 void() SUB_UseTargets =
31 {
32         local entity t, stemp, otemp, act;
33
34 //
35 // check for a delay
36 //
37         if (self.delay)
38         {
39         // create a temp object to fire at a later time
40                 t = spawn();
41                 t.classname = "DelayedUse";
42                 t.nextthink = time + self.delay;
43                 t.think = DelayThink;
44                 t.enemy = activator;
45                 t.message = self.message;
46                 t.killtarget = self.killtarget;
47                 t.target = self.target;
48                 return;
49         }
50
51
52 //
53 // print the message
54 //
55         if (activator.classname == "player" && self.message != "")
56         {
57                 centerprint (activator, self.message);
58                 if (!self.noise)
59                         sound (activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
60         }
61
62 //
63 // kill the killtagets
64 //
65         if (self.killtarget)
66         {
67                 t = world;
68                 do
69                 {
70                         t = find (t, targetname, self.killtarget);
71                         if (!t)
72                                 return;
73                         remove (t);
74                 } while ( 1 );
75         }
76
77 //
78 // fire targets
79 //
80         if (self.target)
81         {
82                 act = activator;
83                 t = world;
84                 do
85                 {
86                         t = find (t, targetname, self.target);
87                         if (!t)
88                         {
89                                 return;
90                         }
91                         stemp = self;
92                         otemp = other;
93                         self = t;
94                         other = stemp;
95                         if (self.use)
96                                 self.use ();
97                         self = stemp;
98                         other = otemp;
99                         activator = act;
100                 } while ( 1 );
101         }
102
103
104 };
105
106 entity stemp, otemp, s, old;
107
108
109 void() trigger_reactivate =
110 {
111         self.solid = SOLID_TRIGGER;
112 };
113
114 //=============================================================================
115
116 float   SPAWNFLAG_NOMESSAGE = 1;
117 float   SPAWNFLAG_NOTOUCH = 1;
118
119 // the wait time has passed, so set back up for another activation
120 void() multi_wait =
121 {
122         if (self.max_health)
123         {
124                 self.health = self.max_health;
125                 self.takedamage = DAMAGE_YES;
126                 self.solid = SOLID_BBOX;
127         }
128 };
129
130
131 // the trigger was just touched/killed/used
132 // self.enemy should be set to the activator so it can be held through a delay
133 // so wait for the delay time before firing
134 void() multi_trigger =
135 {
136         if (self.nextthink > time)
137         {
138                 return;         // allready been triggered
139         }
140
141         if (self.classname == "trigger_secret")
142         {
143                 if (self.enemy.classname != "player")
144                         return;
145                 found_secrets = found_secrets + 1;
146                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
147         }
148
149         if (self.noise)
150                 sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
151
152 // don't trigger again until reset
153         self.takedamage = DAMAGE_NO;
154
155         activator = self.enemy;
156
157         SUB_UseTargets();
158
159         if (self.wait > 0)
160         {
161                 self.think = multi_wait;
162                 self.nextthink = time + self.wait;
163         }
164         else
165         {       // we can't just remove (self) here, because this is a touch function
166                 // called wheil C code is looping through area links...
167                 self.touch = nullfunction;
168
169                 self.nextthink = time + 0.1;
170                 self.think = SUB_Remove;
171         }
172 };
173
174 void() multi_use =
175 {
176         self.enemy = activator;
177         multi_trigger();
178 };
179
180 void() multi_touch =
181 {
182         if (other.classname != "player")
183                 return;
184
185 // if the trigger has an angles field, check player's facing direction
186         if (self.movedir != '0 0 0')
187         {
188                 makevectors (other.angles);
189                 if (v_forward * self.movedir < 0)
190                         return;         // not facing the right way
191         }
192
193         self.enemy = other;
194         multi_trigger ();
195 };
196
197 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
198 {
199         if (!self.takedamage)
200                 return;
201         self.health = self.health - damage;
202         if (self.health <= 0)
203         {
204                 self.enemy = attacker;
205                 multi_trigger();
206         }
207 }
208
209 /*QUAKED trigger_multiple (.5 .5 .5) ? notouch
210 Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
211 If "delay" is set, the trigger waits some time after activating before firing.
212 "wait" : Seconds between triggerings. (.2 default)
213 If notouch is set, the trigger is only fired by other entities, not by touching.
214 NOTOUCH has been obsoleted by trigger_relay!
215 sounds
216 1)      secret
217 2)      beep beep
218 3)      large switch
219 4)
220 set "message" to text string
221 */
222 void() trigger_multiple =
223 {
224         if (self.sounds == 1)
225         {
226                 precache_sound ("misc/secret.wav");
227                 self.noise = "misc/secret.wav";
228         }
229         else if (self.sounds == 2)
230         {
231                 precache_sound ("misc/talk.wav");
232                 self.noise = "misc/talk.wav";
233         }
234         else if (self.sounds == 3)
235         {
236                 precache_sound ("misc/trigger1.wav");
237                 self.noise = "misc/trigger1.wav";
238         }
239
240         if (!self.wait)
241                 self.wait = 0.2;
242         self.use = multi_use;
243
244         InitTrigger ();
245
246         if (self.health)
247         {
248                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
249                         objerror ("health and notouch don't make sense\n");
250                 self.max_health = self.health;
251                 self.event_damage = multi_eventdamage;
252                 self.takedamage = DAMAGE_YES;
253                 self.solid = SOLID_BBOX;
254                 setorigin (self, self.origin);  // make sure it links into the world
255         }
256         else
257         {
258                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
259                 {
260                         self.touch = multi_touch;
261                 }
262         }
263 };
264
265
266 /*QUAKED trigger_once (.5 .5 .5) ? notouch
267 Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
268 "targetname".  If "health" is set, the trigger must be killed to activate.
269 If notouch is set, the trigger is only fired by other entities, not by touching.
270 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
271 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
272 sounds
273 1)      secret
274 2)      beep beep
275 3)      large switch
276 4)
277 set "message" to text string
278 */
279 void() trigger_once =
280 {
281         self.wait = -1;
282         trigger_multiple();
283 };
284
285 //=============================================================================
286
287 /*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
288 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
289 */
290 void() trigger_relay =
291 {
292         self.use = SUB_UseTargets;
293 };
294
295
296 //=============================================================================
297
298
299 void() counter_use =
300 {
301         local string junk;
302
303         self.count = self.count - 1;
304         if (self.count < 0)
305                 return;
306
307         if (self.count != 0)
308         {
309                 if (activator.classname == "player"
310                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
311                 {
312                         if (self.count >= 4)
313                                 centerprint (activator, "There are more to go...");
314                         else if (self.count == 3)
315                                 centerprint (activator, "Only 3 more to go...");
316                         else if (self.count == 2)
317                                 centerprint (activator, "Only 2 more to go...");
318                         else
319                                 centerprint (activator, "Only 1 more to go...");
320                 }
321                 return;
322         }
323
324         if (activator.classname == "player"
325         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
326                 centerprint(activator, "Sequence completed!");
327         self.enemy = activator;
328         multi_trigger ();
329 };
330
331 /*QUAKED trigger_counter (.5 .5 .5) ? nomessage
332 Acts as an intermediary for an action that takes multiple inputs.
333
334 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
335
336 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
337 */
338 void() trigger_counter =
339 {
340         self.wait = -1;
341         if (!self.count)
342                 self.count = 2;
343
344         self.use = counter_use;
345 };
346
347 void() hurt_on =
348 {
349         self.solid = SOLID_TRIGGER;
350         self.nextthink = -1;
351 };
352
353 void() hurt_off =
354 {
355         self.solid = SOLID_NOT;
356         self.think = hurt_on;
357         self.nextthink = time + 1;
358 };
359
360 void() hurt_touch =
361 {
362         if (other.takedamage)
363         {
364                 self.think = hurt_off;
365                 self.nextthink = time;
366                 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, '0 0 0', '0 0 0');
367         }
368
369         return;
370 };
371
372 /*QUAKED trigger_hurt (.5 .5 .5) ?
373 Any object touching this will be hurt
374 set dmg to damage amount
375 defalt dmg = 5
376 */
377 void() trigger_hurt =
378 {
379         InitTrigger ();
380         self.touch = hurt_touch;
381         if (!self.dmg)
382                 self.dmg = 1000;
383         if (!self.message)
384                 self.message = "was in the wrong place.";
385 };
386
387 //void() target_speaker_use = {sound(self, CHAN_VOICE, self.noise, 1, 1);}
388 //void() target_speaker = {self.use = target_speaker_use;}
389
390 void() target_speaker =
391 {
392 if(self.noise) {
393  precache_sound (self.noise);
394  ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
395 }
396 //remove(self);
397 };
398
399
400
401 void() sparksthink =
402 {
403   self.nextthink = time + 0.1;
404
405   if(random() < self.wait) {
406     te_spark(self.origin,'0 0 -1',self.cnt);
407   }
408 }
409
410
411 void() func_sparks =
412 {
413   self.think = sparksthink;
414   self.nextthink = time + 0.2;
415
416   // self.cnt is the amount of sparks that one burst will spawn
417   if(self.cnt < 1) {
418     self.cnt = 25.0; // nice default value
419   }
420
421   // self.wait is the probability that a sparkthink will spawn a spark shower
422   // range: 0 - 1, but 0 makes little sense, so...
423   if(self.wait < 0.05) {
424     self.wait = 0.25; // nice default value
425   }
426
427   // sound
428   if(self.noise) {
429     precache_sound (self.noise);
430     ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
431   }
432 }
433
434
435
436 /*
437 =============================================================================
438
439 SECRET DOORS
440
441 =============================================================================
442 */
443
444 void() fd_secret_move1;
445 void() fd_secret_move2;
446 void() fd_secret_move3;
447 void() fd_secret_move4;
448 void() fd_secret_move5;
449 void() fd_secret_move6;
450 void() fd_secret_done;
451
452 float SECRET_OPEN_ONCE = 1;             // stays open
453 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
454 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
455 float SECRET_NO_SHOOT = 8;              // only opened by trigger
456 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
457
458
459 void () fd_secret_use =
460 {
461         local float temp;
462
463         self.health = 10000;
464         //self.havocattack = TRUE;
465
466         // exit if still moving around...
467         if (self.origin != self.oldorigin)
468                 return;
469
470         self.message = ""; // no more message
471
472         SUB_UseTargets();                               // fire all targets / killtargets
473
474         self.velocity = '0 0 0';
475
476         // Make a sound, wait a little...
477
478         if (self.noise1 != "")
479                 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
480         self.nextthink = self.ltime + 0.1;
481
482         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
483         makevectors(self.mangle);
484
485         if (!self.t_width)
486         {
487                 if (self.spawnflags & SECRET_1ST_DOWN)
488                         self.t_width = fabs(v_up * self.size);
489                 else
490                         self.t_width = fabs(v_right * self.size);
491         }
492
493         if (!self.t_length)
494                 self.t_length = fabs(v_forward * self.size);
495
496         if (self.spawnflags & SECRET_1ST_DOWN)
497                 self.dest1 = self.origin - v_up * self.t_width;
498         else
499                 self.dest1 = self.origin + v_right * (self.t_width * temp);
500
501         self.dest2 = self.dest1 + v_forward * self.t_length;
502         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
503         if (self.noise2 != "")
504                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
505 };
506
507 // Wait after first movement...
508 void () fd_secret_move1 =
509 {
510         self.nextthink = self.ltime + 1.0;
511         self.think = fd_secret_move2;
512         if (self.noise3 != "")
513                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
514 };
515
516 // Start moving sideways w/sound...
517 void () fd_secret_move2 =
518 {
519         if (self.noise2 != "")
520                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
521         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
522 };
523
524 // Wait here until time to go back...
525 void () fd_secret_move3 =
526 {
527         if (self.noise3 != "")
528                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
529         if (!(self.spawnflags & SECRET_OPEN_ONCE))
530         {
531                 self.nextthink = self.ltime + self.wait;
532                 self.think = fd_secret_move4;
533         }
534 };
535
536 // Move backward...
537 void () fd_secret_move4 =
538 {
539         if (self.noise2 != "")
540                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
541         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
542 };
543
544 // Wait 1 second...
545 void () fd_secret_move5 =
546 {
547         self.nextthink = self.ltime + 1.0;
548         self.think = fd_secret_move6;
549         if (self.noise3 != "")
550                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
551 };
552
553 void () fd_secret_move6 =
554 {
555         if (self.noise2 != "")
556                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
557         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
558 };
559
560 void () fd_secret_done =
561 {
562         if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
563         {
564                 self.health = 10000;
565                 self.takedamage = DAMAGE_YES;
566                 //self.th_pain = fd_secret_use;
567         }
568         if (self.noise3 != "")
569                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
570 };
571
572 void () secret_blocked =
573 {
574         if (time < self.attack_finished)
575                 return;
576         self.attack_finished = time + 0.5;
577         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
578 };
579
580 /*
581 ==============
582 secret_touch
583
584 Prints messages
585 ================
586 */
587 void() secret_touch =
588 {
589         if (activator.classname != "player")
590                 return;
591         if (self.attack_finished > time)
592                 return;
593
594         self.attack_finished = time + 2;
595
596         if (self.message)
597         {
598                 if (other.flags & FL_CLIENT)
599                         centerprint (other, self.message);
600                 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
601         }
602 };
603
604
605 /*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
606 Basic secret door. Slides back, then to the side. Angle determines direction.
607 wait  = # of seconds before coming back
608 1st_left = 1st move is left of arrow
609 1st_down = 1st move is down from arrow
610 always_shoot = even if targeted, keep shootable
611 t_width = override WIDTH to move back (or height if going down)
612 t_length = override LENGTH to move sideways
613 "dmg"           damage to inflict when blocked (2 default)
614
615 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
616 "sounds"
617 1) medieval
618 2) metal
619 3) base
620 */
621
622 void () func_door_secret =
623 {
624         /*if (!self.deathtype) // map makers can override this
625                 self.deathtype = " got in the way";*/
626
627         if (!self.dmg)
628                 self.dmg = 2;
629
630         // Magic formula...
631         self.mangle = self.angles;
632         self.angles = '0 0 0';
633         self.solid = SOLID_BSP;
634         self.movetype = MOVETYPE_PUSH;
635         self.classname = "door";
636         setmodel (self, self.model);
637         setorigin (self, self.origin);
638
639         self.touch = secret_touch;
640         self.blocked = secret_blocked;
641         self.speed = 50;
642         self.use = fd_secret_use;
643         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
644         {
645                 self.health = 10000;
646                 self.takedamage = DAMAGE_YES;
647                 self.event_damage = fd_secret_use;
648         }
649         self.oldorigin = self.origin;
650         if (!self.wait)
651                 self.wait = 5;          // 5 seconds before closing
652 };
653