quake4-sdk/source/mpgame/Projectile.cpp

2285 lines
65 KiB
C++

// RAVEN BEGIN
// bdube: note that this file is no longer merged with Doom3 updates
//
// MERGE_DATE 09/30/2004
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "Game_local.h"
#include "ai/AI_Manager.h"
#include "Projectile.h"
#include "spawner.h"
/*
===============================================================================
idProjectile
===============================================================================
*/
static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f;
static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f;
const idEventDef EV_Explode( "<explode>", NULL );
const idEventDef EV_Fizzle( "<fizzle>", NULL );
const idEventDef EV_RadiusDamage( "<radiusdmg>", "E" );
const idEventDef EV_ResidualDamage ( "<residualdmg>", "E" );
const idEventDef EV_EmitDamage ( "<emitdmg>", "E" );
CLASS_DECLARATION( idEntity, idProjectile )
EVENT( EV_Explode, idProjectile::Event_Explode )
EVENT( EV_Fizzle, idProjectile::Event_Fizzle )
EVENT( EV_Touch, idProjectile::Event_Touch )
EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage )
EVENT( EV_ResidualDamage, idProjectile::Event_ResidualDamage )
EVENT( EV_EmitDamage, idProjectile::Event_EmitDamage )
END_CLASS
/*
================
idProjectile::idProjectile
================
*/
idProjectile::idProjectile( void ) {
methodOfDeath = -1;
owner = NULL;
memset( &projectileFlags, 0, sizeof( projectileFlags ) );
damagePower = 1.0f;
memset( &renderLight, 0, sizeof( renderLight ) );
lightDefHandle = -1;
lightOffset.Zero();
lightStartTime = 0;
lightEndTime = 0;
lightColor.Zero();
visualAngles.Zero();
angularVelocity.Zero();
speed.Init( 0.0f, 0.0f, 0.0f, 0.0f );
updateVelocity = false;
rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() );
flyEffect = NULL;
flyEffectAttenuateSpeed = 0.0f;
bounceCount = 0;
hitCount = 0;
state = SPAWNED;
fl.networkSync = true;
prePredictTime = 0;
syncPhysics = false;
launchTime = 0;
launchOrig = vec3_origin;
launchDir = vec3_origin;
launchSpeed = 0.0f;
playedDamageEffect = false;
predictedProjectiles = false;
}
/*
================
idProjectile::Spawn
================
*/
void idProjectile::Spawn( void ) {
physicsObj.SetSelf( this );
// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
RV_PUSH_HEAP_MEM(this);
// RAVEN END
physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
RV_POP_HEAP();
// RAVEN END
physicsObj.SetContents( 0 );
physicsObj.SetClipMask( 0 );
physicsObj.PutToRest();
SetPhysics( &physicsObj );
prePredictTime = spawnArgs.GetInt( "predictTime", "0" );
syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" );
if ( gameLocal.isClient ) {
Hide();
}
predictedProjectiles = g_predictedProjectiles.GetBool();
}
/*
================
idProjectile::Save
================
*/
void idProjectile::Save( idSaveGame *savefile ) const {
savefile->WriteInt( methodOfDeath ); // cnicholson: Added unsaved var
owner.Save( savefile );
savefile->Write( &projectileFlags, sizeof( projectileFlags ) );
savefile->WriteFloat( damagePower );
savefile->WriteRenderLight( renderLight );
savefile->WriteInt( ( int )lightDefHandle );
savefile->WriteVec3( lightOffset );
savefile->WriteInt( lightStartTime );
savefile->WriteInt( lightEndTime );
savefile->WriteVec3( lightColor );
savefile->WriteStaticObject( physicsObj );
savefile->WriteAngles( visualAngles ); // cnicholson: added unsaved var
savefile->WriteAngles( angularVelocity ); // cnicholson: moved var
// Save speed
savefile->WriteBool ( updateVelocity );
savefile->WriteFloat( speed.GetStartTime() );
savefile->WriteFloat( speed.GetDuration() );
savefile->WriteFloat( speed.GetStartValue() );
savefile->WriteFloat( speed.GetEndValue() );
// rotation; this is a class, so it doesnt get saved here
flyEffect.Save( savefile ); // cnicholson: added unsaved var
savefile->WriteFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var
savefile->WriteInt ( bounceCount );
savefile->WriteInt ( hitCount );
savefile->WriteInt( (int)state );
}
/*
================
idProjectile::Restore
================
*/
void idProjectile::Restore( idRestoreGame *savefile ) {
float fset;
idVec3 temp;
savefile->ReadInt( methodOfDeath ); // cnicholson: Added unrestored var
owner.Restore( savefile );
savefile->Read( &projectileFlags, sizeof( projectileFlags ) );
savefile->ReadFloat( damagePower );
savefile->ReadRenderLight( renderLight );
savefile->ReadInt( (int &)lightDefHandle );
if ( lightDefHandle != -1 ) {
//get the handle again as it's out of date after a restore!
lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
}
savefile->ReadVec3( lightOffset );
savefile->ReadInt( lightStartTime );
savefile->ReadInt( lightEndTime );
savefile->ReadVec3( lightColor );
// Restore the physics
savefile->ReadStaticObject( physicsObj );
RestorePhysics ( &physicsObj );
savefile->ReadAngles( visualAngles ); // cnicholson: added unrestored var
savefile->ReadAngles( angularVelocity ); // cnicholson: moved var
// Restore speed
savefile->ReadBool ( updateVelocity );
savefile->ReadFloat( fset );
speed.SetStartTime( fset );
savefile->ReadFloat( fset );
speed.SetDuration( fset );
savefile->ReadFloat( fset );
speed.SetStartValue( fset );
savefile->ReadFloat( fset );
speed.SetEndValue( fset );
// rotation?
flyEffect.Restore( savefile ); // cnicholson: added unrestored var
savefile->ReadFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var
savefile->ReadInt ( bounceCount );
savefile->ReadInt ( hitCount );
savefile->ReadInt( (int &)state );
}
/*
================
idProjectile::GetOwner
================
*/
idEntity *idProjectile::GetOwner( void ) const {
return owner.GetEntity();
}
/*
================
idProjectile::SetSpeed
================
*/
void idProjectile::SetSpeed( float s, int accelTime ) {
idVec3 vel;
vel = physicsObj.GetLinearVelocity();
vel.Normalize();
speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s );
if ( accelTime > 0 ) {
updateVelocity = true;
} else {
updateVelocity = false;
}
// Update the velocity to match the direction we are facing and include any accelerations
physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel );
}
/*
================
idProjectile::Create
================
*/
void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) {
idDict args;
idStr shaderName;
idVec3 light_color;
idVec3 light_offset;
idVec3 tmp;
idMat3 axis;
Unbind();
axis = dir.ToMat3();
physicsObj.SetOrigin( start );
physicsObj.SetAxis( axis );
physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner );
physicsObj.extraPassEntity = extraPassEntity;
owner = _owner;
memset( &renderLight, 0, sizeof( renderLight ) );
shaderName = spawnArgs.GetString( "mtr_light_shader" );
if ( *shaderName ) {
renderLight.shader = declManager->FindMaterial( shaderName, false );
renderLight.pointLight = true;
renderLight.lightRadius[0] =
renderLight.lightRadius[1] =
renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" );
spawnArgs.GetVector( "light_color", "1 1 1", light_color );
renderLight.shaderParms[0] = light_color[0];
renderLight.shaderParms[1] = light_color[1];
renderLight.shaderParms[2] = light_color[2];
renderLight.shaderParms[3] = 1.0f;
// RAVEN BEGIN
// dluetscher: added detail levels to render lights
renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
// dluetscher: set the projectile lights to be no shadows
renderLight.noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3;
// RAVEN END
}
spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset );
lightStartTime = 0;
lightEndTime = 0;
damagePower = 1.0f;
UpdateVisuals();
state = CREATED;
}
/*
=================
idProjectile::~idProjectile
=================
*/
idProjectile::~idProjectile() {
StopSound( SND_CHANNEL_ANY, false );
FreeLightDef();
SetPhysics( NULL );
}
/*
=================
idProjectile::FreeLightDef
=================
*/
void idProjectile::FreeLightDef( void ) {
if ( lightDefHandle != -1 ) {
gameRenderWorld->FreeLightDef( lightDefHandle );
lightDefHandle = -1;
}
}
/*
=================
idProjectile::Launch
=================
*/
void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) {
float fuse;
idVec3 velocity;
float linear_friction;
float angular_friction;
float contact_friction;
float bounce;
float mass;
float gravity;
float temp, temp2;
idVec3 gravVec;
idVec3 tmp;
int contents;
int clipMask;
// allow characters to throw projectiles during cinematics, but not the player
if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) {
cinematic = owner.GetEntity()->cinematic;
} else {
cinematic = false;
}
// Set the damage
damagePower = dmgPower;
if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) {
spawnArgs.GetVector( "velocity", "0 0 0", tmp );
temp = tmp[0];
} else {
float speedRandom;
if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) {
temp += gameLocal.random.CRandomFloat()*speedRandom;
}
}
if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) {
temp2 = temp;
}
float speedDuration;
speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) );
speed.Init( gameLocal.time, speedDuration, temp, temp2 );
if ( speedDuration > 0 && temp != temp2 ) {
// only support constant velocity projectiles in MP
// ( we also assume that no MP projectiles use speedRandom )
assert( !gameLocal.isServer );
updateVelocity = true;
}
launchSpeed = temp;
spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity );
linear_friction = spawnArgs.GetFloat( "linear_friction" );
angular_friction = spawnArgs.GetFloat( "angular_friction" );
contact_friction = spawnArgs.GetFloat( "contact_friction" );
bounce = spawnArgs.GetFloat( "bounce" );
mass = spawnArgs.GetFloat( "mass" );
gravity = spawnArgs.GetFloat( "gravity" );
fuse = spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() );
bounceCount = spawnArgs.GetInt( "bounce_count", "-1" );
//spawn impact entity information
impactEntity = spawnArgs.GetString("def_impactEntity","");
numImpactEntities = spawnArgs.GetInt("numImpactEntities","0");
ieMinPitch = spawnArgs.GetInt("ieMinPitch","0");
ieMaxPitch = spawnArgs.GetInt("ieMaxPitch","0");
ieSlicePercentage = spawnArgs.GetFloat("ieSlicePercentage","0.0");
projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" );
projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" );
projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" );
projectileFlags.detonate_on_bounce = spawnArgs.GetBool( "detonate_on_bounce" );
lightStartTime = 0;
lightEndTime = 0;
impactedEntity = 0;
if ( health ) {
fl.takedamage = true;
}
// RAVEN BEGIN
// abahr:
gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero;
// RAVEN END
Unbind();
contents = 0;
clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX;
// all projectiles are projectileclip
clipMask |= CONTENTS_PROJECTILECLIP;
if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) {
clipMask |= CONTENTS_LARGESHOTCLIP;
}
if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) {
clipMask |= CONTENTS_MOVEABLECLIP;
}
if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) {
clipMask |= CONTENTS_MONSTERCLIP;
}
if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) {
contents |= CONTENTS_TRIGGER;
}
if ( !spawnArgs.GetBool( "no_contents", "1" ) ) {
contents |= CONTENTS_PROJECTILE;
}
clipMask |= CONTENTS_PROJECTILE;
// don't do tracers on client, we don't know origin and direction
if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) {
SetModel( spawnArgs.GetString( "model_tracer" ) );
projectileFlags.isTracer = true;
}
physicsObj.SetMass( mass );
physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce );
physicsObj.SetGravity( gravVec );
physicsObj.SetContents( contents );
physicsObj.SetClipMask( clipMask | CONTENTS_WATER );
physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity );
physicsObj.SetOrigin( start );
physicsObj.SetAxis( dir.ToMat3() );
if ( !gameLocal.isClient ) {
if ( fuse <= 0 ) {
// run physics for 1 second
RunPhysics();
PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
} else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
fuse -= timeSinceFire;
if ( fuse < 0.0f ) {
fuse = 0.0f;
}
PostEventSec( &EV_Explode, fuse );
} else {
fuse -= timeSinceFire;
if ( fuse < 0.0f ) {
fuse = 0.0f;
}
PostEventSec( &EV_Fizzle, fuse );
}
}
idQuat q( dir.ToMat3().ToQuat() );
rotation.Init( gameLocal.GetTime(), 0.0f, q, q );
if ( projectileFlags.isTracer ) {
StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL );
} else {
StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
}
// used for the plasma bolts but may have other uses as well
if ( projectileFlags.randomShaderSpin ) {
float f = gameLocal.random.RandomFloat();
f *= 0.5f;
renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f;
}
UpdateVisuals();
// Make sure these come after update visuals so the origin and axis are correct
PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis );
flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true );
flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" );
state = LAUNCHED;
hitCount = 0;
predictTime = prePredictTime;
if ( spawnArgs.GetFloat( "delay_emit_damage" ) > 0.0f ) {
PostEventSec( &EV_EmitDamage, spawnArgs.GetFloat( "wait_emit_damage", "0" ), this );
}
if ( gameLocal.isServer ) {
// store launch information for networking
launchTime = gameLocal.time;
launchOrig = physicsObj.GetOrigin();
launchDir = dir;
} else {
if ( predictedProjectiles ) {
physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time );
}
}
if ( g_perfTest_noProjectiles.GetBool() ) {
PostEventMS( &EV_Remove, 0 );
}
}
/*
================
idProjectile::Think
================
*/
void idProjectile::Think( void ) {
// run physics
if ( thinkFlags & TH_PHYSICS ) {
// Update the velocity to match the changing speed
if ( updateVelocity ) {
idVec3 vel;
vel = physicsObj.GetLinearVelocity ( );
vel.Normalize ( );
physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel );
if ( speed.IsDone ( gameLocal.time ) ) {
updateVelocity = false;
}
}
RunPhysics();
// If we werent at rest and are now then start the atrest fuse
if ( physicsObj.IsAtRest( ) ) {
float fuse = spawnArgs.GetFloat( "fuse_atrest" );
if ( fuse > 0.0f ) {
if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
CancelEvents( &EV_Explode );
PostEventSec( &EV_Explode, fuse );
} else {
CancelEvents( &EV_Fizzle );
PostEventSec( &EV_Fizzle, fuse );
}
}
}
// Stop the trail effect if the physics flag was removed
if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) {
if ( physicsObj.IsAtRest( ) ) {
flyEffect->Stop( );
flyEffect = NULL;
} else {
float speed;
speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) );
flyEffect->Attenuate( speed / flyEffectAttenuateSpeed );
}
}
UpdateVisualAngles();
}
Present();
// add the light
if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) {
renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset;
renderLight.axis = GetPhysics()->GetAxis();
if ( ( lightDefHandle != -1 ) ) {
if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) {
idVec3 color( 0, 0, 0 );
if ( gameLocal.time < lightEndTime ) {
float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime );
color.Lerp( lightColor, color, frac );
}
renderLight.shaderParms[SHADERPARM_RED] = color.x;
renderLight.shaderParms[SHADERPARM_GREEN] = color.y;
renderLight.shaderParms[SHADERPARM_BLUE] = color.z;
}
gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight );
} else {
lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
}
}
}
/*
=================
idProjectile::UpdateVisualAngles
=================
*/
void idProjectile::UpdateVisualAngles() {
idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() );
if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) {
rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat() );
return;
}
if( physicsObj.GetNumContacts() ) {
return;
}
if( !rotation.IsDone(gameLocal.GetTime()) ) {
return;
}
if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) {
return;
}
visualAngles += angularVelocity;
idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat();
rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q );
}
/*
=================
idProjectile::Collide
=================
*/
bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) {
bool dummy = false;
return Collide( collision, velocity, dummy );
}
bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) {
idEntity* ent;
idEntity* actualHitEnt = NULL;
idEntity* ignore;
const char* damageDefName;
idVec3 dir;
bool canDamage;
hitTeleporter = false;
if ( state == EXPLODED || state == FIZZLED || ( state == IMPACTED && predictedProjectiles ) ) {
return true;
}
if ( IsHidden() && predictedProjectiles ) {
// teleported, ignore impacts
return false;
}
// allow projectiles to hit triggers (teleports)
// predict this on a client
if ( collision.c.contents & CONTENTS_TRIGGER ) {
idEntity* trigger = gameLocal.entities[ collision.c.entityNum ];
if ( trigger ) {
if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) {
hitTeleporter = true;
trace_t trace;
trace.endpos = physicsObj.GetOrigin();
trace.endAxis = physicsObj.GetAxis();
trace.c.contents = collision.c.contents;
trace.c.entityNum = collision.c.entityNum;
if( trigger->GetPhysics()->GetClipModel() ) {
trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId();
} else {
trace.c.id = 0;
}
// hack to play the effect on clients when mispredicted (Hide() ensures it can't play twice for the same teleport)
int wasNewFrame = gameLocal.isNewFrame;
if ( gameLocal.isClient && predictedProjectiles ) {
gameLocal.isNewFrame = true;
}
trigger->Signal( SIG_TOUCH );
trigger->ProcessEvent( &EV_Touch, this, &trace );
if ( gameLocal.isClient && predictedProjectiles ) {
Hide();
gameLocal.isNewFrame = wasNewFrame;
}
}
}
// when we hit a trigger, align our velocity to the trigger's coordinate plane
if( gameLocal.isServer ) {
idVec3 up( 0.0f, 0.0f, 1.0f );
idVec3 right = collision.c.normal.Cross( up );
idMat3 mat( collision.c.normal, right, up );
physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) );
physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) );
// update the projectile's launchdir and launch origin
// this will propagate the change to the clients for prediction
// re-launch the projectile
idVec3 newDir = physicsObj.GetLinearVelocity();
newDir.Normalize();
launchTime = gameLocal.time;
launchDir = newDir;
physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) );
physicsObj.SetAxis( newDir.ToMat3() );
launchOrig = physicsObj.GetOrigin();
}
if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) {
return false;
}
}
// network clients: heuristic to skip predicting collisions
if ( gameLocal.isClient ) {
if ( predictedProjectiles ) {
if ( syncPhysics ) {
return false;
}
} else {
if ( spawnArgs.GetBool( "no_impact_prediction" ) || syncPhysics ) {
return false;
}
}
}
// remove projectile when a 'noimpact' surface is hit
if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
PostEventMS( &EV_Remove, 0 );
StopEffect( "fx_fly" );
if( flyEffect) {
//flyEffect->Event_Remove();
}
return true;
}
// get the entity the projectile collided with
ent = gameLocal.entities[ collision.c.entityNum ];
if ( ent == owner.GetEntity() ) {
return true;
}
// just get rid of the projectile when it hits a player in noclip
if ( ent->IsType( idPlayer::GetClassType() ) && static_cast<idPlayer *>( ent )->noclip ) {
PostEventMS( &EV_Remove, 0 );
common->DPrintf( "Projectile collision no impact\n" );
return true;
}
// If the hit entity is bound to an actor use the actor instead
if ( ent->GetTeamMaster() && ent->GetTeamMaster()->IsType ( idActor::GetClassType() ) ) {
actualHitEnt = ent;
ent = ent->GetTeamMaster();
}
// Can the projectile damage?
canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ));
// direction of projectile
dir = velocity;
dir.Normalize();
// projectiles can apply an additional impulse next to the rigid body physics impulse
// RAVEN BEGIN
// abahr: added call to SkipDamageImpulse changed where push comes from
damageDefName = NULL;
if ( collision.c.materialType ) {
damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) );
}
if ( !damageDefName || !*damageDefName ) {
damageDefName = spawnArgs.GetString ( "def_damage" );
}
if( damageDefName && damageDefName[0] ) {
const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false );
if ( dict ) {
ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict );
}
}
// RAVEN END
//Spawn any impact entities if necessary.
SpawnImpactEntities(collision, velocity);
//Apply any impact force if the necessary
//ApplyImpactForce(ent, collision, dir);
// MP: projectiles open doors
if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) {
ent->ProcessEvent( &EV_Activate , this );
}
// If the projectile hits water then we need to let the projectile keep going
if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) {
if ( !physicsObj.IsInWater( ) ) {
StopEffect( "fx_fly" );
if( flyEffect) {
//flyEffect->Event_Remove();
}
}
// Pass through water
return false;
} else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) {
if ( !projectileFlags.detonate_on_actor ) {
return false;
}
} else {
bool bounce = false;
// Determine if the projectile should bounce
bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage;
bounce = bounce && (bounceCount == -1 || bounceCount > 0);
//assert(collision.c.material);
if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) {
bounce = !projectileFlags.detonate_on_bounce;
}
if ( bounce ) {
if ( bounceCount != -1 ) {
bounceCount--;
}
StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL );
float len = velocity.Length();
if ( len > BOUNCE_SOUND_MIN_VELOCITY ) {
if ( ent->IsType ( idMover::GetClassType ( ) ) ) {
ent->PlayEffect(
gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType),
collision.c.point, collision.c.normal.ToMat3(),
false, vec3_origin, true );
} else {
gameLocal.PlayEffect(
gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType),
collision.c.point, collision.c.normal.ToMat3(),
false, vec3_origin, true );
}
} else {
// FIXME: clean up
idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() );
axis[0].ProjectOntoPlane( collision.c.normal );
axis[0].Normalize();
axis[2] = collision.c.normal;
axis[1] = axis[2].Cross( axis[0] ).ToNormal();
rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() );
}
if ( actualHitEnt
&& actualHitEnt != ent
&& actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) )
{//bleh...
if ( damageDefName[0] != '\0' ) {
idVec3 dir = velocity;
dir.Normalize();
actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
}
}
return false;
}
}
SetOrigin( collision.endpos );
// SetAxis( collision.endAxis );
// unlink the clip model because we no longer need it
GetPhysics()->UnlinkClip();
ignore = NULL;
// RAVEN BEGIN
// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects.
bool willPlayDamageEffect = true;
if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) {
// if the projectile hit an ai
if ( ent->IsType( idAI::GetClassType() ) ) {
idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
player->AddProjectileHits( 1 );
// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects.
idAI * ai_ent = static_cast<idAI* >(ent);
if( ai_ent->team == player->team) {
willPlayDamageEffect = false;
}
}
}
// RAVEN END
// if the hit entity takes damage
if ( canDamage ) {
if ( damageDefName[0] != '\0' ) {
idVec3 dir = velocity;
dir.Normalize();
// RAVEN BEGIN
// jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint
// otherwise damage zones for head attachments no-worky
int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id);
if ( ent->IsType(idActor::GetClassType()) )
{
idActor* entActor = static_cast<idActor*>(ent);
if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) )
{
idAFAttachment* headEnt = static_cast<idAFAttachment*>(entActor->GetHead());
if ( headEnt && headEnt->entityNumber == collision.c.entityNum )
{//hit ent's head, get the proper joint for the head
hitJoint = entActor->GetAnimator()->GetJointHandle("head");
}
}
}
// RAVEN END
ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint );
if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) {
statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 );
hitCount++;
}
}
}
ignore = ent;
if ( predictedProjectiles ) {
if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) {
ent->AddDamageEffect( collision, velocity, damageDefName, owner );
}
// hack to play the effect on clients when mispredicted (state/playedDamageEffect ensure it can't happen twice)
int wasNewFrame = gameLocal.isNewFrame;
if ( gameLocal.isClient ) {
gameLocal.isNewFrame = true;
}
// if the the player is the attacker and the victim is teammate, don't play any effects.
if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect && ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) ) {
DefaultDamageEffect( collision, velocity, damageDefName );
playedDamageEffect = true;
}
Explode( &collision, false, ignore );
gameLocal.isNewFrame = wasNewFrame;
} else {
ent->AddDamageEffect ( collision, velocity, damageDefName, owner );
// if the the player is the attacker and the victim is teammate, don't play any effects.
if ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) {
DefaultDamageEffect( collision, velocity, damageDefName );
}
// don't predict explosions on clients
if ( gameLocal.isClient ) {
return true;
}
Explode( &collision, false, ignore );
}
return true;
}
void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity)
{
if( impactEntity.Length() == 0 || numImpactEntities == 0 )
return;
const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity);
if(impactEntityDict == NULL)
return;
idVec3 tempDirection;
idVec3 direction;
direction.Zero();
idVec3 up = collision.c.normal;
//Calculate the axes for that are oriented to the impact point.
idMat3 impactAxes;
idVec3 right = velocity.Cross(up);
idVec3 forward = up.Cross(right);
right.Normalize();
forward.Normalize();
impactAxes[0] = forward;
impactAxes[1] = right;
impactAxes[2] = up;
//Calculate the reflection vector by calculating the forward component and up component of the projectile direction
//idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up));
idVec3 reflectionVelocity = up * 0.01f;
//The algorithm below will launch entities at a random pitch and somewhat random yaw.
//The yaw is calculated by dividing 360 by the number of entities to spawn. This creates
//a distribution slice. Then using the slice percentage, this will determine how much of the
//slice to use.
//This creates a random,but somewhat even coverage of the circle.
//Calculate the slice size and pick a random start position.
int sliceSize = 360 / numImpactEntities;
int startPosition = rvRandom::irand(0, 360);
//Move the origin away from the collision point. This prevents the projectiles
//from colliding with the surface.
idVec3 origin = collision.endpos;
origin += 10.0f * collision.c.normal;
for(int i = 0;i < numImpactEntities; i++)
{
idProjectile* spawnProjectile = NULL;
gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile);
if(spawnProjectile != NULL)
{
int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch);
int sliceMiddle = (i * sliceSize) + startPosition;
int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2;
int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness);
yaw = yaw % 360;
float cosPitch = idMath::Cos(DEG2RAD(pitch));
tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw));
tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw));
tempDirection.z = idMath::Sin(DEG2RAD(pitch));
spawnProjectile->SetOwner(owner);
//Now orient the direction to the surface world orientation.
direction = impactAxes * tempDirection;
spawnProjectile->Launch(origin, direction, reflectionVelocity);
}
}
}
/*
=================
idProjectile::DefaultDamageEffect
=================
*/
void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) {
idEntity* ent;
idMat3 axis;
ent = gameLocal.entities[ tr.c.entityNum ];
// Make sure we want to play effects
if ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" ))
&& owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) {
return;
}
// Effect axis when hitting actors is along the direction of impact because actor models are
// very detailed.
if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) {
idVec3 dir;
dir = velocity;
dir.Normalize ( );
axis = ((-dir + tr.c.normal) * 0.5f).ToMat3();
// Play an actor specific impact effect?
const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType );
if ( actorImpactEffect ) {
gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true, true );
return;
}
} else {
axis = tr.c.normal.ToMat3();
}
// Play an impact effect on the entity that got hit
if ( ent->IsType( idMover::GetClassType ( ) ) ) {
ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true );
} else {
gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true );
}
}
/*
================
idProjectile::Killed
================
*/
void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
Explode( NULL, true );
physicsObj.ClearContacts();
physicsObj.PutToRest();
} else {
Fizzle();
}
}
/*
================
idProjectile::Fizzle
================
*/
void idProjectile::Fizzle( void ) {
if ( state == EXPLODED || state == FIZZLED ) {
return;
}
if ( predictedProjectiles ) {
if ( state == IMPACTED ) {
return;
}
} else {
if ( gameLocal.isClient ) {
return;
}
}
StopSound( SND_CHANNEL_BODY, false );
StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
fl.takedamage = false;
physicsObj.SetContents( 0 );
physicsObj.GetClipModel()->Unlink();
physicsObj.PutToRest();
// No more fly effects
StopEffect( "fx_fly" );
if( flyEffect) {
//flyEffect->Event_Remove();
}
Hide();
FreeLightDef();
state = FIZZLED;
int removeTime = spawnArgs.GetInt( "remove_time", "1500" );
if ( predictedProjectiles ) {
// ensure the projectile makes it in to at least one snapshot
int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); if ( removeTime < snapshotTime )
if ( removeTime < snapshotTime ) {
removeTime = snapshotTime;
}
}
CancelEvents( &EV_Fizzle );
PostEventMS( &EV_Remove, removeTime );
}
/*
================
idProjectile::Event_RadiusDamage
================
*/
void idProjectile::Event_RadiusDamage( idEntity *ignore ) {
const char *splash_damage = spawnArgs.GetString( "def_splash_damage" );
if ( splash_damage[0] != '\0' ) {
gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount );
}
}
/*
================
idProjectile::Event_ResidualDamage
================
*/
void idProjectile::Event_ResidualDamage ( idEntity* ignore ) {
const char *residual_damage = spawnArgs.GetString( "def_residual_damage" );
if ( residual_damage[0] != '\0' ) {
gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount );
}
// Keep the loop going
PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore );
}
void idProjectile::Event_EmitDamage ( idEntity* ignore ) {
const char *emit_damage = spawnArgs.GetString( "def_emit_damage" );
if ( emit_damage[0] != '\0' ) {
gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, emit_damage, damagePower, &hitCount );
}
// Keep the loop going
PostEventSec ( &EV_EmitDamage, spawnArgs.GetFloat ( "delay_emit_damage" ), ignore );
}
/*
================
idProjectile::Explode
================
*/
void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) {
idVec3 normal, endpos;
int removeTime;
if ( state == EXPLODED || state == FIZZLED ) {
return;
}
if ( predictedProjectiles && state == IMPACTED ) {
return;
}
CancelEvents( &EV_EmitDamage );
if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) {
GetPhysics()->SetAxis( normal.ToMat3() );
} else {
normal = collision ? collision->c.normal : idVec3( 0, 0, 1 );
}
endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin();
removeTime = spawnArgs.GetInt( "remove_time", "1500" );
// play sound
StopSound( SND_CHANNEL_BODY, false );
StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL );
idVec3 fxDir;
if ( physicsObj.GetGravityNormal( ) != vec3_zero ) {
fxDir = -physicsObj.GetGravityNormal( );
} else {
fxDir = -physicsObj.GetLinearVelocity( );
fxDir.Normalize( );
}
if ( predictedProjectiles ) {
if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) {
PlayDetonateEffect( endpos, fxDir.ToMat3() );
}
} else if ( showExplodeFX ) {
PlayDetonateEffect( endpos, fxDir.ToMat3() );
}
// Stop the fly effect without destroying particles to ensure the trail within can persist.
StopEffect( "fx_fly" );
// Stop the remaining particles
StopAllEffects( );
Hide();
FreeLightDef();
GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal );
fl.takedamage = false;
physicsObj.SetContents( 0 );
physicsObj.PutToRest();
if ( predictedProjectiles ) {
state = showExplodeFX ? EXPLODED : IMPACTED;
} else {
state = EXPLODED;
}
if ( gameLocal.isClient ) {
return;
}
// alert the ai
gameLocal.AlertAI( owner.GetEntity() );
// bind the projectile to the impact entity if necesary
if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) {
Bind( gameLocal.entities[collision->c.entityNum], true );
}
if ( predictedProjectiles ) {
removeTime = spawnArgs.GetInt( "detonate_remove_time", "0" );
// ensure the projectile makes it in to at least one snapshot
int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" );
if ( removeTime < snapshotTime ) {
removeTime = snapshotTime;
}
} else {
removeTime = 0;
}
// splash damage
float delay = spawnArgs.GetFloat( "delay_splash" );
if ( delay ) {
if ( removeTime < delay * 1000 ) {
removeTime = ( delay + 0.10 ) * 1000;
}
PostEventSec( &EV_RadiusDamage, delay, ignore );
} else {
Event_RadiusDamage( ignore );
}
// Residual damage (damage over time)
delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) );
if ( delay > 0.0f ) {
PostEventMS ( &EV_ResidualDamage, delay, ignore );
// Keep the projectile around until the residual damage is done
delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) );
if ( removeTime < delay ) {
removeTime = delay;
}
}
CancelEvents( &EV_Explode );
PostEventMS( &EV_Remove, removeTime );
}
/*
================
idProjectile::GetVelocity
================
*/
idVec3 idProjectile::GetVelocity( const idDict *projectile ) {
idVec3 velocity;
velocity.Zero ( );
if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) {
projectile->GetVector( "velocity", "0 0 0", velocity );
}
return velocity;
}
/*
================
idProjectile::GetGravity
================
*/
idVec3 idProjectile::GetGravity( const idDict *projectile ) {
if ( projectile ) {
return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" );
}
return vec3_origin;
}
/*
================
idProjectile::PlayPainEffect
================
*/
void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir ) {
static int damageTable[] = { 100, 50, 25, 10, 0 };
int index;
// Normalize the damage value to the damage table
for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ );
// loop until we find a pain effect, trying lower damage numbers if needed
for ( ; damageTable[index]; index ++ ) {
// Try the pain effect for the current damage value and if it plays then
// we are done
if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3() ) ) {
return;
}
}
// Play the default pain effect
ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() );
}
/*
================
idProjectile::PlayDetonateEffect
================
*/
void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis, bool forceImpact ) {
if( physicsObj.HasGroundContacts() || ( forceImpact && predictedProjectiles ) ) {
if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) {
trace_t tr;
idVec3 down;
down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f;
gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this );
gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true, true );
} else {
gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true, true );
}
return;
}
gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true, true );
}
/*
================
idProjectile::Event_Explode
================
*/
void idProjectile::Event_Explode( void ) {
// events are processed outside of the think loop, so set the current thinking ent appropriately
idEntity* think = gameLocal.currentThinkingEntity;
gameLocal.currentThinkingEntity = this;
Explode( NULL, true );
gameLocal.currentThinkingEntity = think;
}
/*
================
idProjectile::Event_Fizzle
================
*/
void idProjectile::Event_Fizzle( void ) {
idEntity* think = gameLocal.currentThinkingEntity;
gameLocal.currentThinkingEntity = this;
Fizzle();
gameLocal.currentThinkingEntity = think;
}
/*
================
idProjectile::Event_Touch
================
*/
void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) {
if ( IsHidden() ) {
return;
}
if ( other != owner.GetEntity() ) {
idEntity* think = gameLocal.currentThinkingEntity;
gameLocal.currentThinkingEntity = this;
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.c.point = GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
bool playDefaultDamageEffect = predictedProjectiles ? !playedDamageEffect : true;
if ( playDefaultDamageEffect ) {
DefaultDamageEffect( collision, collision.c.normal, NULL );
playedDamageEffect = true;
}
Explode( NULL, !predictedProjectiles );
gameLocal.currentThinkingEntity = think;
}
}
/*
================
idProjectile::ClientPredictionThink
================
*/
void idProjectile::ClientPredictionThink( void ) {
if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) {
return;
}
if ( !syncPhysics && state == LAUNCHED ) {
idMat3 axis = launchDir.ToMat3();
idVec3 origin( launchOrig );
origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir;
physicsObj.SetAxis( axis );
physicsObj.SetOrigin( origin );
physicsObj.SetLinearVelocity( launchSpeed * launchDir );
}
Think();
}
/*
================
idProjectile::WriteToSnapshot
================
*/
void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const {
if ( syncPhysics ) {
physicsObj.WriteToSnapshot( msg );
}
msg.WriteBits( state, 3 );
if ( state >= LAUNCHED ) {
// feed the client with start position, direction and time. let the client do everything else
// this won't change during projectile life and be completely deltified away
msg.WriteLong( launchTime );
msg.WriteFloat( launchOrig[ 0 ] );
msg.WriteFloat( launchOrig[ 1 ] );
msg.WriteFloat( launchOrig[ 2 ] );
msg.WriteDir( launchDir, 24 );
int ownerNum = (GetOwner() && GetOwner()->entityNumber < MAX_CLIENTS) ? GetOwner()->entityNumber : MAX_CLIENTS;
msg.WriteBits( ownerNum, idMath::BitsForInteger(MAX_CLIENTS) );
}
}
/*
================
idProjectile::ReadFromSnapshot
================
*/
void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) {
projectileState_t newState;
idEntity *ownerEnt = NULL;
int newLaunchTime;
if ( syncPhysics ) {
physicsObj.ReadFromSnapshot( msg );
}
newState = (projectileState_t) msg.ReadBits( 3 );
if ( newState >= LAUNCHED ) {
newLaunchTime = msg.ReadLong();
launchOrig[ 0 ] = msg.ReadFloat();
launchOrig[ 1 ] = msg.ReadFloat();
launchOrig[ 2 ] = msg.ReadFloat();
launchDir = msg.ReadDir( 24 );
int ownerNum = msg.ReadBits( idMath::BitsForInteger(MAX_CLIENTS) );
if ( ownerNum < MAX_CLIENTS && gameLocal.entities[ ownerNum ] && gameLocal.entities[ ownerNum ]->IsType( idPlayer::GetClassType() ) ) {
ownerEnt = gameLocal.entities[ ownerNum ];
}
if ( predictedProjectiles && launchTime != newLaunchTime ) {
launchTime = newLaunchTime;
if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) {
idMat3 axis = launchDir.ToMat3();
idVec3 origin( launchOrig );
physicsObj.SetAxis( axis );
physicsObj.SetOrigin( origin );
physicsObj.SetLinearVelocity( launchSpeed * launchDir );
physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time );
}
if ( state == LAUNCHED && newState == LAUNCHED ) {
Show();
}
UpdateVisuals();
}
if ( !predictedProjectiles ) {
launchTime = newLaunchTime;
}
}
// we always create and launch at the same time
// state on a client can be SPAWNED, LAUNCHED, EXPLODED
// expect never to get a CREATED projectile, they should launch right away
if ( predictedProjectiles ) {
if ( msg.HasChanged() ) {
if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) {
idMat3 axis = launchDir.ToMat3();
idVec3 origin( launchOrig );
physicsObj.SetAxis( axis );
physicsObj.SetOrigin( origin );
physicsObj.SetLinearVelocity( launchSpeed * launchDir );
physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time );
}
}
UpdateVisuals();
assert( state != CREATED && newState != CREATED );
if ( newState != state ) {
if ( state < LAUNCHED && newState >= LAUNCHED ) {
Create( ownerEnt, launchOrig, launchDir );
Launch( launchOrig, launchDir, vec3_origin );
Show();
}
switch ( newState ) {
case FIZZLED:
Fizzle();
break;
case EXPLODED:
case IMPACTED:
Explode( NULL, state == EXPLODED );
break;
}
}
} else {
assert( state != CREATED && state != FIZZLED && newState != CREATED );
if ( newState != state ) {
switch ( newState ) {
case LAUNCHED:
Create( NULL, launchOrig, launchDir );
Launch( launchOrig, launchDir, vec3_origin );
Show();
break;
case FIZZLED:
case EXPLODED:
if ( state != EXPLODED ) {
StopSound( SND_CHANNEL_BODY, false );
StopAllEffects();
Hide();
FreeLightDef();
state = EXPLODED;
}
break;
}
}
if ( !syncPhysics && state == LAUNCHED ) {
idMat3 axis = launchDir.ToMat3();
idVec3 origin( launchOrig );
origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir;
physicsObj.SetAxis( axis );
physicsObj.SetOrigin( origin );
physicsObj.SetLinearVelocity( launchSpeed * launchDir );
}
UpdateVisuals();
}
}
/*
===============
idProjectile::ClientStale
===============
*/
bool idProjectile::ClientStale( void ) {
// delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway )
StopAllEffects();
return true;
}
/*
================
idProjectile::GetPhysicsToVisualTransform
================
*/
bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose();
origin.Zero();
return true;
}
/*
===============================================================================
idGuidedProjectile
===============================================================================
*/
CLASS_DECLARATION( idProjectile, idGuidedProjectile )
END_CLASS
/*
================
idGuidedProjectile::idGuidedProjectile( void )
================
*/
idGuidedProjectile::idGuidedProjectile( void ) {
guideType = GUIDE_NONE;
turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f );
}
/*
=================
idGuidedProjectile::~idGuidedProjectile
=================
*/
idGuidedProjectile::~idGuidedProjectile() {
}
/*
================
idGuidedProjectile::Save
================
*/
void idGuidedProjectile::Save( idSaveGame *savefile ) const {
savefile->WriteInt ( guideType );
guideEnt.Save( savefile );
savefile->WriteVec3 ( guideDir );
savefile->WriteVec3 ( guidePos );
savefile->WriteJoint ( guideJoint );
savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var
savefile->WriteInt ( driftTime );
savefile->WriteInt ( driftRate );
savefile->WriteFloat ( driftRange );
savefile->WriteFloat ( driftRadius );
savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var
savefile->WriteFloat ( driftAngle );
savefile->WriteFloat ( driftAngleStep );
savefile->WriteFloat ( driftProjectRange );
savefile->WriteFloat( turn_max.GetStartTime() );
savefile->WriteFloat( turn_max.GetDuration() );
savefile->WriteFloat( turn_max.GetStartValue() );
savefile->WriteFloat( turn_max.GetEndValue() );
savefile->WriteInt ( launchTime );
savefile->WriteInt ( guideDelay );
savefile->WriteInt ( driftDelay );
}
/*
================
idGuidedProjectile::Restore
================
*/
void idGuidedProjectile::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadInt ( guideType );
guideEnt.Restore( savefile );
savefile->ReadVec3 ( guideDir );
savefile->ReadVec3 ( guidePos );
savefile->ReadJoint ( guideJoint );
savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var
savefile->ReadInt ( driftTime );
savefile->ReadInt ( driftRate );
savefile->ReadFloat ( driftRange );
savefile->ReadFloat ( driftRadius );
savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var
savefile->ReadFloat ( driftAngle );
savefile->ReadFloat ( driftAngleStep );
savefile->ReadFloat ( driftProjectRange );
savefile->ReadFloat( set );
turn_max.SetStartTime( set );
savefile->ReadFloat( set );
turn_max.SetDuration( set );
savefile->ReadFloat( set );
turn_max.SetStartValue( set );
savefile->ReadFloat( set );
turn_max.SetEndValue( set );
savefile->ReadInt ( launchTime );
savefile->ReadInt ( guideDelay );
savefile->ReadInt ( driftDelay );
}
/*
================
idGuidedProjectile::GetGuideDir
================
*/
bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist ) {
// Dont start guiding immeidately?
if ( gameLocal.GetTime() - launchTime < guideDelay ) {
return false;
}
switch ( guideType ) {
case GUIDE_ENTITY:
// If the guide entity is gone or dead then cancel the guide
if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) {
CancelGuide ( );
return false;
}
// Use eye position for actors and center of bounds for everything else
if ( guideJoint != INVALID_JOINT ) {
idMat3 jointAxis;
guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis );
outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis);
if ( !guidePos.Compare( vec3_origin ) ) {
jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis;
outDir += jointAxis[0]*guidePos[0];
outDir += jointAxis[1]*guidePos[1];
outDir += jointAxis[2]*guidePos[2];
}
} else {
outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter();
if ( guideEnt->IsType( idActor::GetClassType() ) ) {
outDir += static_cast<idActor *>(guideEnt.GetEntity())->GetEyePosition();
outDir *= 0.5f;
}
}
outDir -= physicsObj.GetOrigin();
break;
case GUIDE_POS:
outDir = guidePos - physicsObj.GetOrigin();
break;
case GUIDE_DIR:
// Project our current position on to the desired direction
outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir);
// Seek towards a point forward along our desired direction
outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin();
break;
default:
return false;
}
// Add drifting
if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) {
idMat3 axis;
outDist = outDir.NormalizeFast();
axis = outDir.ToMat3();
if ( gameLocal.time > driftTime ) {
driftRadius = driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity;
driftTime = gameLocal.time + driftRate;
} else {
driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec );
idMath::AngleNormalize360 ( driftAngle );
}
float angle;
angle = DEG2RAD ( driftAngle );
outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange );
outDir += axis[2] * (driftRadius * idMath::Sin ( angle ));
outDir += axis[1] * (driftRadius * idMath::Cos ( angle ));
outDir -= physicsObj.GetOrigin();
}
outDist = outDir.Normalize ( );
return true;
}
/*
================
idGuidedProjectile::Think
================
*/
void idGuidedProjectile::Think( void ) {
if ( state == LAUNCHED ) {
idVec3 dir;
idVec3 vel;
float angle;
float maxangle;
idMat3 axis;
float dist;
// crank up to normal speed ?
if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) {
float newSpeed = spawnArgs.GetFloat( "speed" );
float newSpeed2;
if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) {
newSpeed2 = newSpeed;
}
float newSpeedDuration;
newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) );
speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 );
guideDelay = 0;
}
if ( !GetGuideDir( dir, dist ) ) {
idProjectile::Think();
return;
}
// Direction of travel
vel = physicsObj.GetLinearVelocity();
vel.Normalize();
// Calculate the angle between the current projectile direction and where we want to go
angle = RAD2DEG( idMath::ACos( dir * vel ) );
// Make sure the angle doesnt cross our max turn radius
maxangle = turn_max.GetCurrentValue( gameLocal.time );
if ( angle < -maxangle ) {
angle = -maxangle;
} else if ( angle > maxangle ) {
angle = maxangle;
}
// Debug information
if ( g_debugWeapon.GetBool ( ) ) {
gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f );
gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f );
}
// Calculate the new axis by rotating the current forward vector around the cross of the forward
// vector and the direction vector.
vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle );
physicsObj.SetLinearVelocity( vel * GetSpeed ( ) );
// If within the minium distance to the target anything over a 45 degree change will cancel the guide
if ( guideMinDist != 0.0f && dist < guideMinDist ) {
// Stop guiding if we have passed our target
vel = physicsObj.GetLinearVelocity ( );
vel.Normalize( );
if ( vel * dir < 0.7f ) {
guideType = GUIDE_NONE;
}
}
idProjectile::Think();
} else {
idProjectile::Think();
}
}
/*
=================
idGuidedProjectile::Launch
=================
*/
void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) {
idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower );
launchTime = gameLocal.GetTime();
if ( owner.GetEntity() ) {
if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) {
GuideTo ( static_cast<idAI *>( owner.GetEntity() )->GetEnemy() );
}
}
guideMinDist = spawnArgs.GetFloat ( "min_dist", "128" );
guideDelay = SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) );
if ( guideDelay ) {
float delaySpeed;
if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) ) {
float delaySpeed2;
if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) {
delaySpeed2 = delaySpeed;
}
float delaySpeedDuration;
delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) );
speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 );
physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity );
}
}
driftTime = 0;
driftRate = SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) );
driftRange = spawnArgs.GetFloat ( "driftRange" );
driftDiversity = spawnArgs.GetFloat ( "driftDiversity", ".5" );
driftAngle = gameLocal.random.RandomFloat ( ) * 360.0f;
driftAngleStep = spawnArgs.GetFloat ( "driftRotate" );
driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f);
driftDelay = SEC2MS(spawnArgs.GetFloat ( "driftDelay" ));
driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" );
// Turn rate can be ramped up over time
turn_max.Init ( gameLocal.time,
SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )),
0,
spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() );
UpdateVisuals();
}
/*
=================
idGuidedProjectile::Launch
=================
*/
void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if ( guideEnt.IsValid() ) {
guideEnt->GuidedProjectileIncoming( NULL );
}
idProjectile::Killed( inflictor, attacker, damage, dir, location );
}
/*
===============================================================================
rvDriftingProjectile
===============================================================================
*/
CLASS_DECLARATION( idProjectile, rvDriftingProjectile )
END_CLASS
/*
================
rvDriftingProjectile::rvDriftingProjectile( void )
================
*/
rvDriftingProjectile::rvDriftingProjectile( void ) {
}
/*
=================
rvDriftingProjectile::~rvDriftingProjectile
=================
*/
rvDriftingProjectile::~rvDriftingProjectile ( void ) {
}
/*
================
rvDriftingProjectile::Save
================
*/
void rvDriftingProjectile::Save( idSaveGame *savefile ) const {
savefile->WriteVec3 ( startDir );
savefile->WriteVec3 ( startOrigin );
savefile->WriteMat3 ( startAxis );
savefile->WriteFloat ( startSpeed );
savefile->WriteFloat ( driftOffsetMax );
savefile->WriteFloat ( driftSpeedMax );
savefile->WriteFloat ( driftTime );
savefile->WriteInterpolate( driftSpeed );
for( int ix = 0; ix < 2; ++ix ) {
savefile->WriteInterpolate( driftOffset[ix] );
}
}
/*
================
rvDriftingProjectile::Restore
================
*/
void rvDriftingProjectile::Restore( idRestoreGame *savefile ) {
savefile->ReadVec3 ( startDir );
savefile->ReadVec3 ( startOrigin );
savefile->ReadMat3 ( startAxis );
savefile->ReadFloat ( startSpeed );
savefile->ReadFloat ( driftOffsetMax );
savefile->ReadFloat ( driftSpeedMax );
savefile->ReadFloat ( driftTime );
savefile->ReadInterpolate( driftSpeed );
for( int ix = 0; ix < 2; ++ix ) {
savefile->ReadInterpolate( driftOffset[ix] );
}
}
/*
================
rvDriftingProjectile::Think
================
*/
void rvDriftingProjectile::Think( void ) {
idVec3 diff;
idVec3 origin;
idVec3 oldOrigin;
idVec3 dir;
float dist;
oldOrigin = GetPhysics()->GetOrigin ( );
diff = oldOrigin - startOrigin;
dist = diff.Length ( );
origin = startOrigin + startDir * dist;
if ( driftSpeed.IsDone ( gameLocal.time ) ) {
driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime,
driftSpeed.GetCurrentValue ( gameLocal.time ),
gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) );
}
if ( driftOffset[0].IsDone ( gameLocal.time ) ) {
driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime,
driftOffset[0].GetCurrentValue ( gameLocal.time ),
gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) );
}
if ( driftOffset[1].IsDone ( gameLocal.time ) ) {
driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime,
driftOffset[1].GetCurrentValue ( gameLocal.time ),
gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) );
}
origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time );
origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time );
GetPhysics ( )->SetOrigin ( origin );
GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) );
idProjectile::Think();
// Now orient the projectile using the old origin
dir = GetPhysics()->GetOrigin() - oldOrigin;
dir.Normalize ( );
GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) );
}
/*
=================
rvDriftingProjectile::Launch
=================
*/
void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) {
startDir = dir;
startOrigin = start;
startAxis = dir.ToMat3();
driftOffsetMax = spawnArgs.GetFloat ( "driftOffset", "50" );
driftSpeedMax = spawnArgs.GetFloat ( "driftSpeed", "50" );
driftTime = SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) );
idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower );
startSpeed = GetPhysics()->GetLinearVelocity().Length ( );
}
/*
=================
rvDriftingProjectile::Launch
=================
*/
void rvDriftingProjectile::UpdateVisualAngles ( void ) {
rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat() );
}
/*
===============================================================================
rvSpawnerProjectile
===============================================================================
*/
CLASS_DECLARATION( idProjectile, rvSpawnerProjectile )
EVENT( EV_PostSpawn, rvSpawnerProjectile::Event_PostSpawn )
END_CLASS
/*
================
rvSpawnerProjectile::rvSpawnerProjectile( void )
================
*/
rvSpawnerProjectile::rvSpawnerProjectile( void ) {
spawnState = STATE_NONE;
}
/*
=================
rvSpawnerProjectile::~rvSpawnerProjectile
=================
*/
rvSpawnerProjectile::~rvSpawnerProjectile ( void ) {
if ( spawnState == STATE_ADDED && spawner ) {
spawner->RemoveSpawnPoint ( this );
}
}
/*
=================
rvSpawnerProjectile::SetSpawner
=================
*/
void rvSpawnerProjectile::Spawn ( void ) {
if ( *spawnArgs.GetString ( "spawner" ) ) {
PostEventMS ( &EV_PostSpawn, 0 );
}
}
/*
=================
rvSpawnerProjectile::SetSpawner
=================
*/
void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) {
spawner = _spawner;
}
/*
=================
rvSpawnerProjectile::Think
=================
*/
void rvSpawnerProjectile::Think ( void ) {
idProjectile::Think ( );
if ( physicsObj.IsAtRest ( ) ) {
if ( spawnState == STATE_NONE && spawner ) {
spawner->AddSpawnPoint ( this );
spawnState = STATE_ADDED;
}
}
}
/*
=================
rvSpawnerProjectile::Event_PostSpawn
=================
*/
void rvSpawnerProjectile::Event_PostSpawn ( void ) {
const char* temp;
temp = spawnArgs.GetString ( "spawner" );
if ( temp && *temp ) {
idEntity* ent;
ent = gameLocal.FindEntity ( temp );
if ( !ent ) {
gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) );
} else if ( !ent->IsType ( rvSpawner::GetClassType() ) ) {
gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) );
} else {
SetSpawner ( static_cast<rvSpawner*>(ent) );
}
}
}
/*
===============================================================================
rvMIRVProjectile
===============================================================================
*/
idEventDef EV_LaunchWarheads( "launchWarheads" );
CLASS_DECLARATION( idProjectile, rvMIRVProjectile )
EVENT( EV_LaunchWarheads, rvMIRVProjectile::Event_LaunchWarheads )
END_CLASS
/*
================
rvMIRVProjectile::rvMIRVProjectile( void )
================
*/
rvMIRVProjectile::rvMIRVProjectile( void ) {
}
/*
=================
rvMIRVProjectile::~rvMIRVProjectile
=================
*/
rvMIRVProjectile::~rvMIRVProjectile ( void ) {
}
/*
================
void rvMIRVProjectile::Spawn( void )
================
*/
void rvMIRVProjectile::Spawn( void ) {
float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0");
//post event for warhead launch
PostEventSec( &EV_LaunchWarheads, launchDelay );
}
/*
================
void rvMIRVProjectile::Event_LaunchWarheads( void )
================
*/
void rvMIRVProjectile::Event_LaunchWarheads( void ) {
const char* warhead;
int count;
warhead = spawnArgs.GetString("def_warhead","");
count = spawnArgs.GetFloat("warhead_count","0");
if( warhead && warhead[0] ) {
//start launching!
float angle = (360.0f / count);
idMat3 normalMat = this->GetPhysics()->GetAxis( );
idVec3 normal = normalMat[0];
idVec3 axis = normalMat[1];
float t;
idMat3 axisMat;
idProjectile * warheadEntity;
//hey how 'bout that.
normal.Normalize();
for( t = 0; t< count; t++) {
//rotate axis around normal by angle degrees.
axisMat = axis.ToMat3();
axisMat.RotateArbitrary( normal, angle * t);
warheadEntity = static_cast<idProjectile*>(gameLocal.SpawnEntityDef( warhead));
warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this );
warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] );
}
//foom!
gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
PostEventSec( &EV_Explode, 0 );
}
}
// RAVEN END