quake4-sdk/source/game/Effect.cpp
2007-06-15 00:00:00 +00:00

463 lines
No EOL
10 KiB
C++

#include "../idlib/precompiled.h"
#pragma hdrstop
#include "Game_local.h"
#include "Effect.h"
#include "client/ClientEffect.h"
const idEventDef EV_LookAtTarget( "lookAtTarget", NULL );
const idEventDef EV_Attenuate( "attenuate", "f" );
CLASS_DECLARATION( idEntity, rvEffect )
EVENT( EV_Activate, rvEffect::Event_Activate )
EVENT( EV_LookAtTarget, rvEffect::Event_LookAtTarget )
EVENT( EV_Earthquake, rvEffect::Event_EarthQuake )
EVENT( EV_Camera_Start, rvEffect::Event_Start )
EVENT( EV_Camera_Stop, rvEffect::Event_Stop )
EVENT( EV_Attenuate, rvEffect::Event_Attenuate )
EVENT( EV_IsActive, rvEffect::Event_IsActive )
END_CLASS
/*
================
rvEffect::rvEffect
================
*/
rvEffect::rvEffect ( void ) {
fl.networkSync = true;
loop = false;
lookAtTarget = false;
effect = NULL;
endOrigin.Zero();
}
/*
================
rvEffect::Spawn
================
*/
void rvEffect::Spawn( void ) {
const char* fx;
if ( !spawnArgs.GetString ( "fx", "", &fx ) || !*fx ) {
if ( !( gameLocal.editors & EDITOR_FX ) ) {
gameLocal.Warning ( "no effect file specified on effect entity '%s'", name.c_str() );
PostEventMS ( &EV_Remove, 0 );
return;
}
} else {
effect = ( const idDecl * )declManager->FindEffect( spawnArgs.GetString ( "fx" ) );
if( effect->IsImplicit() ) {
common->Warning( "Unknown effect \'%s\' on entity \'%s\'", spawnArgs.GetString ( "fx" ), GetName() );
}
}
spawnArgs.GetVector ( "endOrigin", "0 0 0", endOrigin );
spawnArgs.GetBool ( "loop", "0", loop );
// If look at target is set the effect will continually update itself to look at its target
spawnArgs.GetBool( "lookAtTarget", "0", lookAtTarget );
renderEntity.shaderParms[SHADERPARM_ALPHA] = spawnArgs.GetFloat ( "_alpha", "1" );
renderEntity.shaderParms[SHADERPARM_BRIGHTNESS] = spawnArgs.GetFloat ( "_brightness", "1" );
if( spawnArgs.GetBool( "start_on", loop ? "1" : "0" ) ) {
ProcessEvent( &EV_Activate, this );
}
#if 0
// If anyone ever gets around to a flood fill from the origin rather than the over generous PushVolumeIntoTree bounds,
// this warning will become useful. Until then, it's a bogus warning.
if( gameRenderWorld->PointInArea( GetPhysics()->GetOrigin() ) < 0 ) {
common->Warning( "Effect \'%s\' out of world", name.c_str() );
}
#endif
}
/*
================
rvEffect::Think
================
*/
void rvEffect::Think( void ) {
if( clientEntities.IsListEmpty ( ) ) {
BecomeInactive( TH_THINK );
// Should the func_fx be removed now?
if( !(gameLocal.editors & EDITOR_FX) && spawnArgs.GetBool( "remove" ) ) {
PostEventMS( &EV_Remove, 0 );
}
return;
}
else if( lookAtTarget ) {
// If activated and looking at its target then update the target information
ProcessEvent( &EV_LookAtTarget );
}
UpdateVisuals();
Present ( );
}
/*
================
rvEffect::Save
================
*/
void rvEffect::Save( idSaveGame *savefile ) const {
savefile->WriteBool( loop );
savefile->WriteBool( lookAtTarget );
savefile->WriteString( effect->GetName() );
savefile->WriteVec3( endOrigin );
clientEffect.Save( savefile );
}
/*
================
rvEffect::Restore
================
*/
void rvEffect::Restore( idRestoreGame *savefile ) {
idStr name;
savefile->ReadBool( loop );
savefile->ReadBool( lookAtTarget );
savefile->ReadString( name );
effect = declManager->FindType( DECL_EFFECT, name );
savefile->ReadVec3( endOrigin );
clientEffect.Restore( savefile );
}
/*
================
rvEffect::Think
================
*/
void rvEffect::Stop( bool destroyParticles ) {
StopEffect ( effect, destroyParticles );
}
/*
================
rvEffect::Play
================
*/
bool rvEffect::Play( void ) {
clientEffect = PlayEffect ( effect, renderEntity.origin, renderEntity.axis, loop, endOrigin );
if ( clientEffect ) {
idVec4 color;
color[0] = renderEntity.shaderParms[SHADERPARM_RED];
color[1] = renderEntity.shaderParms[SHADERPARM_GREEN];
color[2] = renderEntity.shaderParms[SHADERPARM_BLUE];
color[3] = renderEntity.shaderParms[SHADERPARM_ALPHA];
clientEffect->SetColor ( color );
clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] );
clientEffect->SetAmbient( true );
BecomeActive ( TH_THINK );
return true;
}
return false;
}
/*
================
rvEffect::Attenuate
================
*/
void rvEffect::Attenuate ( float attenuation ) {
rvClientEntity* cent;
for( cent = clientEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) {
// RAVEN BEGIN
// jnewquist: Use accessor for static class type
if ( cent->IsType ( rvClientEffect::GetClassType() ) ) {
// RAVEN END
static_cast<rvClientEffect*>(cent)->Attenuate ( attenuation );
}
}
}
/*
================
rvEffect::Restart
================
*/
void rvEffect::Restart( void ) {
Stop( false );
if( loop ) {
Play();
}
}
/*
================
rvEffect::UpdateChangeableSpawnArgs
================
*/
void rvEffect::UpdateChangeableSpawnArgs( const idDict *source ) {
const char* fx;
const idDecl *newEffect;
bool newLoop;
idEntity::UpdateChangeableSpawnArgs(source);
if ( !source ) {
return;
}
if ( source->GetString ( "fx", "", &fx ) && *fx ) {
newEffect = ( const idDecl * )declManager->FindEffect( fx );
} else {
newEffect = NULL;
}
idVec3 color;
source->GetVector( "_color", "1 1 1", color );
renderEntity.shaderParms[ SHADERPARM_RED ] = color[0];
renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1];
renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2];
renderEntity.shaderParms[ SHADERPARM_ALPHA ] = source->GetFloat ( "_alpha", "1" );
renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] = source->GetFloat ( "_brightness", "1" );
if ( clientEffect ) {
clientEffect->SetColor ( idVec4(color[0],color[1],color[2],renderEntity.shaderParms[ SHADERPARM_ALPHA ]) );
clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] );
}
source->GetBool ( "loop", "0", newLoop );
spawnArgs.Copy( *source );
// IF the effect handle has changed or the loop status has changed then restart the effect
if ( newEffect != effect || loop != newLoop ) {
Stop ( false );
loop = newLoop;
effect = newEffect;
if ( effect ) {
Play ( );
BecomeActive( TH_THINK );
UpdateVisuals();
} else {
BecomeInactive ( TH_THINK );
UpdateVisuals();
}
}
}
/*
===============
rvEffect::ShowEditingDialog
===============
*/
void rvEffect::ShowEditingDialog( void ) {
common->InitTool( EDITOR_FX, &spawnArgs );
}
/*
=================
rvEffect::WriteToSnapshot
=================
*/
void rvEffect::WriteToSnapshot( idBitMsgDelta &msg ) const {
GetPhysics()->WriteToSnapshot( msg );
WriteBindToSnapshot( msg );
idGameLocal::WriteDecl( msg, effect );
msg.WriteBits( loop, 1 );
}
/*
=================
rvEffect::ReadFromSnapshot
=================
*/
void rvEffect::ReadFromSnapshot( const idBitMsgDelta &msg ) {
const idDecl *old = effect;
GetPhysics()->ReadFromSnapshot( msg );
ReadBindFromSnapshot( msg );
effect = idGameLocal::ReadDecl( msg, DECL_EFFECT );
loop = ( msg.ReadBits( 1 ) != 0 );
if ( effect && !old ) {
// TODO: need to account for when the effect really started
Play();
}
}
/*
=================
rvEffect::ClientPredictionThink
=================
*/
void rvEffect::ClientPredictionThink( void ) {
if ( gameLocal.isNewFrame ) {
Think ( );
}
RunPhysics();
Present();
}
/*
================
rvEffect::Event_Start
================
*/
void rvEffect::Event_Start ( void ) {
if( !effect || !clientEntities.IsListEmpty ( ) ) {
return;
}
if( !Play() ) {
if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) {
// no effects on dedicated server
} else {
gameLocal.Warning( "Unable to play effect '%s'", effect->GetName() );
}
BecomeInactive ( TH_THINK );
}
ProcessEvent( &EV_LookAtTarget );
}
/*
================
rvEffect::Event_Stop
================
*/
void rvEffect::Event_Stop ( void ) {
if( !effect ) {
return;
}
Stop( false );
}
/*
=================
rvEffect::Event_Activate
=================
*/
void rvEffect::Event_Activate( idEntity *activator ) {
// Stop the effect if its already playing
if( !clientEntities.IsListEmpty ( ) ) {
Event_Stop ( );
} else {
Event_Start ( );
}
ActivateTargets( activator );
}
/*
================
rvEffect::Event_LookAtTarget
Reorients the effect entity towards its target and sets the end origin as well
================
*/
void rvEffect::Event_LookAtTarget ( void ) {
const idKeyValue *kv;
idVec3 dir;
if ( !effect || !clientEffect ) {
return;
}
kv = spawnArgs.MatchPrefix( "target", NULL );
while( kv ) {
idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
if( ent ) {
if( !idStr::Icmp( ent->GetEntityDefName(), "target_null" ) ) {
dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
dir.Normalize();
clientEffect->SetEndOrigin ( ent->GetPhysics()->GetOrigin() );
clientEffect->SetAxis ( dir.ToMat3( ) );
return;
}
}
kv = spawnArgs.MatchPrefix( "target", kv );
}
}
/*
================
rvEffect::Event_EarthQuake
================
*/
void rvEffect::Event_EarthQuake ( float requiresLOS ) {
float quakeChance;
if ( !spawnArgs.GetFloat("quakeChance", "0", quakeChance) ) {
return;
}
if ( rvRandom::flrand(0, 1.0f) > quakeChance ) {
// failed its activation roll
return;
}
if ( requiresLOS ) {
// if the player doesn't have line of sight to this fx, don't do anything
trace_t trace;
idPlayer *player = gameLocal.GetLocalPlayer();
idVec3 viewOrigin;
idMat3 viewAxis;
player->GetViewPos(viewOrigin, viewAxis);
// RAVEN BEGIN
// ddynerman: multiple collision worlds
gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player );
// RAVEN END
if (trace.fraction < 1.0f)
{
// something blocked LOS
return;
}
}
// activate this effect now
ProcessEvent ( &EV_Activate, gameLocal.entities[ENTITYNUM_WORLD] );
}
/*
================
rvEffect::Event_Attenuate
================
*/
void rvEffect::Event_Attenuate( float attenuation ) {
Attenuate( attenuation );
}
/*
================
rvEffect::Event_Attenuate
================
*/
void rvEffect::Event_IsActive( void ) {
idThread::ReturnFloat( ( !effect || !clientEntities.IsListEmpty() ) ? 0.0f : 1.0f );
}
/*
================
rvEffect::InstanceLeave
================
*/
void rvEffect::InstanceLeave( void ) {
idEntity::InstanceLeave();
Stop( true );
}
/*
================
rvEffect::InstanceJoin
================
*/
void rvEffect::InstanceJoin( void ) {
idEntity::InstanceJoin();
Restart();
}