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