]> icculus.org git repositories - divverent/nexuiz.git/blob - qcsrc/gamec/g_triggers.c
cleaned up effects handling in powerup code, so that powerups won't mess up eachother...
[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 .float triggerhurttime;
348 void() hurt_touch =
349 {
350         if (other.takedamage)
351         if (other.triggerhurttime < time)
352         {
353                 other.triggerhurttime = time + 1;
354                 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
355         }
356
357         return;
358 };
359
360 /*QUAKED trigger_hurt (.5 .5 .5) ?
361 Any object touching this will be hurt
362 set dmg to damage amount
363 defalt dmg = 5
364 */
365 void() trigger_hurt =
366 {
367         InitTrigger ();
368         self.touch = hurt_touch;
369         if (!self.dmg)
370                 self.dmg = 1000;
371         if (!self.message)
372                 self.message = "was in the wrong place.";
373 };
374
375 //void() target_speaker_use = {sound(self, CHAN_VOICE, self.noise, 1, 1);}
376 //void() target_speaker = {self.use = target_speaker_use;}
377
378 void() target_speaker =
379 {
380 if(self.noise) {
381  precache_sound (self.noise);
382  ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
383 }
384 //remove(self);
385 };
386
387
388
389 void() sparksthink =
390 {
391   self.nextthink = time + 0.1;
392
393   if(random() < self.wait) {
394     te_spark(self.origin,'0 0 -1',self.cnt);
395   }
396 }
397
398
399 void() func_sparks =
400 {
401   self.think = sparksthink;
402   self.nextthink = time + 0.2;
403
404   // self.cnt is the amount of sparks that one burst will spawn
405   if(self.cnt < 1) {
406     self.cnt = 25.0; // nice default value
407   }
408
409   // self.wait is the probability that a sparkthink will spawn a spark shower
410   // range: 0 - 1, but 0 makes little sense, so...
411   if(self.wait < 0.05) {
412     self.wait = 0.25; // nice default value
413   }
414
415   // sound
416   if(self.noise) {
417     precache_sound (self.noise);
418     ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
419   }
420 }
421
422
423
424 /*
425 =============================================================================
426
427 SECRET DOORS
428
429 =============================================================================
430 */
431
432 void() fd_secret_move1;
433 void() fd_secret_move2;
434 void() fd_secret_move3;
435 void() fd_secret_move4;
436 void() fd_secret_move5;
437 void() fd_secret_move6;
438 void() fd_secret_done;
439
440 float SECRET_OPEN_ONCE = 1;             // stays open
441 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
442 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
443 float SECRET_NO_SHOOT = 8;              // only opened by trigger
444 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
445
446
447 void () fd_secret_use =
448 {
449         local float temp;
450
451         self.health = 10000;
452         //self.havocattack = TRUE;
453
454         // exit if still moving around...
455         if (self.origin != self.oldorigin)
456                 return;
457
458         self.message = ""; // no more message
459
460         SUB_UseTargets();                               // fire all targets / killtargets
461
462         self.velocity = '0 0 0';
463
464         // Make a sound, wait a little...
465
466         if (self.noise1 != "")
467                 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
468         self.nextthink = self.ltime + 0.1;
469
470         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
471         makevectors(self.mangle);
472
473         if (!self.t_width)
474         {
475                 if (self.spawnflags & SECRET_1ST_DOWN)
476                         self.t_width = fabs(v_up * self.size);
477                 else
478                         self.t_width = fabs(v_right * self.size);
479         }
480
481         if (!self.t_length)
482                 self.t_length = fabs(v_forward * self.size);
483
484         if (self.spawnflags & SECRET_1ST_DOWN)
485                 self.dest1 = self.origin - v_up * self.t_width;
486         else
487                 self.dest1 = self.origin + v_right * (self.t_width * temp);
488
489         self.dest2 = self.dest1 + v_forward * self.t_length;
490         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
491         if (self.noise2 != "")
492                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
493 };
494
495 // Wait after first movement...
496 void () fd_secret_move1 =
497 {
498         self.nextthink = self.ltime + 1.0;
499         self.think = fd_secret_move2;
500         if (self.noise3 != "")
501                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
502 };
503
504 // Start moving sideways w/sound...
505 void () fd_secret_move2 =
506 {
507         if (self.noise2 != "")
508                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
509         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
510 };
511
512 // Wait here until time to go back...
513 void () fd_secret_move3 =
514 {
515         if (self.noise3 != "")
516                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
517         if (!(self.spawnflags & SECRET_OPEN_ONCE))
518         {
519                 self.nextthink = self.ltime + self.wait;
520                 self.think = fd_secret_move4;
521         }
522 };
523
524 // Move backward...
525 void () fd_secret_move4 =
526 {
527         if (self.noise2 != "")
528                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
529         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
530 };
531
532 // Wait 1 second...
533 void () fd_secret_move5 =
534 {
535         self.nextthink = self.ltime + 1.0;
536         self.think = fd_secret_move6;
537         if (self.noise3 != "")
538                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
539 };
540
541 void () fd_secret_move6 =
542 {
543         if (self.noise2 != "")
544                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
545         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
546 };
547
548 void () fd_secret_done =
549 {
550         if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
551         {
552                 self.health = 10000;
553                 self.takedamage = DAMAGE_YES;
554                 //self.th_pain = fd_secret_use;
555         }
556         if (self.noise3 != "")
557                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
558 };
559
560 void () secret_blocked =
561 {
562         if (time < self.attack_finished)
563                 return;
564         self.attack_finished = time + 0.5;
565         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
566 };
567
568 /*
569 ==============
570 secret_touch
571
572 Prints messages
573 ================
574 */
575 void() secret_touch =
576 {
577         if (activator.classname != "player")
578                 return;
579         if (self.attack_finished > time)
580                 return;
581
582         self.attack_finished = time + 2;
583
584         if (self.message)
585         {
586                 if (other.flags & FL_CLIENT)
587                         centerprint (other, self.message);
588                 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
589         }
590 };
591
592
593 /*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
594 Basic secret door. Slides back, then to the side. Angle determines direction.
595 wait  = # of seconds before coming back
596 1st_left = 1st move is left of arrow
597 1st_down = 1st move is down from arrow
598 always_shoot = even if targeted, keep shootable
599 t_width = override WIDTH to move back (or height if going down)
600 t_length = override LENGTH to move sideways
601 "dmg"           damage to inflict when blocked (2 default)
602
603 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
604 "sounds"
605 1) medieval
606 2) metal
607 3) base
608 */
609
610 void () func_door_secret =
611 {
612         /*if (!self.deathtype) // map makers can override this
613                 self.deathtype = " got in the way";*/
614
615         if (!self.dmg)
616                 self.dmg = 2;
617
618         // Magic formula...
619         self.mangle = self.angles;
620         self.angles = '0 0 0';
621         self.solid = SOLID_BSP;
622         self.movetype = MOVETYPE_PUSH;
623         self.classname = "door";
624         setmodel (self, self.model);
625         setorigin (self, self.origin);
626
627         self.touch = secret_touch;
628         self.blocked = secret_blocked;
629         self.speed = 50;
630         self.use = fd_secret_use;
631         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
632         {
633                 self.health = 10000;
634                 self.takedamage = DAMAGE_YES;
635                 self.event_damage = fd_secret_use;
636         }
637         self.oldorigin = self.origin;
638         if (!self.wait)
639                 self.wait = 5;          // 5 seconds before closing
640 };
641