]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
change all spawn functions to use the spawnfunc_ prefix; sync dpextensions
[divverent/nexuiz.git] / data / qcsrc / server / g_triggers.qc
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
107
108 void() spawnfunc_trigger_reactivate =
109 {
110         self.solid = SOLID_TRIGGER;
111 };
112
113 //=============================================================================
114
115 float   SPAWNFLAG_NOMESSAGE = 1;
116 float   SPAWNFLAG_NOTOUCH = 1;
117
118 // the wait time has passed, so set back up for another activation
119 void() multi_wait =
120 {
121         if (self.max_health)
122         {
123                 self.health = self.max_health;
124                 self.takedamage = DAMAGE_YES;
125                 self.solid = SOLID_BBOX;
126         }
127 };
128
129
130 // the trigger was just touched/killed/used
131 // self.enemy should be set to the activator so it can be held through a delay
132 // so wait for the delay time before firing
133 void() multi_trigger =
134 {
135         if (self.nextthink > time)
136         {
137                 return;         // allready been triggered
138         }
139
140         if (self.classname == "trigger_secret")
141         {
142                 if (self.enemy.classname != "player")
143                         return;
144                 found_secrets = found_secrets + 1;
145                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
146         }
147
148         if (self.noise)
149                 sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
150
151 // don't trigger again until reset
152         self.takedamage = DAMAGE_NO;
153
154         activator = self.enemy;
155
156         SUB_UseTargets();
157
158         if (self.wait > 0)
159         {
160                 self.think = multi_wait;
161                 self.nextthink = time + self.wait;
162         }
163         else
164         {       // we can't just remove (self) here, because this is a touch function
165                 // called wheil C code is looping through area links...
166                 self.touch = SUB_Null;
167
168                 self.nextthink = time + 0.1;
169                 self.think = SUB_Remove;
170         }
171 };
172
173 void() multi_use =
174 {
175         self.enemy = activator;
176         multi_trigger();
177 };
178
179 void() multi_touch =
180 {
181         if (other.classname != "player")
182                 return;
183
184     if(self.team)
185     if(self.team == other.team)
186         return;
187
188 // if the trigger has an angles field, check player's facing direction
189         if (self.movedir != '0 0 0')
190         {
191                 makevectors (other.angles);
192                 if (v_forward * self.movedir < 0)
193                         return;         // not facing the right way
194         }
195
196         self.enemy = other;
197         multi_trigger ();
198 };
199
200 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
201 {
202         if (!self.takedamage)
203                 return;
204         self.health = self.health - damage;
205         if (self.health <= 0)
206         {
207                 self.enemy = attacker;
208                 multi_trigger();
209         }
210 }
211
212 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
213 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.
214 If "delay" is set, the trigger waits some time after activating before firing.
215 "wait" : Seconds between triggerings. (.2 default)
216 If notouch is set, the trigger is only fired by other entities, not by touching.
217 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
218 sounds
219 1)      secret
220 2)      beep beep
221 3)      large switch
222 4)
223 set "message" to text string
224 */
225 void() spawnfunc_trigger_multiple =
226 {
227         if (self.sounds == 1)
228         {
229                 precache_sound ("misc/secret.wav");
230                 self.noise = "misc/secret.wav";
231         }
232         else if (self.sounds == 2)
233         {
234                 precache_sound ("misc/talk.wav");
235                 self.noise = "misc/talk.wav";
236         }
237         else if (self.sounds == 3)
238         {
239                 precache_sound ("misc/trigger1.wav");
240                 self.noise = "misc/trigger1.wav";
241         }
242
243         if (!self.wait)
244                 self.wait = 0.2;
245         self.use = multi_use;
246
247         InitTrigger ();
248
249         if (self.health)
250         {
251                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
252                         objerror ("health and notouch don't make sense\n");
253                 self.max_health = self.health;
254                 self.event_damage = multi_eventdamage;
255                 self.takedamage = DAMAGE_YES;
256                 self.solid = SOLID_BBOX;
257                 setorigin (self, self.origin);  // make sure it links into the world
258         }
259         else
260         {
261                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
262                 {
263                         self.touch = multi_touch;
264                 }
265         }
266 };
267
268
269 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
270 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
271 "targetname".  If "health" is set, the trigger must be killed to activate.
272 If notouch is set, the trigger is only fired by other entities, not by touching.
273 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
274 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.
275 sounds
276 1)      secret
277 2)      beep beep
278 3)      large switch
279 4)
280 set "message" to text string
281 */
282 void() spawnfunc_trigger_once =
283 {
284         self.wait = -1;
285         spawnfunc_trigger_multiple();
286 };
287
288 //=============================================================================
289
290 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
291 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
292 */
293 void() spawnfunc_trigger_relay =
294 {
295         self.use = SUB_UseTargets;
296 };
297
298 void() delay_use
299 {
300     self.think = SUB_UseTargets;
301     self.nextthink = self.wait;
302 }
303
304 void() spawnfunc_trigger_delay
305 {
306     if(!self.wait)
307         self.wait = 1;
308
309     self.use = delay_use;
310 }
311
312 //=============================================================================
313
314
315 void() counter_use =
316 {
317         self.count = self.count - 1;
318         if (self.count < 0)
319                 return;
320
321         if (self.count != 0)
322         {
323                 if (activator.classname == "player"
324                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
325                 {
326                         if (self.count >= 4)
327                                 centerprint (activator, "There are more to go...");
328                         else if (self.count == 3)
329                                 centerprint (activator, "Only 3 more to go...");
330                         else if (self.count == 2)
331                                 centerprint (activator, "Only 2 more to go...");
332                         else
333                                 centerprint (activator, "Only 1 more to go...");
334                 }
335                 return;
336         }
337
338         if (activator.classname == "player"
339         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
340                 centerprint(activator, "Sequence completed!");
341         self.enemy = activator;
342         multi_trigger ();
343 };
344
345 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
346 Acts as an intermediary for an action that takes multiple inputs.
347
348 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
349
350 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
351 */
352 void() spawnfunc_trigger_counter =
353 {
354         self.wait = -1;
355         if (!self.count)
356                 self.count = 2;
357
358         self.use = counter_use;
359 };
360
361 .float triggerhurttime;
362 void() spawnfunc_hurt_touch =
363 {
364         if (!other.owner)
365         {
366                 if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
367                         other.pain_finished = min(other.pain_finished, time + 2);
368                 else if (other.classname == "rune")                     // reset runes
369                         other.nextthink = min(other.nextthink, time + 1);
370         }
371
372         if (other.takedamage)
373         if (other.triggerhurttime < time)
374         {
375                 other.triggerhurttime = time + 1;
376                 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
377         }
378
379         return;
380 };
381
382 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
383 Any object touching this will be hurt
384 set dmg to damage amount
385 defalt dmg = 5
386 */
387 void() spawnfunc_trigger_hurt =
388 {
389         InitTrigger ();
390         self.touch = spawnfunc_hurt_touch;
391         if (!self.dmg)
392                 self.dmg = 1000;
393         if (!self.message)
394                 self.message = "was in the wrong place.";
395 };
396
397 //void() target_speaker_use = {sound(self, CHAN_VOICE, self.noise, 1, 1);}
398 //void() spawnfunc_target_speaker = {self.use = target_speaker_use;}
399
400 void() spawnfunc_target_speaker =
401 {
402 if(self.noise) {
403  precache_sound (self.noise);
404  ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
405 }
406 //remove(self);
407 };
408
409
410 void() spawnfunc_func_stardust {
411         self.effects = EF_STARDUST;
412 }
413
414
415 /*
416 void() sparksthink =
417 {
418   self.nextthink = time + 0.1;
419
420   if(random() < self.wait) {
421     te_spark(self.origin,'0 0 -1',self.cnt);
422   }
423 }
424
425
426 void() func_sparks =
427 {
428   self.think = sparksthink;
429   self.nextthink = time + 0.2;
430
431   // self.cnt is the amount of sparks that one burst will spawn
432   if(self.cnt < 1) {
433     self.cnt = 25.0; // nice default value
434   }
435
436   // self.wait is the probability that a sparkthink will spawn a spark shower
437   // range: 0 - 1, but 0 makes little sense, so...
438   if(self.wait < 0.05) {
439     self.wait = 0.25; // nice default value
440   }
441
442   // sound
443   if(self.noise) {
444     precache_sound (self.noise);
445     ambientsound (self.origin, self.noise, 1, ATTN_STATIC);
446   }
447 }
448 */
449 void() rain_think =
450 {
451         self.nextthink = time + 0.1;
452         te_particlerain(self.absmin, self.absmax, self.dest, self.count, self.cnt);
453 //      te_particlesnow(self.absmin, self.absmax, self.dest * 0.25, self.count, self.cnt);
454 //      WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
455 //      WriteByte (MSG_BROADCAST, TE_PARTICLERAIN);
456 //      WriteVec (MSG_BROADCAST, self.absmin);
457 //      WriteVec (MSG_BROADCAST, self.absmax);
458 //      WriteVec (MSG_BROADCAST, self.dest);
459 //      WriteShort (MSG_BROADCAST, self.count);
460 //      WriteByte (MSG_BROADCAST, self.cnt);
461 };
462
463 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
464 This is an invisible area like a trigger, which rain falls inside of.
465
466 Keys:
467 "velocity"
468  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
469 "cnt"
470  sets color of rain (default 12 - white)
471 "count"
472  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
473 */
474 void() spawnfunc_func_rain =
475 {
476         self.dest = self.velocity;
477         self.velocity = '0 0 0';
478         if (!self.dest)
479                 self.dest = '0 0 -700';
480         self.angles = '0 0 0';
481         self.movetype = MOVETYPE_NONE;
482         self.solid = SOLID_NOT;
483         if(self.model != "")
484                 setmodel(self, self.model); // no precision needed
485         setorigin(self, self.origin);
486         setsize(self, self.mins, self.maxs);
487         self.model = "";
488         if (!self.cnt)
489                 self.cnt = 12;
490         if (!self.count)
491                 self.count = 2000;
492         self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
493         if (self.count < 1)
494         {
495                 remove(self);
496                 return;
497         }
498         // convert from per second to per 0.1 sec,
499         self.count = ceil(self.count * 0.1);
500         self.think = rain_think;
501         self.nextthink = time + 0.5;
502 };
503
504
505 void() snow_think =
506 {
507         self.nextthink = time + 0.1 + random() * 0.05;
508         te_particlesnow(self.absmin, self.absmax, self.dest, self.count, self.cnt);
509 //      WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
510 //      WriteByte (MSG_BROADCAST, TE_PARTICLESNOW);
511 //      WriteVec (MSG_BROADCAST, self.absmin);
512 //      WriteVec (MSG_BROADCAST, self.absmax);
513 //      WriteVec (MSG_BROADCAST, self.dest);
514 //      WriteShort (MSG_BROADCAST, self.count);
515 //      WriteByte (MSG_BROADCAST, self.cnt);
516 };
517
518 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
519 This is an invisible area like a trigger, which snow falls inside of.
520
521 Keys:
522 "velocity"
523  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
524 "cnt"
525  sets color of rain (default 12 - white)
526 "count"
527  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
528 */
529 void() spawnfunc_func_snow =
530 {
531         self.dest = self.velocity;
532         self.velocity = '0 0 0';
533         if (!self.dest)
534                 self.dest = '0 0 -300';
535         self.angles = '0 0 0';
536         self.movetype = MOVETYPE_NONE;
537         self.solid = SOLID_NOT;
538         if(self.model != "")
539                 setmodel(self, self.model); // no precision needed
540         setorigin(self, self.origin);
541         setsize(self, self.mins, self.maxs);
542         self.model = "";
543         if (!self.cnt)
544                 self.cnt = 12;
545         if (!self.count)
546                 self.count = 2000;
547         self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
548         if (self.count < 1)
549         {
550                 remove(self);
551                 return;
552         }
553         // convert from per second to per 0.1 sec,
554         self.count = ceil(self.count * 0.1);
555         self.think = snow_think;
556         self.nextthink = time + 0.5;
557 };
558
559
560 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
561 void() misc_laser_think =
562 {
563         vector o;
564         if(!self.state)
565         {
566                 self.enemy = find(world, targetname, self.target);
567                 self.state = 1;
568         }
569         if(self.enemy)
570         {
571                 o = self.enemy.origin;
572         }
573         else
574         {
575                 makevectors(self.angles);
576                 o = self.origin + v_forward * MAX_SHOT_DISTANCE;
577         }
578
579         if(self.dmg)
580         {
581                 if(self.dmg < 0)
582                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
583                 else
584                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
585         }
586
587         if(time > self.ltime)
588         {
589                 traceline(self.origin, o, MOVE_WORLDONLY, self);
590                 trailparticles(self, self.cnt, self.origin, trace_endpos);
591                 pointparticles(self.lip, trace_endpos, trace_plane_normal, 256 * frametime);
592                 self.ltime = time + self.wait;
593         }
594         self.nextthink = time;
595 }
596 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ?
597 Any object touching the beam will be hurt
598 Keys:
599 "target"
600  spawnfunc_target_position where the laser ends
601 "mdl"
602  name of beam effect to use
603 "dmg"
604  damage per second (-1 for a laser that kills immediately)
605 "wait"
606  delay between sending the particle effect
607 */
608 void() spawnfunc_misc_laser =
609 {
610         if(self.mdl)
611         {
612                 self.cnt = particleeffectnum(self.mdl);
613                 self.lip = particleeffectnum(strcat(self.mdl, "_end"));
614         }
615         else
616         {
617                 self.cnt = particleeffectnum("misc_laser_beam");
618                 self.lip = particleeffectnum("misc_laser_beam_end");
619         }
620         if(!self.wait)
621                 self.wait = 1;
622         if(!self.message)
623                 self.message = "saw the spawnfunc_light";
624         self.think = misc_laser_think;
625         self.nextthink = time;
626 }
627
628 .float strength;
629 .float lastpushtime;
630 void trigger_impulse_touch1()
631 {
632         entity targ;
633     float pushdeltatime;
634
635         // FIXME: Better checking for what to push and not.
636         if (other.classname != "player")
637         if (other.classname != "corpse")
638         if (other.classname != "body")
639         if (other.classname != "gib")
640         if (other.classname != "missile")
641         if (other.classname != "casing")
642         if (other.classname != "grenade")
643         if (other.classname != "plasma")
644         if (other.classname != "plasma_prim")
645         if (other.classname != "plasma_chain")
646         if (other.classname != "droppedweapon")
647                 return;
648
649         if (other.deadflag && other.classname == "player")
650                 return;
651
652     targ = find(world, targetname, self.target);
653     if(!targ)
654     {
655         objerror("trigger_force without a (valif) .target!\n");
656         remove(self);
657         return;
658     }
659
660
661     pushdeltatime = time - other.lastpushtime;
662     if (pushdeltatime > 0.15) pushdeltatime = 0;
663     other.lastpushtime = time;
664     if(!pushdeltatime) return;
665
666     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
667 }
668
669 void trigger_impulse_touch2()
670 {
671
672         // FIXME: Better checking for what to push and not.
673         if (other.classname != "player")
674         if (other.classname != "corpse")
675         if (other.classname != "body")
676         if (other.classname != "gib")
677         if (other.classname != "missile")
678         if (other.classname != "casing")
679         if (other.classname != "grenade")
680         if (other.classname != "plasma")
681         if (other.classname != "plasma_prim")
682         if (other.classname != "plasma_chain")
683         if (other.classname != "droppedweapon")
684                 return;
685
686         if (other.deadflag && other.classname == "player")
687                 return;
688
689     other.velocity = other.velocity * self.strength;
690 }
691
692
693 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
694 -------- KEYS --------
695 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
696          If not, this trigger acts like a damper/accelerator field.
697
698 strength : This is how mutch force to add in the direction of .target each second
699            when .target is set. If not, this is hoe mutch to slow down/accelerate
700            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
701
702 -------- NOTES --------
703 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
704 in directional push mode. For damper/accelerator mode this is no nessesary (and has no effect).
705 */
706
707 void spawnfunc_trigger_impulse()
708 {
709     InitTrigger ();
710     if(self.target)
711     {
712         if(!self.strength) self.strength = 950;
713         self.touch = trigger_impulse_touch1;
714     }
715     else
716     {
717         if(!self.strength) self.strength = 0.9;
718         self.touch = trigger_impulse_touch2;
719     }
720 }
721