1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 #include "g_local.h"
24
25
InitTrigger(gentity_t * self)26 void InitTrigger( gentity_t *self ) {
27 if (!VectorCompare (self->s.angles, vec3_origin))
28 G_SetMovedir (self->s.angles, self->movedir);
29
30 trap_SetBrushModel( self, self->model );
31 self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap_SetBrushModel
32 self->r.svFlags = SVF_NOCLIENT;
33 }
34
35
36 // the wait time has passed, so set back up for another activation
multi_wait(gentity_t * ent)37 void multi_wait( gentity_t *ent ) {
38 ent->nextthink = 0;
39 }
40
41
42 // the trigger was just activated
43 // ent->activator should be set to the activator so it can be held through a delay
44 // so wait for the delay time before firing
multi_trigger(gentity_t * ent,gentity_t * activator)45 void multi_trigger( gentity_t *ent, gentity_t *activator ) {
46 ent->activator = activator;
47 if ( ent->nextthink ) {
48 return; // can't retrigger until the wait is over
49 }
50
51 if ( activator->client ) {
52 if ( ( ent->spawnflags & 1 ) &&
53 activator->client->sess.sessionTeam != TEAM_RED ) {
54 return;
55 }
56 if ( ( ent->spawnflags & 2 ) &&
57 activator->client->sess.sessionTeam != TEAM_BLUE ) {
58 return;
59 }
60 }
61
62 G_UseTargets (ent, ent->activator);
63
64 if ( ent->wait > 0 ) {
65 ent->think = multi_wait;
66 ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000;
67 } else {
68 // we can't just remove (self) here, because this is a touch function
69 // called while looping through area links...
70 ent->touch = 0;
71 ent->nextthink = level.time + FRAMETIME;
72 ent->think = G_FreeEntity;
73 }
74 }
75
Use_Multi(gentity_t * ent,gentity_t * other,gentity_t * activator)76 void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
77 multi_trigger( ent, activator );
78 }
79
Touch_Multi(gentity_t * self,gentity_t * other,trace_t * trace)80 void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) {
81 if( !other->client ) {
82 return;
83 }
84 multi_trigger( self, other );
85 }
86
87 /*QUAKED trigger_multiple (.5 .5 .5) ?
88 "wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
89 "random" wait variance, default is 0
90 Variable sized repeatable trigger. Must be targeted at one or more entities.
91 so, the basic time between firing is a random time between
92 (wait - random) and (wait + random)
93 */
SP_trigger_multiple(gentity_t * ent)94 void SP_trigger_multiple( gentity_t *ent ) {
95 G_SpawnFloat( "wait", "0.5", &ent->wait );
96 G_SpawnFloat( "random", "0", &ent->random );
97
98 if ( ent->random >= ent->wait && ent->wait >= 0 ) {
99 ent->random = ent->wait - FRAMETIME;
100 G_Printf( "trigger_multiple has random >= wait\n" );
101 }
102
103 ent->touch = Touch_Multi;
104 ent->use = Use_Multi;
105
106 InitTrigger( ent );
107 trap_LinkEntity (ent);
108 }
109
110
111
112 /*
113 ==============================================================================
114
115 trigger_always
116
117 ==============================================================================
118 */
119
trigger_always_think(gentity_t * ent)120 void trigger_always_think( gentity_t *ent ) {
121 G_UseTargets(ent, ent);
122 G_FreeEntity( ent );
123 }
124
125 /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
126 This trigger will always fire. It is activated by the world.
127 */
SP_trigger_always(gentity_t * ent)128 void SP_trigger_always (gentity_t *ent) {
129 // we must have some delay to make sure our use targets are present
130 ent->nextthink = level.time + 300;
131 ent->think = trigger_always_think;
132 }
133
134
135 /*
136 ==============================================================================
137
138 trigger_push
139
140 ==============================================================================
141 */
142
trigger_push_touch(gentity_t * self,gentity_t * other,trace_t * trace)143 void trigger_push_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
144
145 if ( !other->client ) {
146 return;
147 }
148
149 BG_TouchJumpPad( &other->client->ps, &self->s );
150 }
151
152
153 /*
154 =================
155 AimAtTarget
156
157 Calculate origin2 so the target apogee will be hit
158 =================
159 */
AimAtTarget(gentity_t * self)160 void AimAtTarget( gentity_t *self ) {
161 gentity_t *ent;
162 vec3_t origin;
163 float height, gravity, time, forward;
164 float dist;
165
166 VectorAdd( self->r.absmin, self->r.absmax, origin );
167 VectorScale ( origin, 0.5, origin );
168
169 ent = G_PickTarget( self->target );
170 if ( !ent ) {
171 G_FreeEntity( self );
172 return;
173 }
174
175 height = ent->s.origin[2] - origin[2];
176 gravity = g_gravity.value;
177 time = sqrt( height / ( .5 * gravity ) );
178 if ( !time ) {
179 G_FreeEntity( self );
180 return;
181 }
182
183 // set s.origin2 to the push velocity
184 VectorSubtract ( ent->s.origin, origin, self->s.origin2 );
185 self->s.origin2[2] = 0;
186 dist = VectorNormalize( self->s.origin2);
187
188 forward = dist / time;
189 VectorScale( self->s.origin2, forward, self->s.origin2 );
190
191 self->s.origin2[2] = time * gravity;
192 }
193
194
195 /*QUAKED trigger_push (.5 .5 .5) ?
196 Must point at a target_position, which will be the apex of the leap.
197 This will be client side predicted, unlike target_push
198 */
SP_trigger_push(gentity_t * self)199 void SP_trigger_push( gentity_t *self ) {
200 InitTrigger (self);
201
202 // unlike other triggers, we need to send this one to the client
203 self->r.svFlags &= ~SVF_NOCLIENT;
204
205 // make sure the client precaches this sound
206 G_SoundIndex("sound/world/jumppad.wav");
207
208 self->s.eType = ET_PUSH_TRIGGER;
209 self->touch = trigger_push_touch;
210 self->think = AimAtTarget;
211 self->nextthink = level.time + FRAMETIME;
212 trap_LinkEntity (self);
213 }
214
215
Use_target_push(gentity_t * self,gentity_t * other,gentity_t * activator)216 void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) {
217 if ( !activator->client ) {
218 return;
219 }
220
221 if ( activator->client->ps.pm_type != PM_NORMAL ) {
222 return;
223 }
224 if ( activator->client->ps.powerups[PW_FLIGHT] ) {
225 return;
226 }
227
228 VectorCopy (self->s.origin2, activator->client->ps.velocity);
229
230 // play fly sound every 1.5 seconds
231 if ( activator->fly_sound_debounce_time < level.time ) {
232 activator->fly_sound_debounce_time = level.time + 1500;
233 G_Sound( activator, CHAN_AUTO, self->noise_index );
234 }
235 }
236
237 /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad
238 Pushes the activator in the direction.of angle, or towards a target apex.
239 "speed" defaults to 1000
240 if "bouncepad", play bounce noise instead of windfly
241 */
SP_target_push(gentity_t * self)242 void SP_target_push( gentity_t *self ) {
243 if (!self->speed) {
244 self->speed = 1000;
245 }
246 G_SetMovedir (self->s.angles, self->s.origin2);
247 VectorScale (self->s.origin2, self->speed, self->s.origin2);
248
249 if ( self->spawnflags & 1 ) {
250 self->noise_index = G_SoundIndex("sound/world/jumppad.wav");
251 } else {
252 self->noise_index = G_SoundIndex("sound/misc/windfly.wav");
253 }
254 if ( self->target ) {
255 VectorCopy( self->s.origin, self->r.absmin );
256 VectorCopy( self->s.origin, self->r.absmax );
257 self->think = AimAtTarget;
258 self->nextthink = level.time + FRAMETIME;
259 }
260 self->use = Use_target_push;
261 }
262
263 /*
264 ==============================================================================
265
266 trigger_teleport
267
268 ==============================================================================
269 */
270
trigger_teleporter_touch(gentity_t * self,gentity_t * other,trace_t * trace)271 void trigger_teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace ) {
272 gentity_t *dest;
273
274 if ( !other->client ) {
275 return;
276 }
277 if ( other->client->ps.pm_type == PM_DEAD ) {
278 return;
279 }
280 // Spectators only?
281 if ( ( self->spawnflags & 1 ) &&
282 other->client->sess.sessionTeam != TEAM_SPECTATOR ) {
283 return;
284 }
285
286
287 dest = G_PickTarget( self->target );
288 if (!dest) {
289 G_Printf ("Couldn't find teleporter destination\n");
290 return;
291 }
292
293 TeleportPlayer( other, dest->s.origin, dest->s.angles );
294 }
295
296
297 /*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR
298 Allows client side prediction of teleportation events.
299 Must point at a target_position, which will be the teleport destination.
300
301 If spectator is set, only spectators can use this teleport
302 Spectator teleporters are not normally placed in the editor, but are created
303 automatically near doors to allow spectators to move through them
304 */
SP_trigger_teleport(gentity_t * self)305 void SP_trigger_teleport( gentity_t *self ) {
306 InitTrigger (self);
307
308 // unlike other triggers, we need to send this one to the client
309 // unless is a spectator trigger
310 if ( self->spawnflags & 1 ) {
311 self->r.svFlags |= SVF_NOCLIENT;
312 } else {
313 self->r.svFlags &= ~SVF_NOCLIENT;
314 }
315
316 // make sure the client precaches this sound
317 G_SoundIndex("sound/world/jumppad.wav");
318
319 self->s.eType = ET_TELEPORT_TRIGGER;
320 self->touch = trigger_teleporter_touch;
321
322 trap_LinkEntity (self);
323 }
324
325
326 /*
327 ==============================================================================
328
329 trigger_hurt
330
331 ==============================================================================
332 */
333
334 /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW
335 Any entity that touches this will be hurt.
336 It does dmg points of damage each server frame
337 Targeting the trigger will toggle its on / off state.
338
339 SILENT supresses playing the sound
340 SLOW changes the damage rate to once per second
341 NO_PROTECTION *nothing* stops the damage
342
343 "dmg" default 5 (whole numbers only)
344
345 */
hurt_use(gentity_t * self,gentity_t * other,gentity_t * activator)346 void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
347 if ( self->r.linked ) {
348 trap_UnlinkEntity( self );
349 } else {
350 trap_LinkEntity( self );
351 }
352 }
353
hurt_touch(gentity_t * self,gentity_t * other,trace_t * trace)354 void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
355 int dflags;
356
357 if ( !other->takedamage ) {
358 return;
359 }
360
361 if ( self->timestamp > level.time ) {
362 return;
363 }
364
365 if ( self->spawnflags & 16 ) {
366 self->timestamp = level.time + 1000;
367 } else {
368 self->timestamp = level.time + FRAMETIME;
369 }
370
371 // play sound
372 if ( !(self->spawnflags & 4) ) {
373 G_Sound( other, CHAN_AUTO, self->noise_index );
374 }
375
376 if (self->spawnflags & 8)
377 dflags = DAMAGE_NO_PROTECTION;
378 else
379 dflags = 0;
380 G_Damage (other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT);
381 }
382
SP_trigger_hurt(gentity_t * self)383 void SP_trigger_hurt( gentity_t *self ) {
384 InitTrigger (self);
385
386 self->noise_index = G_SoundIndex( "sound/world/electro.wav" );
387 self->touch = hurt_touch;
388
389 if ( !self->damage ) {
390 self->damage = 5;
391 }
392
393 self->r.contents = CONTENTS_TRIGGER;
394
395 if ( self->spawnflags & 2 ) {
396 self->use = hurt_use;
397 }
398
399 // link in to the world if starting active
400 if ( ! (self->spawnflags & 1) ) {
401 trap_LinkEntity (self);
402 }
403 }
404
405
406 /*
407 ==============================================================================
408
409 timer
410
411 ==============================================================================
412 */
413
414
415 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
416 This should be renamed trigger_timer...
417 Repeatedly fires its targets.
418 Can be turned on or off by using.
419
420 "wait" base time between triggering all targets, default is 1
421 "random" wait variance, default is 0
422 so, the basic time between firing is a random time between
423 (wait - random) and (wait + random)
424
425 */
func_timer_think(gentity_t * self)426 void func_timer_think( gentity_t *self ) {
427 G_UseTargets (self, self->activator);
428 // set time before next firing
429 self->nextthink = level.time + 1000 * ( self->wait + crandom() * self->random );
430 }
431
func_timer_use(gentity_t * self,gentity_t * other,gentity_t * activator)432 void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) {
433 self->activator = activator;
434
435 // if on, turn it off
436 if ( self->nextthink ) {
437 self->nextthink = 0;
438 return;
439 }
440
441 // turn it on
442 func_timer_think (self);
443 }
444
SP_func_timer(gentity_t * self)445 void SP_func_timer( gentity_t *self ) {
446 G_SpawnFloat( "random", "1", &self->random);
447 G_SpawnFloat( "wait", "1", &self->wait );
448
449 self->use = func_timer_use;
450 self->think = func_timer_think;
451
452 if ( self->random >= self->wait ) {
453 self->random = self->wait - FRAMETIME;
454 G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
455 }
456
457 if ( self->spawnflags & 1 ) {
458 self->nextthink = level.time + FRAMETIME;
459 self->activator = self;
460 }
461
462 self->r.svFlags = SVF_NOCLIENT;
463 }
464
465
466