jkxr/Projects/Android/jni/OpenJK/code/game/g_fx.cpp
Simon 4597b03873 Initial Commit
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2022-09-18 16:37:21 +01:00

1253 lines
34 KiB
C++

/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "g_local.h"
#include "g_functions.h"
#include "b_local.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 + Q_flrand(0.0f, 1.0f) * 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->s.isPortalEnt)
{ //rww - mark it as broadcast upon first use if it's within the area of a skyportal
self->svFlags |= SVF_BROADCAST;
}
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) LIGHT MEDIUM HEAVY MISTY_FOG
This world effect will spawn snow globally into the level.
*/
void SP_CreateSnow( gentity_t *ent )
{
cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
if ( r_weatherScale->value == 0.0f )
{
return;
}
// Different Types Of Rain
//-------------------------
if (ent->spawnflags & 1)
{
G_FindConfigstringIndex("lightsnow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
else if (ent->spawnflags & 2)
{
G_FindConfigstringIndex("snow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
else if (ent->spawnflags & 4)
{
G_FindConfigstringIndex("heavysnow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
else
{
G_FindConfigstringIndex("snow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// MISTY FOG
//===========
if (ent->spawnflags & 8)
{
G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
}
/*QUAKED fx_wind (0 .5 .8) (-16 -16 -16) (16 16 16) NORMAL CONSTANT GUSTING SWIRLING x FOG LIGHT_FOG
Generates global wind forces
NORMAL creates a random light global wind
CONSTANT forces all wind to go in a specified direction
GUSTING causes random gusts of wind
SWIRLING causes random swirls of wind
"angles" the direction for constant wind
"speed" the speed for constant wind
*/
void SP_CreateWind( gentity_t *ent )
{
cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
if ( r_weatherScale->value <= 0.0f )
{
return;
}
char temp[256];
// Normal Wind
//-------------
if (ent->spawnflags & 1)
{
G_FindConfigstringIndex("wind", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// Constant Wind
//---------------
if (ent->spawnflags & 2)
{
vec3_t windDir;
AngleVectors(ent->s.angles, windDir, 0, 0);
G_SpawnFloat( "speed", "500", &ent->speed );
VectorScale(windDir, ent->speed, windDir);
sprintf( temp, "constantwind ( %f %f %f )", windDir[0], windDir[1], windDir[2] );
G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// Gusting Wind
//--------------
if (ent->spawnflags & 4)
{
G_FindConfigstringIndex("gustingwind", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// Swirling Wind
//---------------
if (ent->spawnflags & 8)
{
G_FindConfigstringIndex("swirlingwind", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// MISTY FOG
//===========
if (ent->spawnflags & 32)
{
G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// MISTY FOG
//===========
if (ent->spawnflags & 64)
{
G_FindConfigstringIndex("light_fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
}
/*QUAKED fx_wind_zone (0 .5 .8) ? Creates a constant wind in a local area
Generates local wind forces
"angles" the direction for constant wind
"speed" the speed for constant wind
*/
void SP_CreateWindZone( gentity_t *ent )
{
cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
if ( r_weatherScale->value <= 0.0f )
{
return;
}
gi.SetBrushModel(ent, ent->model);
vec3_t windDir;
AngleVectors(ent->s.angles, windDir, 0, 0);
G_SpawnFloat( "speed", "500", &ent->speed );
VectorScale(windDir, ent->speed, windDir);
char temp[256];
sprintf( temp, "windzone ( %f %f %f ) ( %f %f %f ) ( %f %f %f )",
ent->mins[0], ent->mins[1], ent->mins[2],
ent->maxs[0], ent->maxs[1], ent->maxs[2],
windDir[0], windDir[1], windDir[2]
);
G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
/*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16) LIGHT MEDIUM HEAVY ACID OUTSIDE_SHAKE MISTY_FOG
This world effect will spawn rain globally into the level.
LIGHT create light drizzle
MEDIUM create average medium rain
HEAVY create heavy downpour (with fog and lightning automatically)
ACID create acid rain
OUTSIDE_SHAKE will cause the camera to shake slightly whenever outside
MISTY_FOG causes clouds of misty fog to float through the level
LIGHTNING causes random bursts of lightning and thunder in the level
The following fields are for lightning:
"flashcolor" "200 200 200" (r g b) (values 0.0-255.0)
"flashdelay" "12000" maximum time delay between lightning strikes
"chanceflicker" "2" 1 in 2 chance of flickering fog
"chancesound" "3" 1 in 3 chance of playing a sound
"chanceeffect" "4" 1 in 4 chance of playing the effect
*/
//----------------------------------------------------------
//----------------------------------------------------------
extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast );
void fx_rain_think( gentity_t *ent )
{
if (player)
{
if (ent->count!=0)
{
ent->count--;
if (ent->count==0 || (ent->count%2)==0)
{
gi.WE_SetTempGlobalFogColor(ent->pos2); // Turn Off
if (ent->count==0)
{
ent->nextthink = level.time + Q_irand(1000, 12000);
}
else if (ent->count==2)
{
ent->nextthink = level.time + Q_irand(150, 450);
}
else
{
ent->nextthink = level.time + Q_irand(50, 150);
}
}
else
{
gi.WE_SetTempGlobalFogColor(ent->pos3); // Turn On
ent->nextthink = level.time + 50;
}
}
else if (gi.WE_IsOutside(player->currentOrigin))
{
vec3_t effectPos;
vec3_t effectDir;
VectorClear(effectDir);
effectDir[0] += Q_flrand(-1.0f, 1.0f);
effectDir[1] += Q_flrand(-1.0f, 1.0f);
bool PlayEffect = Q_irand(1,ent->aimDebounceTime)==1;
bool PlayFlicker = Q_irand(1,ent->attackDebounceTime)==1;
bool PlaySound = (PlayEffect || PlayFlicker || Q_irand(1,ent->pushDebounceTime)==1);
// Play The Sound
//----------------
if (PlaySound && !PlayEffect)
{
VectorMA(player->currentOrigin, 250.0f, effectDir, effectPos);
G_SoundAtSpot(effectPos, G_SoundIndex(va("sound/ambience/thunder%d", Q_irand(1,4))), qtrue);
}
// Play The Effect
//-----------------
if (PlayEffect)
{
VectorMA(player->currentOrigin, 400.0f, effectDir, effectPos);
if (PlaySound)
{
G_Sound(player, G_SoundIndex(va("sound/ambience/thunder_close%d", Q_irand(1,2))));
}
// Raise It Up Into The Sky
//--------------------------
effectPos[2] += Q_flrand(600.0f, 1000.0f);
VectorClear(effectDir);
effectDir[2] = -1.0f;
G_PlayEffect("env/huge_lightning", effectPos, effectDir);
ent->nextthink = level.time + Q_irand(100, 200);
}
// Change The Fog Color
//----------------------
if (PlayFlicker)
{
ent->count = (Q_irand(1,4) * 2);
ent->nextthink = level.time + 50;
gi.WE_SetTempGlobalFogColor(ent->pos3);
}
else
{
ent->nextthink = level.time + Q_irand(1000, ent->delay);
}
}
else
{
ent->nextthink = level.time + Q_irand(1000, ent->delay);
}
}
else
{
ent->nextthink = level.time + Q_irand(1000, ent->delay);
}
}
void SP_CreateRain( gentity_t *ent )
{
// Different Types Of Rain
//-------------------------
if (ent->spawnflags & 1)
{
G_FindConfigstringIndex("lightrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
else if (ent->spawnflags & 2)
{
G_FindConfigstringIndex("rain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
else if (ent->spawnflags & 4)
{
G_FindConfigstringIndex("heavyrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
// Automatically Get Heavy Fog
//-----------------------------
G_FindConfigstringIndex("heavyrainfog", CS_WORLD_FX, MAX_WORLD_FX, qtrue );
// Automatically Get Lightning & Thunder
//---------------------------------------
ent->spawnflags |= 64;
}
else if (ent->spawnflags & 8)
{
G_EffectIndex( "world/acid_fizz" );
G_FindConfigstringIndex("acidrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// OUTSIDE SHAKE
//===============
if (ent->spawnflags & 16)
{
G_FindConfigstringIndex("outsideShake", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
}
// MISTY FOG
//===========
if (ent->spawnflags & 32)
{
G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue );
}
// LIGHTNING
//===========
if (ent->spawnflags & 64)
{
G_SoundIndex("sound/ambience/thunder1");
G_SoundIndex("sound/ambience/thunder2");
G_SoundIndex("sound/ambience/thunder3");
G_SoundIndex("sound/ambience/thunder4");
G_SoundIndex("sound/ambience/thunder_close1");
G_SoundIndex("sound/ambience/thunder_close2");
G_EffectIndex( "env/huge_lightning" );
ent->e_ThinkFunc = thinkF_fx_rain_think;
ent->nextthink = level.time + Q_irand(4000, 8000);
if (!G_SpawnVector( "flashcolor", "200 200 200", ent->pos3))
{
VectorSet(ent->pos3, 200, 200, 200);
}
VectorClear(ent->pos2); // the "off" color
G_SpawnInt("flashdelay", "12000", &ent->delay);
G_SpawnInt("chanceflicker", "2", &ent->attackDebounceTime);
G_SpawnInt("chancesound", "3", &ent->pushDebounceTime);
G_SpawnInt("chanceeffect", "4", &ent->aimDebounceTime);
}
}
// Added by Aurelio Reis on 10/20/02.
/*QUAKED fx_puff (1 0 0) (-16 -16 -16) (16 16 16)
This world effect will spawn a puff system globally into the level.
Enter any valid puff command as a key and value to setup the puff
system properties.
"count" The number of puffs/particles (default of 1000).
"whichsystem" Which puff system to use (currently 0 and 1. Default 0).
// Apply a default puff system.
default <value>
Current defaults are "snowstorm", "sandstorm", "foggy", and "smokey"
// Set the color of the particles (0-1.0).
color ( <red>, <green>, <blue> )
default ( 0.5, 0.5, 0.5 )
// Set the alpha (transparency) value for the particles (0-1.0).
alpha <value>
default 0.5
// Set which texture to use for the particles (make sure to include full path).
texture <textures/texture.tga>
default gfx/effects/alpha_smoke2b.tga
// Set the size of particles (from center, like a radius) (MIN 4, MAX 2048).
size <value>
default 100
// Whether the saber should flicker and spark or not (0 false, 1 true).
sabersparks <value>
default 0
// Set texture filtering mode (0 = Bilinear(default), 1 = Nearest(less quality).
filtermode <value>
default 0
// Set the alpha blending mode (0 = src, src-1, 1 = one, one (additive)).
blendmode <value>
default 0
// How much to rotate particles per second (in degree's).
rotate ( <min>, <max> )
default ( 0, 0 )
// Set the area around the player the puffs cover:
spread ( minX minY minZ ) ( maxX maxY maxZ )
default: ( -600 -600 -500 ) ( 600 600 550 )
// Set the random range that sets the speed the puffs fall:
velocity ( minX minY minZ ) ( maxX maxY maxZ )
default: ( -15 -15 -20 ) ( 15 15 -70 )
// Set an area of puff blowing:
wind ( windOriginX windOriginY windOriginZ ) ( windVelocityX windVelocityY windVelocityZ ) ( sizeX sizeY sizeZ )
// Set puff blowing data:
blowing duration <int>
blowing low <int>
default: 3
blowing velocity ( min max )
default: ( 30 70 )
blowing size ( minX minY minZ )
default: ( 1000 300 300 )
*/
//----------------------------------------------------------
void SP_CreatePuffSystem( gentity_t *ent )
{
char temp[128];
// Initialize the puff system to either 1000 particles or whatever they choose.
G_SpawnInt( "count", "1000", &ent->count );
cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
// See which puff system to use.
int iPuffSystem = 0;
int iVal = 0;
if ( G_SpawnInt( "whichsystem", "0", &iVal ) )
{
iPuffSystem = iVal;
if ( iPuffSystem < 0 || iPuffSystem > 1 )
{
iPuffSystem = 0;
//ri.Error( ERR_DROP, "Weather Effect: Invalid value for whichsystem key" );
Com_Printf( "Weather Effect: Invalid value for whichsystem key\n" );
}
}
if ( r_weatherScale->value > 0.0f )
{
sprintf( temp, "puff%i init %i", iPuffSystem, (int)( ent->count * r_weatherScale->value ));
G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue );
// Should return here??? It didn't originally...
}
// See whether we should have the saber spark from the puff system.
iVal = 0;
G_SpawnInt( "sabersparks", "0", &iVal );
if ( iVal == 1 )
level.worldFlags |= WF_PUFFING;
else
level.worldFlags &= ~WF_PUFFING;
// Go through all the fields and assign the values to the created puff system now.
for ( int i = 0; i < 20; i++ )
{
char *key = NULL;
char *value = NULL;
// Fetch a field.
if ( !G_SpawnField( i, &key, &value ) )
continue;
// Make sure we don't get key's that are worthless.
if ( Q_stricmp( key, "origin" ) == 0 || Q_stricmp( key, "classname" ) == 0 ||
Q_stricmp( key, "count" ) == 0 || Q_stricmp( key, "targetname" ) == 0 ||
Q_stricmp( key, "sabersparks" ) == 0 || Q_stricmp( key, "whichsystem" ) == 0 )
continue;
// Send the command.
Com_sprintf( temp, 128, "puff%i %s %s", iPuffSystem, key, value );
G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue );
}
}
// Don't use this! Too powerful! - Aurelio
/*NOTEINUSE! fx_command (1 0 0) (-16 -16 -16) (16 16 16)
//This effect allows you to issue console commands from within the world editor.
//Use the variables c00 to c99 to issue a maximum of 100 console commands.
//example: c00 r_showtris 1
*/
//----------------------------------------------------------
/*void SP_Command( gentity_t *ent )
{
char *strCommand;
// Go through all the commands.
for ( int i = 0; i < 100; i++ )
{
strCommand = NULL;
// Fetch a command.
G_SpawnString( va("c%02d", i), NULL, &strCommand );
// If it's valid, issue it.
if ( strCommand && strCommand[0] )
{
gi.SendConsoleCommand( strCommand );
}
}
}*/
//-----------------
// 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->cameraGroup )
{
// fxFile2....in other words, impact fx
G_PlayEffect( ent->cameraGroup, 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->cameraGroup = self->cameraGroup; //fxfile2
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 = G_NewString(self->soundSet);//get my own copy so i can free it when i die
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 to 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->cameraGroup )
{
G_EffectIndex( ent->cameraGroup );
}
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;
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, (EG2_Collision)0, 0 );//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;
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->cameraGroup );
ent->delay = G_EffectIndex( ent->cameraGroup );
}
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 );
}