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