mirror of
https://github.com/ioquake/jedi-outcast.git
synced 2024-11-10 07:11:42 +00:00
791 lines
No EOL
22 KiB
C++
791 lines
No EOL
22 KiB
C++
// leave this line at the top for all g_xxxx.cpp files...
|
|
#include "g_headers.h"
|
|
|
|
|
|
#include "g_local.h"
|
|
#include "b_local.h"
|
|
#include "g_functions.h"
|
|
|
|
extern int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create );
|
|
|
|
#define FX_ENT_RADIUS 32
|
|
|
|
extern int BMS_START;
|
|
extern int BMS_MID;
|
|
extern int BMS_END;
|
|
|
|
//----------------------------------------------------------
|
|
|
|
/*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT DAMAGE
|
|
Runs the specified effect, can also be targeted at an info_notnull to orient the effect
|
|
|
|
STARTOFF - effect starts off, toggles on/off when used
|
|
ONESHOT - effect fires only when used
|
|
DAMAGE - does radius damage around effect every "delay" milliseonds
|
|
|
|
"fxFile" - name of the effect file to play
|
|
"target" - direction to aim the effect in, otherwise defaults to up
|
|
"target2" - uses its target2 when the fx gets triggered
|
|
"delay" - how often to call the effect, don't over-do this ( default 200 )
|
|
"random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms )
|
|
"splashRadius" - only works when damage is checked ( default 16 )
|
|
"splashDamage" - only works when damage is checked ( default 5 )
|
|
"soundset" - bmodel set to use, plays start sound when toggled on, loop sound while on ( doesn't play on a oneshot), and a stop sound when turned off
|
|
*/
|
|
#define FX_RUNNER_RESERVED 0x800000
|
|
|
|
//----------------------------------------------------------
|
|
void fx_runner_think( gentity_t *ent )
|
|
{
|
|
vec3_t temp;
|
|
|
|
EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin );
|
|
EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
|
|
|
|
// call the effect with the desired position and orientation
|
|
G_AddEvent( ent, EV_PLAY_EFFECT, ent->fxID );
|
|
|
|
// Assume angles, we'll do a cross product on the other end to finish up
|
|
AngleVectors( ent->currentAngles, ent->pos3, NULL, NULL );
|
|
MakeNormalVectors( ent->pos3, ent->pos4, temp ); // there IS a reason this is done...it's so that it doesn't break every effect in the game...
|
|
|
|
ent->nextthink = level.time + ent->delay + random() * ent->random;
|
|
|
|
if ( ent->spawnflags & 4 ) // damage
|
|
{
|
|
G_RadiusDamage( ent->currentOrigin, ent, ent->splashDamage, ent->splashRadius, ent, MOD_UNKNOWN );
|
|
}
|
|
|
|
if ( ent->target2 )
|
|
{
|
|
// let our target know that we have spawned an effect
|
|
G_UseTargets2( ent, ent, ent->target2 );
|
|
}
|
|
|
|
if ( !(ent->spawnflags & 2 ) && !ent->s.loopSound ) // NOT ONESHOT...this is an assy thing to do
|
|
{
|
|
if ( VALIDSTRING( ent->soundSet ) == true )
|
|
{
|
|
ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID );
|
|
|
|
if ( ent->s.loopSound < 0 )
|
|
{
|
|
ent->s.loopSound = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
if ( self->spawnflags & 2 ) // ONESHOT
|
|
{
|
|
// call the effect with the desired position and orientation, as a safety thing,
|
|
// make sure we aren't thinking at all.
|
|
fx_runner_think( self );
|
|
self->nextthink = -1;
|
|
|
|
if ( self->target2 )
|
|
{
|
|
// let our target know that we have spawned an effect
|
|
G_UseTargets2( self, self, self->target2 );
|
|
}
|
|
|
|
if ( VALIDSTRING( self->soundSet ) == true )
|
|
{
|
|
G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ensure we are working with the right think function
|
|
self->e_ThinkFunc = thinkF_fx_runner_think;
|
|
|
|
// toggle our state
|
|
if ( self->nextthink == -1 )
|
|
{
|
|
// NOTE: we fire the effect immediately on use, the fx_runner_think func will set
|
|
// up the nextthink time.
|
|
fx_runner_think( self );
|
|
|
|
if ( VALIDSTRING( self->soundSet ) == true )
|
|
{
|
|
G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
|
|
self->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID );
|
|
|
|
if ( self->s.loopSound < 0 )
|
|
{
|
|
self->s.loopSound = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// turn off for now
|
|
self->nextthink = -1;
|
|
|
|
if ( VALIDSTRING( self->soundSet ) == true )
|
|
{
|
|
G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_END ));
|
|
self->s.loopSound = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
void fx_runner_link( gentity_t *ent )
|
|
{
|
|
vec3_t dir;
|
|
|
|
if ( ent->target )
|
|
{
|
|
// try to use the target to override the orientation
|
|
gentity_t *target = NULL;
|
|
|
|
target = G_Find( target, FOFS(targetname), ent->target );
|
|
|
|
if ( !target )
|
|
{
|
|
// Bah, no good, dump a warning, but continue on and use the UP vector
|
|
Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target );
|
|
Com_Printf( " -assuming UP orientation.\n" );
|
|
}
|
|
else
|
|
{
|
|
// Our target is valid so let's override the default UP vector
|
|
VectorSubtract( target->s.origin, ent->s.origin, dir );
|
|
VectorNormalize( dir );
|
|
vectoangles( dir, ent->s.angles );
|
|
}
|
|
}
|
|
|
|
// don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus
|
|
if ( ent->target2 )
|
|
{
|
|
gentity_t *target = NULL;
|
|
|
|
target = G_Find( target, FOFS(targetname), ent->target2 );
|
|
|
|
if ( !target )
|
|
{
|
|
// Target2 is bogus, but we can still continue
|
|
Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 );
|
|
}
|
|
}
|
|
|
|
G_SetAngles( ent, ent->s.angles );
|
|
|
|
if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT
|
|
{
|
|
// We won't even consider thinking until we are used
|
|
ent->nextthink = -1;
|
|
}
|
|
else
|
|
{
|
|
if ( VALIDSTRING( ent->soundSet ) == true )
|
|
{
|
|
ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID );
|
|
|
|
if ( ent->s.loopSound < 0 )
|
|
{
|
|
ent->s.loopSound = 0;
|
|
}
|
|
}
|
|
|
|
// Let's get to work right now!
|
|
ent->e_ThinkFunc = thinkF_fx_runner_think;
|
|
ent->nextthink = level.time + 200; // wait a small bit, then start working
|
|
}
|
|
|
|
// make us useable if we can be targeted
|
|
if ( ent->targetname )
|
|
{
|
|
ent->e_UseFunc = useF_fx_runner_use;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
void SP_fx_runner( gentity_t *ent )
|
|
{
|
|
// Get our defaults
|
|
G_SpawnInt( "delay", "200", &ent->delay );
|
|
G_SpawnFloat( "random", "0", &ent->random );
|
|
G_SpawnInt( "splashRadius", "16", &ent->splashRadius );
|
|
G_SpawnInt( "splashDamage", "5", &ent->splashDamage );
|
|
|
|
if ( !G_SpawnAngleHack( "angle", "0", ent->s.angles ))
|
|
{
|
|
// didn't have angles, so give us the default of up
|
|
VectorSet( ent->s.angles, -90, 0, 0 );
|
|
}
|
|
|
|
if ( !ent->fxFile )
|
|
{
|
|
gi.Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) );
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// Try and associate an effect file, unfortunately we won't know if this worked or not
|
|
// until the CGAME trys to register it...
|
|
ent->fxID = G_EffectIndex( ent->fxFile );
|
|
|
|
ent->s.eType = ET_MOVER;
|
|
|
|
// Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
|
|
ent->e_ThinkFunc = thinkF_fx_runner_link;
|
|
ent->nextthink = level.time + 400;
|
|
|
|
// Save our position and link us up!
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
|
|
VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
|
|
VectorScale( ent->maxs, -1, ent->mins );
|
|
|
|
gi.linkentity( ent );
|
|
}
|
|
|
|
|
|
/*QUAKED fx_snow (1 0 0) (-16 -16 -16) (16 16 16)
|
|
This world effect will spawn snow globally into the level.
|
|
|
|
"count" the number of snow particles (default of 1000)
|
|
*/
|
|
//----------------------------------------------------------
|
|
void SP_CreateSnow( gentity_t *ent )
|
|
{
|
|
char temp[256];
|
|
|
|
G_SpawnInt( "count", "1000", &ent->count );
|
|
|
|
cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
|
|
|
|
if ( r_weatherScale->value > 0.0f )
|
|
{
|
|
sprintf( temp, "snow init %i", (int)( ent->count * r_weatherScale->value ));
|
|
|
|
G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue );
|
|
|
|
level.worldFlags |= WF_SNOWING;
|
|
}
|
|
}
|
|
|
|
/*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16)
|
|
This world effect will spawn rain globally into the level.
|
|
|
|
"count" the number of rain particles (default of 500)
|
|
*/
|
|
//----------------------------------------------------------
|
|
void SP_CreateRain( gentity_t *ent )
|
|
{
|
|
char temp[256];
|
|
|
|
G_SpawnInt( "count", "500", &ent->count );
|
|
|
|
cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
|
|
|
|
if ( r_weatherScale->value > 0.0f )
|
|
{
|
|
sprintf( temp, "rain init %i", (int)( ent->count * r_weatherScale->value ));
|
|
|
|
G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue );
|
|
|
|
level.worldFlags |= WF_RAINING;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------
|
|
// Explosion Trail
|
|
//-----------------
|
|
|
|
//----------------------------------------------------------
|
|
void fx_explosion_trail_think( gentity_t *ent )
|
|
{
|
|
vec3_t origin;
|
|
trace_t tr;
|
|
|
|
if ( ent->spawnflags & 1 ) // gravity
|
|
{
|
|
ent->s.pos.trType = TR_GRAVITY;
|
|
}
|
|
else
|
|
{
|
|
ent->s.pos.trType = TR_LINEAR;
|
|
}
|
|
|
|
EvaluateTrajectory( &ent->s.pos, level.time, origin );
|
|
|
|
gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, origin,
|
|
ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 );
|
|
|
|
if ( tr.fraction < 1.0f )
|
|
{
|
|
// never explode or bounce on sky
|
|
if ( !( tr.surfaceFlags & SURF_NOIMPACT ))
|
|
{
|
|
if ( ent->splashDamage && ent->splashRadius )
|
|
{
|
|
G_RadiusDamage( tr.endpos, ent, ent->splashDamage, ent->splashRadius, ent, MOD_EXPLOSIVE_SPLASH );
|
|
}
|
|
}
|
|
|
|
if ( ent->fullName )
|
|
{
|
|
// fxFile2....in other words, impact fx
|
|
G_PlayEffect( ent->fullName, tr.endpos, tr.plane.normal );
|
|
}
|
|
|
|
if ( VALIDSTRING( ent->soundSet ) == true )
|
|
{
|
|
G_AddEvent( ent, EV_BMODEL_SOUND, CAS_GetBModelSound( ent->soundSet, BMS_END ));
|
|
}
|
|
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
G_RadiusDamage( origin, ent, ent->damage, ent->radius, ent, MOD_EXPLOSIVE_SPLASH );
|
|
|
|
// call the effect with the desired position and orientation
|
|
G_PlayEffect( ent->fxID, origin, ent->currentAngles );
|
|
|
|
ent->nextthink = level.time + 50;
|
|
gi.linkentity( ent );
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
void fx_explosion_trail_use( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
gentity_t *missile = G_Spawn();
|
|
|
|
// We aren't a missile in the truest sense, rather we just move through the world and spawn effects
|
|
if ( missile )
|
|
{
|
|
missile->classname = "fx_exp_trail";
|
|
|
|
missile->nextthink = level.time + 50;
|
|
missile->e_ThinkFunc = thinkF_fx_explosion_trail_think;
|
|
|
|
missile->s.eType = ET_MOVER;
|
|
|
|
missile->owner = self;
|
|
|
|
missile->s.modelindex = self->s.modelindex2;
|
|
|
|
missile->s.pos.trTime = level.time;
|
|
G_SetOrigin( missile, self->currentOrigin );
|
|
if ( self->spawnflags & 1 ) // gravity
|
|
{
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
}
|
|
else
|
|
{
|
|
missile->s.pos.trType = TR_LINEAR;
|
|
}
|
|
|
|
missile->spawnflags = self->spawnflags;
|
|
|
|
G_SetAngles( missile, self->currentAngles );
|
|
VectorScale( self->currentAngles, self->speed, missile->s.pos.trDelta );
|
|
missile->s.pos.trTime = level.time;
|
|
missile->radius = self->radius;
|
|
missile->damage = self->damage;
|
|
missile->splashDamage = self->splashDamage;
|
|
missile->splashRadius = self->splashRadius;
|
|
missile->fxID = self->fxID;
|
|
missile->fullName = self->fullName;
|
|
|
|
missile->clipmask = MASK_SHOT;
|
|
|
|
gi.linkentity( missile );
|
|
|
|
if ( VALIDSTRING( self->soundSet ) == true )
|
|
{
|
|
G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
|
|
missile->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID );
|
|
missile->soundSet = self->soundSet;
|
|
|
|
if ( missile->s.loopSound < 0 )
|
|
{
|
|
missile->s.loopSound = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
void fx_explosion_trail_link( gentity_t *ent )
|
|
{
|
|
vec3_t dir;
|
|
gentity_t *target = NULL;
|
|
|
|
// we ony activate when used
|
|
ent->e_UseFunc = useF_fx_explosion_trail_use;
|
|
|
|
if ( ent->target )
|
|
{
|
|
// try to use the target to override the orientation
|
|
target = G_Find( target, FOFS(targetname), ent->target );
|
|
|
|
if ( !target )
|
|
{
|
|
gi.Printf( S_COLOR_RED"ERROR: fx_explosion_trail %s could not find target %s\n", ent->targetname, ent->target );
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// Our target is valid so lets use that
|
|
VectorSubtract( target->s.origin, ent->s.origin, dir );
|
|
VectorNormalize( dir );
|
|
}
|
|
else
|
|
{
|
|
// we are assuming that we have angles, but there are no checks to verify this
|
|
AngleVectors( ent->s.angles, dir, NULL, NULL );
|
|
}
|
|
|
|
// NOTE: this really isn't an angle, but rather an orientation vector
|
|
G_SetAngles( ent, dir );
|
|
}
|
|
|
|
/*QUAKED fx_explosion_trail (0 0 1) (-8 -8 -8) (8 8 8) GRAVITY
|
|
Creates an explosion type trail using the specified effect file, damaging things as it moves through the environment
|
|
Can also be used for something like a meteor, just add an impact effect ( fxFile2 ) and a splashDamage and splashRadius
|
|
|
|
GRAVITY - object uses gravity instead of linear motion
|
|
|
|
"fxFile" - name of the effect to play for the trail ( default "env/exp_trail_comp" )
|
|
"fxFile2" - effect file to play on impact
|
|
|
|
"model" - model to attach to the trail
|
|
|
|
"target" - direction to aim the trail in, required unless you specify angles
|
|
"targetname" - (required) trail effect spawns only when used.
|
|
"speed" - velocity through the world, ( default 350 )
|
|
|
|
"radius" - damage radius around trail as it travels through the world ( default 128 )
|
|
"damage" - radius damage ( default 128 )
|
|
"splashDamage" - damage when thing impacts ( default 0 )
|
|
"splashRadius" - damage radius on impact ( default 0 )
|
|
"soundset" - soundset to use, start sound plays when explosion trail starts, loop sound plays on explosion trail, end sound plays when it impacts
|
|
|
|
*/
|
|
//----------------------------------------------------------
|
|
void SP_fx_explosion_trail( gentity_t *ent )
|
|
{
|
|
// We have to be useable, otherwise we won't spawn in
|
|
if ( !ent->targetname )
|
|
{
|
|
gi.Printf( S_COLOR_RED"ERROR: fx_explosion_trail at %s has no targetname specified\n", vtos( ent->s.origin ));
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// Get our defaults
|
|
G_SpawnString( "fxFile", "env/exp_trail_comp", &ent->fxFile );
|
|
G_SpawnInt( "damage", "128", &ent->damage );
|
|
G_SpawnFloat( "radius", "128", &ent->radius );
|
|
G_SpawnFloat( "speed", "350", &ent->speed );
|
|
|
|
// Try and associate an effect file, unfortunately we won't know if this worked or not until the CGAME trys to register it...
|
|
ent->fxID = G_EffectIndex( ent->fxFile );
|
|
|
|
if ( ent->fullName )
|
|
{
|
|
G_EffectIndex( ent->fullName );
|
|
}
|
|
|
|
if ( ent->model )
|
|
{
|
|
ent->s.modelindex2 = G_ModelIndex( ent->model );
|
|
}
|
|
|
|
// Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
|
|
ent->e_ThinkFunc = thinkF_fx_explosion_trail_link;
|
|
ent->nextthink = level.time + 500;
|
|
|
|
// Save our position and link us up!
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
|
|
VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
|
|
VectorScale( ent->maxs, -1, ent->mins );
|
|
|
|
gi.linkentity( ent );
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
|
|
//------------------------------------------
|
|
void fx_target_beam_set_debounce( gentity_t *self )
|
|
{
|
|
if ( self->wait >= FRAMETIME )
|
|
{
|
|
self->attackDebounceTime = level.time + self->wait + Q_irand( -self->random, self->random );
|
|
}
|
|
else if ( self->wait < 0 )
|
|
{
|
|
self->e_UseFunc = useF_NULL;
|
|
}
|
|
else
|
|
{
|
|
self->attackDebounceTime = level.time + FRAMETIME + Q_irand( -self->random, self->random );
|
|
}
|
|
}
|
|
|
|
//------------------------------------------
|
|
void fx_target_beam_fire( gentity_t *ent )
|
|
{
|
|
trace_t trace;
|
|
vec3_t dir, org, end;
|
|
int ignore;
|
|
qboolean open;
|
|
|
|
if ( !ent->enemy || !ent->enemy->inuse )
|
|
{//info_null most likely
|
|
ignore = ent->s.number;
|
|
ent->enemy = NULL;
|
|
VectorCopy( ent->s.origin2, org );
|
|
}
|
|
else
|
|
{
|
|
ignore = ent->enemy->s.number;
|
|
VectorCopy( ent->enemy->currentOrigin, org );
|
|
}
|
|
|
|
VectorCopy( org, ent->s.origin2 );
|
|
VectorSubtract( org, ent->s.origin, dir );
|
|
VectorNormalize( dir );
|
|
|
|
gi.trace( &trace, ent->s.origin, NULL, NULL, org, ENTITYNUM_NONE, MASK_SHOT );//ignore
|
|
if ( ent->spawnflags & 2 )
|
|
{
|
|
open = qtrue;
|
|
VectorCopy( org, end );
|
|
}
|
|
else
|
|
{
|
|
open = qfalse;
|
|
VectorCopy( trace.endpos, end );
|
|
}
|
|
|
|
if ( trace.fraction < 1.0 )
|
|
{
|
|
if ( trace.entityNum < ENTITYNUM_WORLD )
|
|
{
|
|
gentity_t *victim = &g_entities[trace.entityNum];
|
|
if ( victim && victim->takedamage )
|
|
{
|
|
if ( ent->spawnflags & 4 ) // NO_KNOCKBACK
|
|
{
|
|
G_Damage( victim, ent, ent->activator, dir, trace.endpos, ent->damage, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN );
|
|
}
|
|
else
|
|
{
|
|
G_Damage( victim, ent, ent->activator, dir, trace.endpos, ent->damage, 0, MOD_UNKNOWN );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
G_AddEvent( ent, EV_TARGET_BEAM_DRAW, ent->fxID );
|
|
VectorCopy( end, ent->s.origin2 );
|
|
|
|
if ( open )
|
|
{
|
|
VectorScale( dir, -1, ent->pos1 );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( trace.plane.normal, ent->pos1 );
|
|
}
|
|
|
|
ent->e_ThinkFunc = thinkF_fx_target_beam_think;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
//------------------------------------------
|
|
void fx_target_beam_fire_start( gentity_t *self )
|
|
{
|
|
fx_target_beam_set_debounce( self );
|
|
self->e_ThinkFunc = thinkF_fx_target_beam_think;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->painDebounceTime = level.time + self->speed + Q_irand( -500, 500 );
|
|
fx_target_beam_fire( self );
|
|
}
|
|
|
|
//------------------------------------------
|
|
void fx_target_beam_use( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
if ( self->spawnflags & 8 ) // one shot
|
|
{
|
|
fx_target_beam_fire( self );
|
|
self->e_ThinkFunc = thinkF_NULL;
|
|
}
|
|
else if ( self->e_ThinkFunc == thinkF_NULL )
|
|
{
|
|
self->e_ThinkFunc = thinkF_fx_target_beam_think;
|
|
self->nextthink = level.time + 50;
|
|
}
|
|
else
|
|
{
|
|
self->e_ThinkFunc = thinkF_NULL;
|
|
}
|
|
|
|
self->activator = activator;
|
|
}
|
|
|
|
//------------------------------------------
|
|
void fx_target_beam_think( gentity_t *ent )
|
|
{
|
|
if ( ent->attackDebounceTime > level.time )
|
|
{
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
|
|
fx_target_beam_fire_start( ent );
|
|
}
|
|
|
|
//------------------------------------------
|
|
void fx_target_beam_link( gentity_t *ent )
|
|
{
|
|
gentity_t *target = NULL;
|
|
vec3_t dir;
|
|
float len;
|
|
|
|
target = G_Find( target, FOFS(targetname), ent->target );
|
|
|
|
if ( !target )
|
|
{
|
|
Com_Printf( "bolt_link: unable to find target %s\n", ent->target );
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
ent->attackDebounceTime = level.time;
|
|
|
|
if ( !target->classname || Q_stricmp( "info_null", target->classname ) )
|
|
{//don't want to set enemy to something that's going to free itself... actually, this could be bad in other ways, too... ent pointer could be freed up and re-used by the time we check it next
|
|
G_SetEnemy( ent, target );
|
|
}
|
|
VectorSubtract( target->s.origin, ent->s.origin, dir );//er, does it ever use dir?
|
|
len = VectorNormalize( dir );//er, does it use len or dir?
|
|
vectoangles( dir, ent->s.angles );//er, does it use s.angles?
|
|
|
|
VectorCopy( target->s.origin, ent->s.origin2 );
|
|
|
|
if ( ent->spawnflags & 1 )
|
|
{
|
|
// Do nothing
|
|
ent->e_ThinkFunc = thinkF_NULL;
|
|
}
|
|
else
|
|
{
|
|
if ( !(ent->spawnflags & 8 )) // one_shot, only calls when used
|
|
{
|
|
// switch think functions to avoid doing the bolt_link every time
|
|
ent->e_ThinkFunc = thinkF_fx_target_beam_think;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
ent->e_UseFunc = useF_fx_target_beam_use;
|
|
gi.linkentity( ent );
|
|
}
|
|
|
|
/*QUAKED fx_target_beam (1 0.5 0.5) (-8 -8 -8) (8 8 8) STARTOFF OPEN NO_KNOCKBACK ONE_SHOT NO_IMPACT
|
|
Emits specified effect file, doing damage if required
|
|
|
|
STARTOFF - must be used before it's on
|
|
OPEN - will draw all the way to the target, regardless of where the trace hits
|
|
NO_KNOCKBACK - beam damage does no knockback
|
|
|
|
"fxFile" - Effect used to draw the beam, ( default "env/targ_beam" )
|
|
"fxFile2" - Effect used for the beam impact effect, ( default "env/targ_beam_impact" )
|
|
"targetname" - Fires only when used
|
|
"duration" - How many seconds each burst lasts, -1 will make it stay on forever
|
|
"wait" - If always on, how long to wait between blasts, in MILLISECONDS - default/min is 100 (1 frame at 10 fps), -1 means it will never fire again
|
|
"random" - random amount of seconds added to/subtracted from "wait" each firing
|
|
"damage" - How much damage to inflict PER FRAME (so probably want it kind of low), default is none
|
|
"target" - ent to point at- you MUST have this. This can be anything you want, including a moving ent - for static beams, just use info_null
|
|
*/
|
|
//------------------------------------------
|
|
void SP_fx_target_beam( gentity_t *ent )
|
|
{
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
|
|
ent->speed *= 1000;
|
|
ent->wait *= 1000;
|
|
ent->random *= 1000;
|
|
|
|
if ( ent->speed < FRAMETIME )
|
|
{
|
|
ent->speed = FRAMETIME;
|
|
}
|
|
|
|
G_SpawnInt( "damage", "0", &ent->damage );
|
|
G_SpawnString( "fxFile", "env/targ_beam", &ent->fxFile );
|
|
|
|
if ( ent->spawnflags & 16 ) // NO_IMPACT FX
|
|
{
|
|
ent->delay = 0;
|
|
}
|
|
else
|
|
{
|
|
G_SpawnString( "fxFile2", "env/targ_beam_impact", &ent->fullName );
|
|
ent->delay = G_EffectIndex( ent->fullName );
|
|
}
|
|
|
|
ent->fxID = G_EffectIndex( ent->fxFile );
|
|
|
|
ent->activator = ent;
|
|
ent->owner = NULL;
|
|
|
|
ent->e_ThinkFunc = thinkF_fx_target_beam_link;
|
|
ent->nextthink = level.time + START_TIME_LINK_ENTS;
|
|
|
|
VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
|
|
VectorScale( ent->maxs, -1, ent->mins );
|
|
|
|
gi.linkentity( ent );
|
|
}
|
|
|
|
|
|
/*QUAKED fx_cloudlayer (1 0.3 0.5) (-8 -8 -8) (8 8 8) TUBE ALT
|
|
|
|
Creates a scalable scrolling cloud layer, mostly for bespin undercity but could be used other places
|
|
|
|
TUBE - creates cloud layer with tube opening in the middle, must an INNER radius also
|
|
ALT - uses slightly different shader, good if using two layers sort of close together
|
|
|
|
"radius" - outer radius of cloud layer, (default 2048)
|
|
"random" - inner radius of cloud layer, (default 128) only works for TUBE type
|
|
"wait" - adds curvature as it moves out to the edge of the layer. ( default 0 ), 1 = small up, 3 = up more, -1 = small down, -3 = down more, etc.
|
|
|
|
*/
|
|
|
|
void SP_fx_cloudlayer( gentity_t *ent )
|
|
{
|
|
// HACK: this effect is never played, rather it just caches the shaders I need cgame side
|
|
G_EffectIndex( "world/haze_cache" );
|
|
|
|
G_SpawnFloat( "radius", "2048", &ent->radius );
|
|
G_SpawnFloat( "random", "128", &ent->random );
|
|
G_SpawnFloat( "wait", "0", &ent->wait );
|
|
|
|
ent->s.eType = ET_CLOUD; // dumb
|
|
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
|
|
ent->contents = 0;
|
|
VectorSet( ent->maxs, 200, 200, 200 );
|
|
VectorScale( ent->maxs, -1, ent->mins );
|
|
|
|
gi.linkentity( ent );
|
|
} |