1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5 
6 This file is part of Tremulous.
7 
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12 
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 
26 
InitTrigger(gentity_t * self)27 void InitTrigger( gentity_t *self )
28 {
29   if( !VectorCompare( self->s.angles, vec3_origin ) )
30     G_SetMovedir( self->s.angles, self->movedir );
31 
32   trap_SetBrushModel( self, self->model );
33   self->r.contents = CONTENTS_TRIGGER;    // replaces the -1 from trap_SetBrushModel
34   self->r.svFlags = SVF_NOCLIENT;
35 }
36 
37 
38 // the wait time has passed, so set back up for another activation
multi_wait(gentity_t * ent)39 void multi_wait( gentity_t *ent )
40 {
41   ent->nextthink = 0;
42 }
43 
44 
45 // the trigger was just activated
46 // ent->activator should be set to the activator so it can be held through a delay
47 // so wait for the delay time before firing
multi_trigger(gentity_t * ent,gentity_t * activator)48 void multi_trigger( gentity_t *ent, gentity_t *activator )
49 {
50   ent->activator = activator;
51   if( ent->nextthink )
52     return;   // can't retrigger until the wait is over
53 
54   if( activator->client )
55   {
56     if( ( ent->spawnflags & 1 ) &&
57         activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
58       return;
59 
60     if( ( ent->spawnflags & 2 ) &&
61         activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
62       return;
63   }
64 
65   G_UseTargets( ent, ent->activator );
66 
67   if( ent->wait > 0 )
68   {
69     ent->think = multi_wait;
70     ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000;
71   }
72   else
73   {
74     // we can't just remove (self) here, because this is a touch function
75     // called while looping through area links...
76     ent->touch = 0;
77     ent->nextthink = level.time + FRAMETIME;
78     ent->think = G_FreeEntity;
79   }
80 }
81 
Use_Multi(gentity_t * ent,gentity_t * other,gentity_t * activator)82 void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator )
83 {
84   multi_trigger( ent, activator );
85 }
86 
Touch_Multi(gentity_t * self,gentity_t * other,trace_t * trace)87 void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace )
88 {
89   if( !other->client && other->s.eType != ET_BUILDABLE )
90     return;
91 
92   multi_trigger( self, other );
93 }
94 
95 /*QUAKED trigger_multiple (.5 .5 .5) ?
96 "wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
97 "random"  wait variance, default is 0
98 Variable sized repeatable trigger.  Must be targeted at one or more entities.
99 so, the basic time between firing is a random time between
100 (wait - random) and (wait + random)
101 */
SP_trigger_multiple(gentity_t * ent)102 void SP_trigger_multiple( gentity_t *ent )
103 {
104   G_SpawnFloat( "wait", "0.5", &ent->wait );
105   G_SpawnFloat( "random", "0", &ent->random );
106 
107   if( ent->random >= ent->wait && ent->wait >= 0 )
108   {
109     ent->random = ent->wait - FRAMETIME;
110     G_Printf( "trigger_multiple has random >= wait\n" );
111   }
112 
113   ent->touch = Touch_Multi;
114   ent->use = Use_Multi;
115 
116   InitTrigger( ent );
117   trap_LinkEntity( ent );
118 }
119 
120 
121 
122 /*
123 ==============================================================================
124 
125 trigger_always
126 
127 ==============================================================================
128 */
129 
trigger_always_think(gentity_t * ent)130 void trigger_always_think( gentity_t *ent )
131 {
132   G_UseTargets( ent, ent );
133   G_FreeEntity( ent );
134 }
135 
136 /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
137 This trigger will always fire.  It is activated by the world.
138 */
SP_trigger_always(gentity_t * ent)139 void SP_trigger_always( gentity_t *ent )
140 {
141   // we must have some delay to make sure our use targets are present
142   ent->nextthink = level.time + 300;
143   ent->think = trigger_always_think;
144 }
145 
146 
147 /*
148 ==============================================================================
149 
150 trigger_push
151 
152 ==============================================================================
153 */
154 
trigger_push_touch(gentity_t * self,gentity_t * other,trace_t * trace)155 void trigger_push_touch( gentity_t *self, gentity_t *other, trace_t *trace )
156 {
157   if( !other->client )
158     return;
159 }
160 
161 
162 /*
163 =================
164 AimAtTarget
165 
166 Calculate origin2 so the target apogee will be hit
167 =================
168 */
AimAtTarget(gentity_t * self)169 void AimAtTarget( gentity_t *self )
170 {
171   gentity_t *ent;
172   vec3_t    origin;
173   float     height, gravity, time, forward;
174   float     dist;
175 
176   VectorAdd( self->r.absmin, self->r.absmax, origin );
177   VectorScale( origin, 0.5, origin );
178 
179   ent = G_PickTarget( self->target );
180 
181   if( !ent )
182   {
183     G_FreeEntity( self );
184     return;
185   }
186 
187   height = ent->s.origin[ 2 ] - origin[ 2 ];
188   gravity = g_gravity.value;
189   time = sqrt( height / ( 0.5 * gravity ) );
190 
191   if( !time )
192   {
193     G_FreeEntity( self );
194     return;
195   }
196 
197   // set s.origin2 to the push velocity
198   VectorSubtract( ent->s.origin, origin, self->s.origin2 );
199   self->s.origin2[ 2 ] = 0;
200   dist = VectorNormalize( self->s.origin2 );
201 
202   forward = dist / time;
203   VectorScale( self->s.origin2, forward, self->s.origin2 );
204 
205   self->s.origin2[ 2 ] = time * gravity;
206 }
207 
208 
209 /*QUAKED trigger_push (.5 .5 .5) ?
210 Must point at a target_position, which will be the apex of the leap.
211 This will be client side predicted, unlike target_push
212 */
SP_trigger_push(gentity_t * self)213 void SP_trigger_push( gentity_t *self )
214 {
215   InitTrigger( self );
216 
217   // unlike other triggers, we need to send this one to the client
218   self->r.svFlags &= ~SVF_NOCLIENT;
219 
220   // make sure the client precaches this sound
221   G_SoundIndex( "sound/world/jumppad.wav" );
222 
223   self->s.eType = ET_PUSH_TRIGGER;
224   self->touch = trigger_push_touch;
225   self->think = AimAtTarget;
226   self->nextthink = level.time + FRAMETIME;
227   trap_LinkEntity( self );
228 }
229 
230 
Use_target_push(gentity_t * self,gentity_t * other,gentity_t * activator)231 void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator )
232 {
233   if( !activator->client )
234     return;
235 
236   if( activator->client->ps.pm_type != PM_NORMAL )
237     return;
238 
239   VectorCopy( self->s.origin2, activator->client->ps.velocity );
240 
241   // play fly sound every 1.5 seconds
242   if( activator->fly_sound_debounce_time < level.time )
243   {
244     activator->fly_sound_debounce_time = level.time + 1500;
245     G_Sound( activator, CHAN_AUTO, self->noise_index );
246   }
247 }
248 
249 /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad
250 Pushes the activator in the direction.of angle, or towards a target apex.
251 "speed"   defaults to 1000
252 if "bouncepad", play bounce noise instead of windfly
253 */
SP_target_push(gentity_t * self)254 void SP_target_push( gentity_t *self )
255 {
256   if( !self->speed )
257     self->speed = 1000;
258 
259   G_SetMovedir( self->s.angles, self->s.origin2 );
260   VectorScale( self->s.origin2, self->speed, self->s.origin2 );
261 
262   if( self->spawnflags & 1 )
263     self->noise_index = G_SoundIndex( "sound/world/jumppad.wav" );
264   else
265     self->noise_index = G_SoundIndex( "sound/misc/windfly.wav" );
266 
267   if( self->target )
268   {
269     VectorCopy( self->s.origin, self->r.absmin );
270     VectorCopy( self->s.origin, self->r.absmax );
271     self->think = AimAtTarget;
272     self->nextthink = level.time + FRAMETIME;
273   }
274 
275   self->use = Use_target_push;
276 }
277 
278 /*
279 ==============================================================================
280 
281 trigger_teleport
282 
283 ==============================================================================
284 */
285 
trigger_teleporter_touch(gentity_t * self,gentity_t * other,trace_t * trace)286 void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace )
287 {
288   gentity_t *dest;
289 
290   if( !other->client )
291     return;
292 
293   if( other->client->ps.pm_type == PM_DEAD )
294     return;
295 
296   // Spectators only?
297   if( ( self->spawnflags & 1 ) &&
298       other->client->sess.sessionTeam != TEAM_SPECTATOR )
299     return;
300 
301 
302   dest = G_PickTarget( self->target );
303 
304   if( !dest )
305   {
306     G_Printf( "Couldn't find teleporter destination\n" );
307     return;
308   }
309 
310   TeleportPlayer( other, dest->s.origin, dest->s.angles );
311 }
312 
313 
314 /*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR
315 Allows client side prediction of teleportation events.
316 Must point at a target_position, which will be the teleport destination.
317 
318 If spectator is set, only spectators can use this teleport
319 Spectator teleporters are not normally placed in the editor, but are created
320 automatically near doors to allow spectators to move through them
321 */
SP_trigger_teleport(gentity_t * self)322 void SP_trigger_teleport( gentity_t *self )
323 {
324   InitTrigger( self );
325 
326   // unlike other triggers, we need to send this one to the client
327   // unless is a spectator trigger
328   if( self->spawnflags & 1 )
329     self->r.svFlags |= SVF_NOCLIENT;
330   else
331     self->r.svFlags &= ~SVF_NOCLIENT;
332 
333   // make sure the client precaches this sound
334   G_SoundIndex( "sound/world/jumppad.wav" );
335 
336   self->s.eType = ET_TELEPORT_TRIGGER;
337   self->touch = trigger_teleporter_touch;
338 
339   trap_LinkEntity( self );
340 }
341 
342 
343 /*
344 ==============================================================================
345 
346 trigger_hurt
347 
348 ==============================================================================
349 */
350 
351 /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW
352 Any entity that touches this will be hurt.
353 It does dmg points of damage each server frame
354 Targeting the trigger will toggle its on / off state.
355 
356 SILENT      supresses playing the sound
357 SLOW      changes the damage rate to once per second
358 NO_PROTECTION *nothing* stops the damage
359 
360 "dmg"     default 5 (whole numbers only)
361 
362 */
hurt_use(gentity_t * self,gentity_t * other,gentity_t * activator)363 void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator )
364 {
365   if( self->r.linked )
366     trap_UnlinkEntity( self );
367   else
368     trap_LinkEntity( self );
369 }
370 
hurt_touch(gentity_t * self,gentity_t * other,trace_t * trace)371 void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace )
372 {
373   int   dflags;
374 
375   if( !other->takedamage )
376     return;
377 
378   if( self->timestamp > level.time )
379     return;
380 
381   if( self->spawnflags & 16 )
382     self->timestamp = level.time + 1000;
383   else
384     self->timestamp = level.time + FRAMETIME;
385 
386   // play sound
387   if( !( self->spawnflags & 4 ) )
388     G_Sound( other, CHAN_AUTO, self->noise_index );
389 
390   if( self->spawnflags & 8 )
391     dflags = DAMAGE_NO_PROTECTION;
392   else
393     dflags = 0;
394 
395   G_Damage( other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT );
396 }
397 
SP_trigger_hurt(gentity_t * self)398 void SP_trigger_hurt( gentity_t *self )
399 {
400   InitTrigger( self );
401 
402   self->noise_index = G_SoundIndex( "sound/misc/electro.wav" );
403   self->touch = hurt_touch;
404 
405   if( !self->damage )
406     self->damage = 5;
407 
408   self->r.contents = CONTENTS_TRIGGER;
409 
410   if( self->spawnflags & 2 )
411     self->use = hurt_use;
412 
413   // link in to the world if starting active
414   if( !( self->spawnflags & 1 ) )
415     trap_LinkEntity( self );
416 }
417 
418 
419 /*
420 ==============================================================================
421 
422 timer
423 
424 ==============================================================================
425 */
426 
427 
428 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
429 This should be renamed trigger_timer...
430 Repeatedly fires its targets.
431 Can be turned on or off by using.
432 
433 "wait"      base time between triggering all targets, default is 1
434 "random"    wait variance, default is 0
435 so, the basic time between firing is a random time between
436 (wait - random) and (wait + random)
437 
438 */
func_timer_think(gentity_t * self)439 void func_timer_think( gentity_t *self )
440 {
441   G_UseTargets( self, self->activator );
442   // set time before next firing
443   self->nextthink = level.time + 1000 * ( self->wait + crandom( ) * self->random );
444 }
445 
func_timer_use(gentity_t * self,gentity_t * other,gentity_t * activator)446 void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator )
447 {
448   self->activator = activator;
449 
450   // if on, turn it off
451   if( self->nextthink )
452   {
453     self->nextthink = 0;
454     return;
455   }
456 
457   // turn it on
458   func_timer_think( self );
459 }
460 
SP_func_timer(gentity_t * self)461 void SP_func_timer( gentity_t *self )
462 {
463   G_SpawnFloat( "random", "1", &self->random );
464   G_SpawnFloat( "wait", "1", &self->wait );
465 
466   self->use = func_timer_use;
467   self->think = func_timer_think;
468 
469   if( self->random >= self->wait )
470   {
471     self->random = self->wait - FRAMETIME;
472     G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
473   }
474 
475   if( self->spawnflags & 1 )
476   {
477     self->nextthink = level.time + FRAMETIME;
478     self->activator = self;
479   }
480 
481   self->r.svFlags = SVF_NOCLIENT;
482 }
483 
484 
485 /*
486 ===============
487 G_Checktrigger_stages
488 
489 Called when stages change
490 ===============
491 */
G_Checktrigger_stages(pTeam_t team,stage_t stage)492 void G_Checktrigger_stages( pTeam_t team, stage_t stage )
493 {
494   int i;
495   gentity_t *ent;
496 
497   for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
498   {
499     if( !ent->inuse )
500       continue;
501 
502     if( !Q_stricmp( ent->classname, "trigger_stage" ) )
503     {
504       if( team == ent->stageTeam && stage == ent->stageStage )
505         ent->use( ent, ent, ent );
506     }
507   }
508 }
509 
510 
511 /*
512 ===============
513 trigger_stage_use
514 ===============
515 */
trigger_stage_use(gentity_t * self,gentity_t * other,gentity_t * activator)516 void trigger_stage_use( gentity_t *self, gentity_t *other, gentity_t *activator )
517 {
518   G_UseTargets( self, self );
519 }
520 
SP_trigger_stage(gentity_t * self)521 void SP_trigger_stage( gentity_t *self )
522 {
523   G_SpawnInt( "team", "0", (int *)&self->stageTeam );
524   G_SpawnInt( "stage", "0", (int *)&self->stageStage );
525 
526   self->use = trigger_stage_use;
527 
528   self->r.svFlags = SVF_NOCLIENT;
529 }
530 
531 
532 /*
533 ===============
534 trigger_win
535 ===============
536 */
trigger_win(gentity_t * self,gentity_t * other,gentity_t * activator)537 void trigger_win( gentity_t *self, gentity_t *other, gentity_t *activator )
538 {
539   G_UseTargets( self, self );
540 }
541 
SP_trigger_win(gentity_t * self)542 void SP_trigger_win( gentity_t *self )
543 {
544   G_SpawnInt( "team", "0", (int *)&self->stageTeam );
545 
546   self->use = trigger_win;
547 
548   self->r.svFlags = SVF_NOCLIENT;
549 }
550 
551 
552 /*
553 ===============
554 trigger_buildable_trigger
555 ===============
556 */
trigger_buildable_trigger(gentity_t * self,gentity_t * activator)557 void trigger_buildable_trigger( gentity_t *self, gentity_t *activator )
558 {
559   int i = 0;
560 
561   self->activator = activator;
562   if( self->nextthink )
563     return;   // can't retrigger until the wait is over
564 
565   //if there is no buildable list every buildable triggers
566   if( self->bTriggers[ i ] == BA_NONE )
567     G_UseTargets( self, activator );
568   else
569   {
570     //otherwise check against the list
571     for( i = 0; self->bTriggers[ i ] != BA_NONE; i++ )
572     {
573       if( activator->s.modelindex == self->bTriggers[ i ] )
574       {
575         G_UseTargets( self, activator );
576         return;
577       }
578     }
579   }
580 
581   if( self->wait > 0 )
582   {
583     self->think = multi_wait;
584     self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
585   }
586   else
587   {
588     // we can't just remove (self) here, because this is a touch function
589     // called while looping through area links...
590     self->touch = 0;
591     self->nextthink = level.time + FRAMETIME;
592     self->think = G_FreeEntity;
593   }
594 }
595 
596 /*
597 ===============
598 trigger_buildable_touch
599 ===============
600 */
trigger_buildable_touch(gentity_t * ent,gentity_t * other,trace_t * trace)601 void trigger_buildable_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
602 {
603   //only triggered by buildables
604   if( other->s.eType != ET_BUILDABLE )
605     return;
606 
607   trigger_buildable_trigger( ent, other );
608 }
609 
610 /*
611 ===============
612 trigger_buildable_use
613 ===============
614 */
trigger_buildable_use(gentity_t * ent,gentity_t * other,gentity_t * activator)615 void trigger_buildable_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
616 {
617   trigger_buildable_trigger( ent, activator );
618 }
619 
620 /*
621 ===============
622 SP_trigger_buildable
623 ===============
624 */
SP_trigger_buildable(gentity_t * self)625 void SP_trigger_buildable( gentity_t *self )
626 {
627   char *buffer;
628 
629   G_SpawnFloat( "wait", "0.5", &self->wait );
630   G_SpawnFloat( "random", "0", &self->random );
631 
632   if( self->random >= self->wait && self->wait >= 0 )
633   {
634     self->random = self->wait - FRAMETIME;
635     G_Printf( S_COLOR_YELLOW "WARNING: trigger_buildable has random >= wait\n" );
636   }
637 
638   G_SpawnString( "buildables", "", &buffer );
639 
640   BG_ParseCSVBuildableList( buffer, self->bTriggers, BA_NUM_BUILDABLES );
641 
642   self->touch = trigger_buildable_touch;
643   self->use = trigger_buildable_use;
644 
645   InitTrigger( self );
646   trap_LinkEntity( self );
647 }
648 
649 
650 /*
651 ===============
652 trigger_class_trigger
653 ===============
654 */
trigger_class_trigger(gentity_t * self,gentity_t * activator)655 void trigger_class_trigger( gentity_t *self, gentity_t *activator )
656 {
657   int i = 0;
658 
659   //sanity check
660   if( !activator->client )
661     return;
662 
663   if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
664     return;
665 
666   self->activator = activator;
667   if( self->nextthink )
668     return;   // can't retrigger until the wait is over
669 
670   //if there is no class list every class triggers (stupid case)
671   if( self->cTriggers[ i ] == PCL_NONE )
672     G_UseTargets( self, activator );
673   else
674   {
675     //otherwise check against the list
676     for( i = 0; self->cTriggers[ i ] != PCL_NONE; i++ )
677     {
678       if( activator->client->ps.stats[ STAT_PCLASS ] == self->cTriggers[ i ] )
679       {
680         G_UseTargets( self, activator );
681         return;
682       }
683     }
684   }
685 
686   if( self->wait > 0 )
687   {
688     self->think = multi_wait;
689     self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
690   }
691   else
692   {
693     // we can't just remove (self) here, because this is a touch function
694     // called while looping through area links...
695     self->touch = 0;
696     self->nextthink = level.time + FRAMETIME;
697     self->think = G_FreeEntity;
698   }
699 }
700 
701 /*
702 ===============
703 trigger_class_touch
704 ===============
705 */
trigger_class_touch(gentity_t * ent,gentity_t * other,trace_t * trace)706 void trigger_class_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
707 {
708   //only triggered by clients
709   if( !other->client )
710     return;
711 
712   trigger_class_trigger( ent, other );
713 }
714 
715 /*
716 ===============
717 trigger_class_use
718 ===============
719 */
trigger_class_use(gentity_t * ent,gentity_t * other,gentity_t * activator)720 void trigger_class_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
721 {
722   trigger_class_trigger( ent, activator );
723 }
724 
725 /*
726 ===============
727 SP_trigger_class
728 ===============
729 */
SP_trigger_class(gentity_t * self)730 void SP_trigger_class( gentity_t *self )
731 {
732   char *buffer;
733 
734   G_SpawnFloat( "wait", "0.5", &self->wait );
735   G_SpawnFloat( "random", "0", &self->random );
736 
737   if( self->random >= self->wait && self->wait >= 0 )
738   {
739     self->random = self->wait - FRAMETIME;
740     G_Printf( S_COLOR_YELLOW "WARNING: trigger_class has random >= wait\n" );
741   }
742 
743   G_SpawnString( "classes", "", &buffer );
744 
745   BG_ParseCSVClassList( buffer, self->cTriggers, PCL_NUM_CLASSES );
746 
747   self->touch = trigger_class_touch;
748   self->use = trigger_class_use;
749 
750   InitTrigger( self );
751   trap_LinkEntity( self );
752 }
753 
754 
755 /*
756 ===============
757 trigger_equipment_trigger
758 ===============
759 */
trigger_equipment_trigger(gentity_t * self,gentity_t * activator)760 void trigger_equipment_trigger( gentity_t *self, gentity_t *activator )
761 {
762   int i = 0;
763 
764   //sanity check
765   if( !activator->client )
766     return;
767 
768   if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
769     return;
770 
771   self->activator = activator;
772   if( self->nextthink )
773     return;   // can't retrigger until the wait is over
774 
775   //if there is no equipment list all equipment triggers (stupid case)
776   if( self->wTriggers[ i ] == WP_NONE && self->wTriggers[ i ] == UP_NONE )
777     G_UseTargets( self, activator );
778   else
779   {
780     //otherwise check against the lists
781     for( i = 0; self->wTriggers[ i ] != WP_NONE; i++ )
782     {
783       if( BG_InventoryContainsWeapon( self->wTriggers[ i ], activator->client->ps.stats ) )
784       {
785         G_UseTargets( self, activator );
786         return;
787       }
788     }
789 
790     for( i = 0; self->uTriggers[ i ] != UP_NONE; i++ )
791     {
792       if( BG_InventoryContainsUpgrade( self->uTriggers[ i ], activator->client->ps.stats ) )
793       {
794         G_UseTargets( self, activator );
795         return;
796       }
797     }
798   }
799 
800   if( self->wait > 0 )
801   {
802     self->think = multi_wait;
803     self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
804   }
805   else
806   {
807     // we can't just remove (self) here, because this is a touch function
808     // called while looping through area links...
809     self->touch = 0;
810     self->nextthink = level.time + FRAMETIME;
811     self->think = G_FreeEntity;
812   }
813 }
814 
815 /*
816 ===============
817 trigger_equipment_touch
818 ===============
819 */
trigger_equipment_touch(gentity_t * ent,gentity_t * other,trace_t * trace)820 void trigger_equipment_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
821 {
822   //only triggered by clients
823   if( !other->client )
824     return;
825 
826   trigger_equipment_trigger( ent, other );
827 }
828 
829 /*
830 ===============
831 trigger_equipment_use
832 ===============
833 */
trigger_equipment_use(gentity_t * ent,gentity_t * other,gentity_t * activator)834 void trigger_equipment_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
835 {
836   trigger_equipment_trigger( ent, activator );
837 }
838 
839 /*
840 ===============
841 SP_trigger_equipment
842 ===============
843 */
SP_trigger_equipment(gentity_t * self)844 void SP_trigger_equipment( gentity_t *self )
845 {
846   char *buffer;
847 
848   G_SpawnFloat( "wait", "0.5", &self->wait );
849   G_SpawnFloat( "random", "0", &self->random );
850 
851   if( self->random >= self->wait && self->wait >= 0 )
852   {
853     self->random = self->wait - FRAMETIME;
854     G_Printf( S_COLOR_YELLOW "WARNING: trigger_equipment has random >= wait\n" );
855   }
856 
857   G_SpawnString( "equipment", "", &buffer );
858 
859   BG_ParseCSVEquipmentList( buffer, self->wTriggers, WP_NUM_WEAPONS,
860       self->uTriggers, UP_NUM_UPGRADES );
861 
862   self->touch = trigger_equipment_touch;
863   self->use = trigger_equipment_use;
864 
865   InitTrigger( self );
866   trap_LinkEntity( self );
867 }
868 
869 
870 /*
871 ===============
872 trigger_gravity_touch
873 ===============
874 */
trigger_gravity_touch(gentity_t * ent,gentity_t * other,trace_t * trace)875 void trigger_gravity_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
876 {
877   //only triggered by clients
878   if( !other->client )
879     return;
880 
881   other->client->ps.gravity = ent->triggerGravity;
882 }
883 
884 /*
885 ===============
886 trigger_gravity_use
887 ===============
888 */
trigger_gravity_use(gentity_t * ent,gentity_t * other,gentity_t * activator)889 void trigger_gravity_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
890 {
891   if( ent->r.linked )
892     trap_UnlinkEntity( ent );
893   else
894     trap_LinkEntity( ent );
895 }
896 
897 
898 /*
899 ===============
900 SP_trigger_gravity
901 ===============
902 */
SP_trigger_gravity(gentity_t * self)903 void SP_trigger_gravity( gentity_t *self )
904 {
905   G_SpawnInt( "gravity", "800", &self->triggerGravity );
906 
907   self->touch = trigger_gravity_touch;
908   self->use = trigger_gravity_use;
909 
910   InitTrigger( self );
911   trap_LinkEntity( self );
912 }
913 
914 
915 /*
916 ===============
917 trigger_heal_use
918 ===============
919 */
trigger_heal_use(gentity_t * self,gentity_t * other,gentity_t * activator)920 void trigger_heal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
921 {
922   if( self->r.linked )
923     trap_UnlinkEntity( self );
924   else
925     trap_LinkEntity( self );
926 }
927 
928 /*
929 ===============
930 trigger_heal_touch
931 ===============
932 */
trigger_heal_touch(gentity_t * self,gentity_t * other,trace_t * trace)933 void trigger_heal_touch( gentity_t *self, gentity_t *other, trace_t *trace )
934 {
935   int max;
936 
937   if( !other->client )
938     return;
939 
940   if( self->timestamp > level.time )
941     return;
942 
943   if( self->spawnflags & 2 )
944     self->timestamp = level.time + 1000;
945   else
946     self->timestamp = level.time + FRAMETIME;
947 
948   max = other->client->ps.stats[ STAT_MAX_HEALTH ];
949 
950   other->health += self->damage;
951 
952   if( other->health > max )
953     other->health = max;
954 
955   other->client->ps.stats[ STAT_HEALTH ] = other->health;
956 }
957 
958 /*
959 ===============
960 SP_trigger_heal
961 ===============
962 */
SP_trigger_heal(gentity_t * self)963 void SP_trigger_heal( gentity_t *self )
964 {
965   G_SpawnInt( "heal", "5", &self->damage );
966 
967   self->touch = trigger_heal_touch;
968   self->use = trigger_heal_use;
969 
970   InitTrigger( self );
971 
972   // link in to the world if starting active
973   if( !( self->spawnflags & 1 ) )
974     trap_LinkEntity( self );
975 }
976 
977 
978 /*
979 ===============
980 trigger_ammo_touch
981 ===============
982 */
trigger_ammo_touch(gentity_t * self,gentity_t * other,trace_t * trace)983 void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace )
984 {
985   int ammo, clips, maxClips, maxAmmo;
986 
987   if( !other->client )
988     return;
989 
990   if( other->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
991     return;
992 
993   if( self->timestamp > level.time )
994     return;
995 
996   if( other->client->ps.weaponstate != WEAPON_READY )
997     return;
998 
999   if( BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 2 )
1000     return;
1001 
1002   if( !BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 4 )
1003     return;
1004 
1005   if( self->spawnflags & 1 )
1006     self->timestamp = level.time + 1000;
1007   else
1008     self->timestamp = level.time + FRAMETIME;
1009 
1010   BG_FindAmmoForWeapon( other->client->ps.weapon, &maxAmmo, &maxClips );
1011   BG_UnpackAmmoArray( other->client->ps.weapon, other->client->ps.ammo, other->client->ps.powerups,
1012                       &ammo, &clips );
1013 
1014   if( ( ammo + self->damage ) > maxAmmo )
1015   {
1016     if( clips < maxClips )
1017     {
1018       clips++;
1019       ammo = 1;
1020     }
1021     else
1022       ammo = maxAmmo;
1023   }
1024   else
1025     ammo += self->damage;
1026 
1027   BG_PackAmmoArray( other->client->ps.weapon, other->client->ps.ammo, other->client->ps.powerups,
1028                     ammo, clips );
1029 }
1030 
1031 /*
1032 ===============
1033 SP_trigger_ammo
1034 ===============
1035 */
SP_trigger_ammo(gentity_t * self)1036 void SP_trigger_ammo( gentity_t *self )
1037 {
1038   G_SpawnInt( "ammo", "1", &self->damage );
1039 
1040   self->touch = trigger_ammo_touch;
1041 
1042   InitTrigger( self );
1043   trap_LinkEntity( self );
1044 }
1045