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

1448 lines
42 KiB
C++

#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "../vehicle/Vehicle.h"
#define MAX_MISSILE_JOINTS 4
#define MAX_HOVER_JOINTS 4
class rvMonsterStroggHover : public idAI {
public:
CLASS_PROTOTYPE( rvMonsterStroggHover );
rvMonsterStroggHover ( void );
~rvMonsterStroggHover ( void );
void InitSpawnArgsVariables( void );
void Spawn ( void );
void Save ( idSaveGame *savefile ) const;
void Restore ( idRestoreGame *savefile );
virtual void Think ( void );
virtual bool Collide ( const trace_t &collision, const idVec3 &velocity );
virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void OnDeath ( void );
virtual void DeadMove ( void );
virtual bool SkipImpulse ( idEntity *ent, int id );
virtual int FilterTactical ( int availableTactical );
virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData );
virtual void Hide( void );
virtual void Show( void );
void StartHeadlight ( void );
void StopHeadlight ( void );
protected:
// rvAIAction actionRocketAttack;
// rvAIAction actionBlasterAttack;
rvAIAction actionMGunAttack;
rvAIAction actionMissileAttack;
rvAIAction actionBombAttack;
rvAIAction actionStrafe;
rvAIAction actionCircleStrafe;
virtual bool CheckActions ( void );
virtual void OnEnemyChange ( idEntity* oldEnemy );
virtual void OnStartMoving ( void );
virtual const char* GetIdleAnimName ( void );
private:
idEntityPtr<idEntity> marker;
idVec3 attackPosOffset;
bool inPursuit;
int holdPosTime;
int strafeTime;
bool strafeRight;
bool circleStrafing;
float deathPitch;
float deathRoll;
float deathPitchRate;
float deathYawRate;
float deathRollRate;
float deathSpeed;
float deathGrav;
int markerCheckTime;
bool MarkerPosValid ( void );
void TryStartPursuit ( void );
void Pursue ( void );
void CircleStrafe ( void );
void Evade ( bool left );
int mGunFireRate;
int missileFireRate;
int bombFireRate;
int nextMGunFireTime;
int nextMissileFireTime;
int nextBombFireTime;
int mGunMinShots;
int mGunMaxShots;
int missileMinShots;
int missileMaxShots;
int bombMinShots;
int bombMaxShots;
int shots;
int evadeDebounce;
int evadeDebounceRate;
float evadeChance;
float evadeSpeed;
float strafeSpeed;
float circleStrafeSpeed;
rvClientEffectPtr effectDust;
rvClientEffectPtr effectHover[MAX_HOVER_JOINTS];
rvClientEffectPtr effectHeadlight;
jointHandle_t jointDust;
int numHoverJoints;
jointHandle_t jointHover[MAX_HOVER_JOINTS];
jointHandle_t jointBomb;
jointHandle_t jointMGun;
int numMissileJoints;
jointHandle_t jointMissile[MAX_MISSILE_JOINTS];
jointHandle_t jointHeadlight;
jointHandle_t jointHeadlightControl;
renderLight_t renderLight;
int lightHandle;
// bool lightOn;
void DoNamedAttack ( const char* attackName, jointHandle_t joint );
void UpdateLightDef ( void );
bool CheckAction_Strafe ( rvAIAction* action, int animNum );
bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum );
bool CheckAction_BombAttack ( rvAIAction* action, int animNum );
//virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum );
//virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum );
// stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms );
// stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms );
stateResult_t State_Torso_MGunAttack ( const stateParms_t& parms );
stateResult_t State_Torso_MissileAttack ( const stateParms_t& parms );
stateResult_t State_Torso_BombAttack ( const stateParms_t& parms );
// stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms );
// stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms );
stateResult_t State_Torso_Strafe ( const stateParms_t& parms );
stateResult_t State_Torso_CircleStrafe ( const stateParms_t& parms );
stateResult_t State_CircleStrafe ( const stateParms_t& parms );
stateResult_t State_DeathSpiral ( const stateParms_t& parms );
stateResult_t State_Pursue ( const stateParms_t& parms );
CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover );
};
CLASS_DECLARATION( idAI, rvMonsterStroggHover )
END_CLASS
/*
================
rvMonsterStroggHover::rvMonsterStroggHover
================
*/
rvMonsterStroggHover::rvMonsterStroggHover ( ) {
effectDust = NULL;
for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) {
effectHover[i] = NULL;
}
effectHeadlight = NULL;
shots = 0;
strafeTime = 0;
strafeRight = false;
circleStrafing = false;
evadeDebounce = 0;
deathPitch = 0;
deathRoll = 0;
deathPitchRate = 0;
deathYawRate = 0;
deathRollRate = 0;
deathSpeed = 0;
deathGrav = 0;
markerCheckTime = 0;
marker = NULL;
attackPosOffset.Zero();
inPursuit = false;
holdPosTime = 0;
nextMGunFireTime = 0;
nextMissileFireTime = 0;
nextBombFireTime = 0;
lightHandle = -1;
}
/*
================
rvMonsterStroggHover::~rvMonsterStroggHover
================
*/
rvMonsterStroggHover::~rvMonsterStroggHover ( ) {
if ( lightHandle != -1 ) {
gameRenderWorld->FreeLightDef( lightHandle );
lightHandle = -1;
}
}
void rvMonsterStroggHover::InitSpawnArgsVariables( void )
{
numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" ));
for ( int i = 0; i < numHoverJoints; i++ ) {
jointHover[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) );
}
jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) );
jointMGun = animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) );
numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" ));
for ( int i = 0; i < numMissileJoints; i++ ) {
jointMissile[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) );
}
jointBomb = animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) );
mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" ));
missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" ));
bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" ));
mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" );
mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" );
missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" );
missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" );
bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" );
bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" );
evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" ));
evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" );
evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" );
strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" );
circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" );
//LIGHT
jointHeadlight = animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) );
jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) );
}
void rvMonsterStroggHover::Hide( void )
{
StopHeadlight();
idAI::Hide();
}
void rvMonsterStroggHover::Show( void )
{
idAI::Show();
StartHeadlight();
}
void rvMonsterStroggHover::StartHeadlight( void )
{
if ( jointHeadlight != INVALID_JOINT )
{
lightHandle = -1;
if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) > 1 && spawnArgs.GetString("mtr_light") ) {
idVec3 color;
//const char* temp;
const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false );
if ( headLightMaterial )
{
renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false );
renderLight.pointLight = spawnArgs.GetBool( "light_pointlight", "1" );
// RAVEN BEGIN
// dluetscher: added detail levels to render lights
renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
// RAVEN END
spawnArgs.GetVector( "light_color", "0 0 0", color );
renderLight.shaderParms[ SHADERPARM_RED ] = color[0];
renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1];
renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2];
renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
renderLight.lightRadius[0] = renderLight.lightRadius[1] =
renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" );
if ( !renderLight.pointLight ) {
renderLight.target = spawnArgs.GetVector( "light_target" );
renderLight.up = spawnArgs.GetVector( "light_up" );
renderLight.right = spawnArgs.GetVector( "light_right" );
renderLight.end = spawnArgs.GetVector( "light_target" );;
}
//lightOn = spawnArgs.GetBool( "start_on", "1" );
lightHandle = gameRenderWorld->AddLightDef( &renderLight );
}
}
// Hide flare surface if there is one
/*
temp = spawnArgs.GetString ( "light_flaresurface", "" );
if ( temp && *temp ) {
parent->ProcessEvent ( &EV_HideSurface, temp );
}
*/
// Sounds shader when turning light
//spawnArgs.GetString ( "snd_on", "", soundOn );
// Sound shader when turning light off
//spawnArgs.GetString ( "snd_off", "", soundOff);
UpdateLightDef ( );
}
}
void rvMonsterStroggHover::StopHeadlight( void )
{
if ( lightHandle != -1 ) {
gameRenderWorld->FreeLightDef ( lightHandle );
lightHandle = -1;
}
memset ( &renderLight, 0, sizeof(renderLight) );
}
/*
================
rvMonsterStroggHover::Spawn
================
*/
void rvMonsterStroggHover::Spawn ( void ) {
// actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK );
// actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK );
actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK );
actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK );
actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK );
actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 );
actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe", AIACTIONF_ATTACK );
InitSpawnArgsVariables();
evadeDebounce = 0;
numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" ));
for ( int i = 0; i < numHoverJoints; i++ ) {
if ( jointHover[i] != INVALID_JOINT ) {
effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true );
}
}
if ( !marker ) {
marker = gameLocal.SpawnEntityDef( "target_null" );
}
//LIGHT
StopHeadlight();
StartHeadlight();
if ( jointHeadlight != INVALID_JOINT )
{
effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true );
}
}
/*
================
rvMonsterStroggHover::GetDebugInfo
================
*/
void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) {
// Base class first
idAI::GetDebugInfo ( proc, userData );
proc ( "rvMonsterStroggHover", "action_mGunAttack", aiActionStatusString[actionMGunAttack.status], userData );
proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData );
proc ( "rvMonsterStroggHover", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData );
proc ( "rvMonsterStroggHover", "action_strafe", aiActionStatusString[actionStrafe.status], userData );
proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData );
proc ( "rvMonsterStroggHover", "inPursuit", inPursuit?"true":"false", userData );
proc ( "rvMonsterStroggHover", "marker", (!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData );
proc ( "rvMonsterStroggHover", "holdPosTime", va("%d",holdPosTime), userData );
proc ( "rvMonsterStroggHover", "circleStrafing", circleStrafing?"true":"false", userData );
proc ( "rvMonsterStroggHover", "strafeRight", strafeRight?"true":"false", userData );
proc ( "rvMonsterStroggHover", "strafeTime", va("%d",strafeTime), userData );
proc ( "rvMonsterStroggHover", "mGunFireRate", va("%d",mGunFireRate), userData );
proc ( "rvMonsterStroggHover", "missileFireRate", va("%d",missileFireRate), userData );
proc ( "rvMonsterStroggHover", "bombFireRate", va("%d",bombFireRate), userData );
proc ( "rvMonsterStroggHover", "mGunMinShots", va("%d",mGunMinShots), userData );
proc ( "rvMonsterStroggHover", "mGunMaxShots", va("%d",mGunMaxShots), userData );
proc ( "rvMonsterStroggHover", "missileMinShots", va("%d",missileMinShots), userData );
proc ( "rvMonsterStroggHover", "missileMaxShots", va("%d",missileMaxShots), userData );
proc ( "rvMonsterStroggHover", "bombMinShots", va("%d",bombMinShots), userData );
proc ( "rvMonsterStroggHover", "bombMaxShots", va("%d",bombMaxShots), userData );
proc ( "rvMonsterStroggHover", "nextMGunFireTime", va("%d",nextMGunFireTime), userData );
proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData );
proc ( "rvMonsterStroggHover", "nextBombFireTime", va("%d",nextBombFireTime), userData );
proc ( "rvMonsterStroggHover", "shots", va("%d",shots), userData );
proc ( "rvMonsterStroggHover", "evadeDebounce", va("%d",evadeDebounce), userData );
proc ( "rvMonsterStroggHover", "evadeDebounceRate", va("%d",evadeDebounceRate), userData );
proc ( "rvMonsterStroggHover", "evadeChance", va("%g",evadeChance), userData );
proc ( "rvMonsterStroggHover", "evadeSpeed", va("%g",evadeSpeed), userData );
proc ( "rvMonsterStroggHover", "strafeSpeed", va("%g",strafeSpeed), userData );
proc ( "rvMonsterStroggHover", "circleStrafeSpeed", va("%g",circleStrafeSpeed), userData );
}
/*
=====================
rvMonsterStroggHover::UpdateLightDef
=====================
*/
void rvMonsterStroggHover::UpdateLightDef ( void ) {
if ( jointHeadlight != INVALID_JOINT )
{
idVec3 origin;
idMat3 axis;
if ( jointHeadlightControl != INVALID_JOINT ) {
idAngles jointAng;
jointAng.Zero();
jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI );
jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI );
animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() );
}
GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis );
//origin += (localOffset * axis);
// Include this part in the total bounds
// FIXME: bounds are local
//parent->AddToBounds ( worldOrigin );
//UpdateOrigin ( );
if ( lightHandle != -1 ) {
renderLight.origin = origin;
renderLight.axis = axis;
gameRenderWorld->UpdateLightDef( lightHandle, &renderLight );
}
}
}
/*
================
rvMonsterStroggHover::Think
================
*/
void rvMonsterStroggHover::Think ( void ) {
idAI::Think ( );
if ( !aifl.dead )
{
// If thinking we should play an effect on the ground under us
if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) {
trace_t tr;
idVec3 origin;
idMat3 axis;
// Project the effect 80 units down from the bottom of our bbox
GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis );
// RAVEN BEGIN
// ddynerman: multiple clip worlds
gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this );
// RAVEN END
// Start the dust effect if not already started
if ( !effectDust ) {
effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true );
}
// If the effect is playing we should update its attenuation as well as its origin and axis
if ( effectDust ) {
effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) );
effectDust->SetOrigin ( tr.endpos );
effectDust->SetAxis ( tr.c.normal.ToMat3() );
}
// If the hover effect is playing we can set its end origin to the ground
/*
if ( effectHover ) {
effectHover->SetEndOrigin ( tr.endpos );
}
*/
} else if ( effectDust ) {
effectDust->Stop ( );
effectDust = NULL;
}
//Try to circle strafe or pursue
if ( circleStrafing )
{
CircleStrafe();
}
else if ( !inPursuit )
{
if ( !aifl.action && move.fl.done && !aifl.scripted )
{
if ( GetEnemy() )
{
if ( DistanceTo( GetEnemy() ) > 2000.0f
|| (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f )
{//enemy is far away or moving away from us at a pretty decent speed
TryStartPursuit();
}
}
}
}
else
{
Pursue();
}
//Dodge
if ( !circleStrafing ) {
if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) {
if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) {
if ( gameLocal.random.RandomFloat() > evadeChance ) {
//40% chance of ignoring it - makes them dodge rockets less often but bullets more often?
combat.shotAtTime = 0;
} else if ( evadeDebounce < gameLocal.GetTime() ) {
//ramps down from 400 to 100 over 1 second
float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f)));
idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed;
evadeVel.z *= 0.5f;
move.addVelocity += evadeVel;
move.addVelocity.Normalize();
move.addVelocity *= speed;
/*
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) {
//just need to do it once?
combat.shotAtTime = 0;
}
*/
if ( evadeDebounceRate > 1 )
{
evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f));
}
}
}
}
}
//If using melee rush to nav to him, stop when we're close enough to attack
if ( combat.tacticalCurrent == AITACTICAL_MELEE
&& move.moveCommand == MOVE_TO_ENEMY
&& !move.fl.done
&& nextBombFireTime < gameLocal.GetTime() - 3000
&& enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) {
StopMove( MOVE_STATUS_DONE );
ForceTacticalUpdate();
} else {
//whenever we're not in the middle of something, force an update of our tactical
if ( !aifl.action ) {
if ( !aasFind ) {
if ( move.fl.done ) {
if ( !inPursuit && !circleStrafing ) {
ForceTacticalUpdate();
}
}
}
}
}
}
//update light
// if ( lightOn ) {
UpdateLightDef ( );
// }
}
/*
============
rvMonsterStroggHover::OnStartMoving
============
*/
void rvMonsterStroggHover::OnStartMoving ( void ) {
idAI::OnStartMoving();
if ( move.moveCommand == MOVE_TO_ENEMY ) {
move.range = combat.meleeRange;
}
}
/*
================
rvMonsterStroggHover::Save
================
*/
void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const {
savefile->WriteInt ( shots );
savefile->WriteInt ( strafeTime );
savefile->WriteBool ( strafeRight );
savefile->WriteBool ( circleStrafing );
savefile->WriteFloat ( deathPitch );
savefile->WriteFloat ( deathRoll );
savefile->WriteFloat ( deathPitchRate );
savefile->WriteFloat ( deathYawRate );
savefile->WriteFloat ( deathRollRate );
savefile->WriteFloat ( deathSpeed );
savefile->WriteFloat ( deathGrav );
savefile->WriteInt ( markerCheckTime );
savefile->WriteVec3( attackPosOffset );
// actionRocketAttack.Save ( savefile );
// actionBlasterAttack.Save ( savefile );
actionMGunAttack.Save ( savefile );
actionMissileAttack.Save ( savefile );
actionBombAttack.Save ( savefile );
actionStrafe.Save ( savefile );
actionCircleStrafe.Save ( savefile );
for ( int i = 0; i < numHoverJoints; i++ ) {
effectHover[i].Save ( savefile );
}
effectDust.Save ( savefile );
effectHeadlight.Save ( savefile );
marker.Save( savefile );
savefile->WriteBool ( inPursuit );
savefile->WriteInt ( holdPosTime );
savefile->WriteInt ( nextMGunFireTime );
savefile->WriteInt ( nextMissileFireTime );
savefile->WriteInt ( nextBombFireTime );
savefile->WriteRenderLight ( renderLight );
savefile->WriteInt ( lightHandle );
savefile->WriteInt( evadeDebounce );
}
/*
================
rvMonsterStroggHover::Restore
================
*/
void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) {
savefile->ReadInt ( shots );
savefile->ReadInt ( strafeTime );
savefile->ReadBool ( strafeRight );
savefile->ReadBool ( circleStrafing );
savefile->ReadFloat ( deathPitch );
savefile->ReadFloat ( deathRoll );
savefile->ReadFloat ( deathPitchRate );
savefile->ReadFloat ( deathYawRate );
savefile->ReadFloat ( deathRollRate );
savefile->ReadFloat ( deathSpeed );
savefile->ReadFloat ( deathGrav );
savefile->ReadInt ( markerCheckTime );
savefile->ReadVec3( attackPosOffset );
// actionRocketAttack.Restore ( savefile );
// actionBlasterAttack.Restore ( savefile );
actionMGunAttack.Restore ( savefile );
actionMissileAttack.Restore ( savefile );
actionBombAttack.Restore ( savefile );
actionStrafe.Restore ( savefile );
actionCircleStrafe.Restore ( savefile );
InitSpawnArgsVariables();
//NOTE: if the def file changes the the number of numHoverJoints, this will be BAD...
for ( int i = 0; i < numHoverJoints; i++ ) {
effectHover[i].Restore ( savefile );
}
effectDust.Restore ( savefile );
effectHeadlight.Restore ( savefile );
marker.Restore( savefile );
savefile->ReadBool ( inPursuit );
savefile->ReadInt ( holdPosTime );
savefile->ReadInt ( nextMGunFireTime );
savefile->ReadInt ( nextMissileFireTime );
savefile->ReadInt ( nextBombFireTime );
savefile->ReadRenderLight ( renderLight );
savefile->ReadInt ( lightHandle );
if ( lightHandle != -1 ) {
//get the handle again as it's out of date after a restore!
lightHandle = gameRenderWorld->AddLightDef( &renderLight );
}
savefile->ReadInt ( evadeDebounce );
}
/*
================
rvMonsterStroggHover::Collide
================
*/
bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) {
if ( aifl.dead ) {
StopHeadlight();
//stop headlight
if ( effectHeadlight ) {
effectHeadlight->Stop ( );
effectHeadlight = NULL;
}
// Stop the crash & burn effect
for ( int i = 0; i < numHoverJoints; i++ ) {
if ( effectHover[i] ) {
effectHover[i]->Stop ( );
effectHover[i] = NULL;
}
}
gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
SetState ( "State_Remove" );
return false;
}
return idAI::Collide( collision, velocity );
}
/*
================
rvMonsterStroggHover::Damage
================
*/
void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
const char *damageDefName, const float damageScale, const int location ) {
if ( attacker == this ) {
return;
}
bool wasDead = aifl.dead;
idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
if ( !wasDead && aifl.dead ) {
SetState( "State_DeathSpiral" );
}
}
/*
================
rvMonsterStroggHover::OnDeath
================
*/
void rvMonsterStroggHover::OnDeath ( void ) {
// Stop the dust effect
if ( effectDust ) {
effectDust->Stop ( );
effectDust = NULL;
}
// Stop the hover effect
for ( int i = 0; i < numHoverJoints; i++ ) {
if ( effectHover[i] ) {
effectHover[i]->Stop ( );
effectHover[i] = NULL;
}
}
idAI::OnDeath ( );
}
/*
=====================
rvMonsterStroggHover::DeadMove
=====================
*/
void rvMonsterStroggHover::DeadMove( void ) {
DeathPush ( );
physicsObj.UseVelocityMove( true );
RunPhysics();
}
/*
=====================
rvMonsterStroggHover::SkipImpulse
=====================
*/
bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) {
return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK));
}
/*
================
rvMonsterStroggHover::CheckAction_Strafe
================
*/
bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) {
if ( inPursuit && !holdPosTime ) {
return false;
}
if ( !enemy.fl.visible ) {
return false;
}
if ( !enemy.fl.inFov ) {
return false;
}
if ( !move.fl.done ) {
return false;
}
if ( evadeDebounce >= gameLocal.GetTime() ) {
return false;
}
if ( animNum != -1 && !TestAnimMove ( animNum ) ) {
//well, at least try a new attack position
if ( combat.tacticalCurrent == AITACTICAL_RANGED ) {
combat.tacticalUpdateTime = 0;
}
return false;
}
return true;
}
/*
================
rvMonsterStroggHover::CheckAction_CircleStrafe
================
*/
bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) {
if ( inPursuit ) {
return false;
}
if ( !enemy.fl.visible ) {
return false;
}
if ( !enemy.fl.inFov ) {
return false;
}
if ( !move.fl.done ) {
return false;
}
return true;
}
/*
================
rvMonsterStroggHover::CheckAction_CircleStrafe
================
*/
bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) {
if ( !GetEnemy() || !enemy.fl.visible ) {
return false;
}
/*
if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) {
//not moving enough
return false;
}
*/
if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) {
//enemy is moving toward me, drop 'em!
return true;
}
return false;
}
/*
================
rvMonsterStroggHover::Spawn
================
*/
bool rvMonsterStroggHover::CheckActions ( void ) {
if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) {
return true;
}
/*
if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) {
return true;
}
if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
*/
if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) {
return true;
}
if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
if ( idAI::CheckActions ( ) ) {
return true;
}
if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) {
return true;
}
return false;
}
/*
================
rvMonsterStroggHover::OnEnemyChange
================
*/
void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) {
idAI::OnEnemyChange ( oldEnemy );
if ( !enemy.ent ) {
return;
}
}
/*
================
rvMonsterStroggHover::GetIdleAnimName
================
*/
const char* rvMonsterStroggHover::GetIdleAnimName ( void ) {
/*
if ( move.moveType == MOVETYPE_FLY ) {
return "flying_idle";
}
*/
return idAI::GetIdleAnimName ( );
}
/*
================
rvMonsterStroggHover::DoNamedAttack
================
*/
void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) {
if ( joint != INVALID_JOINT ) {
StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL );
PlayEffect ( va("fx_%s_flash",attackName), joint );
Attack ( attackName, joint, GetEnemy() );
}
}
/*
================
rvMonsterStroggHover::FilterTactical
================
*/
int rvMonsterStroggHover::FilterTactical ( int availableTactical ) {
availableTactical = idAI::FilterTactical( availableTactical );
if ( circleStrafing || inPursuit ) {
return 0;
}
if ( nextBombFireTime >= gameLocal.GetTime() ) {
availableTactical &= ~(AITACTICAL_RANGED_BITS);
} else if ( enemy.fl.visible ) {
availableTactical &= ~AITACTICAL_MELEE_BIT;
}
return availableTactical;
}
/*
===============================================================================
States
===============================================================================
*/
CLASS_STATES_DECLARATION ( rvMonsterStroggHover )
// STATE ( "Torso_BlasterAttack", rvMonsterStroggHover::State_Torso_BlasterAttack )
// STATE ( "Torso_RocketAttack", rvMonsterStroggHover::State_Torso_RocketAttack )
STATE ( "Torso_MGunAttack", rvMonsterStroggHover::State_Torso_MGunAttack )
STATE ( "Torso_MissileAttack", rvMonsterStroggHover::State_Torso_MissileAttack )
STATE ( "Torso_BombAttack", rvMonsterStroggHover::State_Torso_BombAttack )
// STATE ( "Torso_EvadeLeft", rvMonsterStroggHover::State_Torso_EvadeLeft )
// STATE ( "Torso_EvadeRight", rvMonsterStroggHover::State_Torso_EvadeRight )
STATE ( "Torso_Strafe", rvMonsterStroggHover::State_Torso_Strafe )
STATE ( "Torso_CircleStrafe", rvMonsterStroggHover::State_Torso_CircleStrafe )
STATE ( "State_CircleStrafe", rvMonsterStroggHover::State_CircleStrafe )
STATE ( "State_DeathSpiral", rvMonsterStroggHover::State_DeathSpiral )
STATE ( "State_Pursue", rvMonsterStroggHover::State_Pursue )
END_CLASS_STATES
/*
================
rvMonsterStroggHover::State_Torso_MGunAttack
================
*/
stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_LOOP,
STAGE_WAITLOOP,
};
switch ( parms.stage ) {
case STAGE_INIT:
shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots;
return SRESULT_STAGE ( STAGE_LOOP );
case STAGE_LOOP:
DoNamedAttack( "mgun", jointMGun );
nextMGunFireTime = gameLocal.GetTime() + mGunFireRate;
return SRESULT_STAGE ( STAGE_WAITLOOP );
case STAGE_WAITLOOP:
if ( nextMGunFireTime <= gameLocal.GetTime() ) {
if ( --shots <= 0 ) {
ForceTacticalUpdate();
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterStroggHover::State_Torso_MissileAttack
================
*/
stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_LOOP,
STAGE_WAITLOOP,
};
switch ( parms.stage ) {
case STAGE_INIT:
shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots;
return SRESULT_STAGE ( STAGE_LOOP );
case STAGE_LOOP:
DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] );
nextMissileFireTime = gameLocal.GetTime() + missileFireRate;
return SRESULT_STAGE ( STAGE_WAITLOOP );
case STAGE_WAITLOOP:
if ( nextMissileFireTime <= gameLocal.GetTime() ) {
if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) {
//out of shots or enemy too close to safely keep launching rockets at
ForceTacticalUpdate();
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterStroggHover::State_Torso_BombAttack
================
*/
stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_LOOP,
STAGE_WAITLOOP,
};
idVec3 vel = GetPhysics()->GetLinearVelocity();
if ( vel.z < 150.0f ) {
vel.z += 20.0f;
physicsObj.UseVelocityMove( true );
GetPhysics()->SetLinearVelocity( vel );
}
switch ( parms.stage ) {
case STAGE_INIT:
move.fly_offset = 800;//go up!
shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots;
if ( GetEnemy() ) {
//if I'm not above him, give me a quick boost first
float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z;
if ( zDiff < 150.0f ) {
idVec3 vel = GetPhysics()->GetLinearVelocity();
vel.z += 200.0f;
if ( zDiff < 0.0f ) {
//even more if I'm below him!
vel.z -= zDiff*2.0f;
}
physicsObj.UseVelocityMove( true );
GetPhysics()->SetLinearVelocity( vel );
}
}
if ( move.moveCommand == MOVE_TO_ATTACK ) {
StopMove( MOVE_STATUS_DONE );
}
if ( combat.tacticalCurrent == AITACTICAL_RANGED ) {
ForceTacticalUpdate();
}
if ( move.moveCommand == MOVE_NONE
&& !inPursuit && !circleStrafing ) {
MoveToEnemy();
}
return SRESULT_STAGE ( STAGE_LOOP );
case STAGE_LOOP:
DoNamedAttack( "bomb", jointBomb );
nextBombFireTime = gameLocal.GetTime() + bombFireRate;
return SRESULT_STAGE ( STAGE_WAITLOOP );
case STAGE_WAITLOOP:
if ( nextBombFireTime <= gameLocal.GetTime() ) {
if ( --shots <= 0 ) {
move.fly_offset = spawnArgs.GetFloat("fly_offset","250");
ForceTacticalUpdate();
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterStroggHover::State_Torso_BlasterAttack
================
*/
/*
stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAITSTART,
STAGE_LOOP,
STAGE_WAITLOOP,
STAGE_WAITEND
};
switch ( parms.stage ) {
case STAGE_INIT:
shots = gameLocal.random.RandomInt ( 8 ) + 4;
PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAITSTART );
case STAGE_WAITSTART:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_LOOP:
PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 );
return SRESULT_STAGE ( STAGE_WAITLOOP );
case STAGE_WAITLOOP:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
if ( --shots <= 0 ) {
PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 );
return SRESULT_STAGE ( STAGE_WAITEND );
}
return SRESULT_STAGE ( STAGE_LOOP );
}
return SRESULT_WAIT;
case STAGE_WAITEND:
if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
ForceTacticalUpdate();
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
*/
/*
================
rvMonsterStroggHover::State_Torso_RocketAttack
================
*/
/*
stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAITSTART,
STAGE_LOOP,
STAGE_WAITLOOP,
STAGE_WAITEND
};
switch ( parms.stage ) {
case STAGE_INIT:
//DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAITSTART );
case STAGE_WAITSTART:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
ForceTacticalUpdate();
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
*/
void rvMonsterStroggHover::Evade ( bool left ) {
idVec3 vel = GetPhysics()->GetLinearVelocity();
vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed);
physicsObj.UseVelocityMove( true );
GetPhysics()->SetLinearVelocity( vel );
}
stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) {
//fixme: trace first for visibility & obstruction?
if ( gameLocal.random.RandomFloat() > 0.5f ) {
Evade( false );
} else {
Evade( true );
}
return SRESULT_DONE;
}
stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) {
circleStrafing = true;
return SRESULT_DONE;
}
bool rvMonsterStroggHover::MarkerPosValid ( void )
{
//debouncer ftw
if( markerCheckTime > gameLocal.GetTime() ) {
return true;
}
markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500);
trace_t trace;
gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL );
if ( !(trace.c.contents&GetPhysics()->GetClipMask()) )
{//not in solid
gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() );
idActor* enemyAct = NULL;
rvVehicle* enemyVeh = NULL;
if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) {
enemyVeh = static_cast<rvVehicle*>(GetEnemy());
} else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) {
enemyAct = static_cast<idActor*>(GetEnemy());
}
idEntity* hitEnt = gameLocal.entities[trace.c.entityNum];
idActor* hitAct = NULL;
if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) {
hitAct = static_cast<idActor*>(hitEnt);
}
if ( trace.fraction >= 1.0f
|| (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum])
|| (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) )
{//have a clear LOS to enemy
if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) )
{//valid AAS there...
return true;
}
}
}
return false;
}
void rvMonsterStroggHover::TryStartPursuit ( void )
{
if ( GetEnemy() )
{
inPursuit = false;
if ( !marker.GetEntity() ) {
//wtf?!
assert(0);
return;
}
attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f );
if ( attackPosOffset.Length() < 150.0f )
{
attackPosOffset.Normalize();
attackPosOffset *= 150.0f;
}
attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset;
marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset );
if ( MarkerPosValid() )
{
if ( MoveToEntity( marker ) )
{
inPursuit = true;
holdPosTime = 0;
SetState( "State_Pursue" );
}
}
}
}
void rvMonsterStroggHover::Pursue ( void )
{
if ( marker.GetEntity() && GetEnemy() )
{
marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset );
if ( DebugFilter(ai_debugMove) ) {
gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() );
}
if ( MarkerPosValid() )
{
bool breakOff = false;
if ( move.fl.done )
{//even once get there, hold that position for a while...
if ( holdPosTime && holdPosTime > gameLocal.GetTime() )
{//held this position long enough
breakOff = true;
}
else
{
if ( !holdPosTime )
{//just got there, hold position for a bit
holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime();
}
if ( !MoveToEntity( marker ) )
{
breakOff = true;
}
}
}
if ( !breakOff )
{
return;
}
}
}
if ( !move.fl.done )
{
StopMove( MOVE_STATUS_DONE );
}
inPursuit = false;
}
stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) {
if ( inPursuit ) {
// Perform actions along the way
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
}
SetState( "State_Combat" );
return SRESULT_DONE;
}
void rvMonsterStroggHover::CircleStrafe ( void )
{
if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov )
{
//FIXME: also stop if I bump into something
circleStrafing = false;
strafeTime = 0;
SetState( "State_Combat" );
return;
}
if ( !strafeTime )
{
strafeTime = gameLocal.GetTime() + 8000;
//FIXME: try to see which side it clear?
strafeRight = (gameLocal.random.RandomFloat()>0.5f);
}
idVec3 vel = GetPhysics()->GetLinearVelocity();
idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed);
strafeVel.z = 0.0f;
vel += strafeVel;
vel.Normalize();
vel *= circleStrafeSpeed;
physicsObj.UseVelocityMove( true );
GetPhysics()->SetLinearVelocity( vel );
TurnToward( GetEnemy()->GetPhysics()->GetOrigin() );
}
stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) {
if ( circleStrafing ) {
// Perform actions along the way
if ( UpdateAction ( ) ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
}
SetState( "State_Combat" );
return SRESULT_DONE;
}
stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_SPIRAL
};
switch ( parms.stage ) {
case STAGE_INIT:
{
disablePain = true;
// Make sure all animation and attack states stop
StopAnimState ( ANIMCHANNEL_TORSO );
StopAnimState ( ANIMCHANNEL_LEGS );
// Start the crash & burn effects
for ( int i = 0; i < numHoverJoints; i++ ) {
if ( jointHover[i] != INVALID_JOINT ) {
PlayEffect ( "fx_hurt", jointHover[i], false );
effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true );
}
}
deathPitch = viewAxis.ToAngles()[0];
deathRoll = viewAxis.ToAngles()[2];
deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f;
deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f;
deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f;
deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f;
deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f;
strafeRight = (gameLocal.random.RandomFloat()>0.5f);
StopSound( SND_CHANNEL_HEART, false );
StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL );
}
return SRESULT_STAGE ( STAGE_SPIRAL );
case STAGE_SPIRAL:
{
move.current_yaw += (strafeRight?-deathYawRate:deathYawRate);
deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate );
deathRoll += (strafeRight?deathRollRate:-deathRollRate);
viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3();
idVec3 vel = GetPhysics()->GetLinearVelocity();
idVec3 strafeVel = viewAxis[0] * deathSpeed;
strafeVel.z = 0;
vel += strafeVel;
vel.Normalize();
vel *= deathSpeed;
vel += GetPhysics()->GetGravity()/deathGrav;
physicsObj.UseVelocityMove( true );
physicsObj.SetLinearVelocity( vel );
idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle );
if( emitter ) {
refSound.parms.frequencyShift += 0.025f;
emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms );
}
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}