]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
new entity misc_laser with particle effect misc_laser_beam
[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() 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 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 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() 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 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() trigger_once =
283 {
284         self.wait = -1;
285         trigger_multiple();
286 };
287
288 //=============================================================================
289
290 /*QUAKED 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() 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() 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 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() 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() 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 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() trigger_hurt =
388 {
389         InitTrigger ();
390         self.touch = 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() target_speaker = {self.use = target_speaker_use;}
399
400 void() 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() 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 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() 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         setmodel(self, self.model); // no precision needed
484         setorigin(self, self.origin);
485         setsize(self, self.mins, self.maxs);
486         self.model = "";
487         if (!self.cnt)
488                 self.cnt = 12;
489         if (!self.count)
490                 self.count = 2000;
491         self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
492         if (self.count < 1)
493         {
494                 remove(self);
495                 return;
496         }
497         // convert from per second to per 0.1 sec,
498         self.count = ceil(self.count * 0.1);
499         self.think = rain_think;
500         self.nextthink = time + 0.5;
501 };
502
503
504 void() snow_think =
505 {
506         self.nextthink = time + 0.1 + random() * 0.05;
507         te_particlesnow(self.absmin, self.absmax, self.dest, self.count, self.cnt);
508 //      WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
509 //      WriteByte (MSG_BROADCAST, TE_PARTICLESNOW);
510 //      WriteVec (MSG_BROADCAST, self.absmin);
511 //      WriteVec (MSG_BROADCAST, self.absmax);
512 //      WriteVec (MSG_BROADCAST, self.dest);
513 //      WriteShort (MSG_BROADCAST, self.count);
514 //      WriteByte (MSG_BROADCAST, self.cnt);
515 };
516
517 /*QUAKED func_snow (0 .5 .8) ?
518 This is an invisible area like a trigger, which snow falls inside of.
519
520 Keys:
521 "velocity"
522  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
523 "cnt"
524  sets color of rain (default 12 - white)
525 "count"
526  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
527 */
528 void() func_snow =
529 {
530         self.dest = self.velocity;
531         self.velocity = '0 0 0';
532         if (!self.dest)
533                 self.dest = '0 0 -300';
534         self.angles = '0 0 0';
535         self.movetype = MOVETYPE_NONE;
536         self.solid = SOLID_NOT;
537         setmodel(self, self.model); // no precision needed
538         setorigin(self, self.origin);
539         setsize(self, self.mins, self.maxs);
540         self.model = "";
541         if (!self.cnt)
542                 self.cnt = 12;
543         if (!self.count)
544                 self.count = 2000;
545         self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
546         if (self.count < 1)
547         {
548                 remove(self);
549                 return;
550         }
551         // convert from per second to per 0.1 sec,
552         self.count = ceil(self.count * 0.1);
553         self.think = snow_think;
554         self.nextthink = time + 0.5;
555 };
556
557
558 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
559 void() misc_laser_think =
560 {
561         vector o;
562         if(!self.state)
563         {
564                 self.enemy = find(world, targetname, self.target);
565                 self.state = 1;
566         }
567         if(self.enemy)
568         {
569                 o = self.enemy.origin;
570         }
571         else
572         {
573                 makevectors(self.angles);
574                 o = self.origin + v_forward * MAX_SHOT_DISTANCE;
575         }
576
577         if(self.dmg)
578         {
579                 if(self.dmg < 0)
580                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
581                 else
582                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
583         }
584         if(time > self.ltime)
585         {
586                 trailparticles(self, self.cnt, self.origin, trace_endpos);
587                 pointparticles(self.lip, trace_endpos, trace_plane_normal, 256 * frametime);
588                 self.ltime = time + self.wait;
589         }
590         self.nextthink = time;
591 }
592 /*QUAKED misc_laser (.5 .5 .5) ?
593 Any object touching the beam will be hurt
594 Keys:
595 "target"
596  target_position where the laser ends
597 "mdl"
598  name of beam effect to use
599 "dmg"
600  damage per second (-1 for a laser that kills immediately)
601 "wait"
602  delay between sending the particle effect
603 */
604 void() misc_laser =
605 {
606         if(self.mdl)
607         {
608                 self.cnt = particleeffectnum(self.mdl);
609                 self.lip = particleeffectnum(strcat(self.mdl, "_end"));
610         }
611         else
612         {
613                 self.cnt = particleeffectnum("misc_laser_beam");
614                 self.lip = particleeffectnum("misc_laser_beam_end");
615         }
616         if(!self.wait)
617                 self.wait = 1;
618         if(!self.message)
619                 self.message = "saw the light";
620         self.think = misc_laser_think;
621         self.nextthink = time;
622 }