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