Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_trigger.c (11863B)


      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 Foobar; 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 
     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
     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
     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 
     76 void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
     77 	multi_trigger( ent, activator );
     78 }
     79 
     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 */
     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 
    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 */
    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 
    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 */
    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 */
    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 
    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 */
    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 
    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 */
    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 */
    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 
    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 
    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 */
    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 
    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 
    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