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

603 lines
16 KiB
C++

#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "../vehicle/Vehicle.h"
//
class rvMonsterConvoyGround : public idAI {
public:
CLASS_PROTOTYPE( rvMonsterConvoyGround );
rvMonsterConvoyGround ( void );
void Spawn ( void );
void InitSpawnArgsVariables ( void );
void Save ( idSaveGame *savefile ) const;
void Restore ( idRestoreGame *savefile );
virtual void Postthink ( void );
virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
virtual bool CheckPainActions ( void );
virtual bool CanTurn ( void ) const;
virtual bool CanMove ( void ) const;
virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
virtual void AdjustHealthByDamage ( int inDamage );
virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData );
protected:
int shots;
int minShots;
int maxShots;
bool isOpen;
bool vehicleCollision;
float moveCurrentAnimRate;
float moveAnimRateMin;
float moveAnimRateRange;
float moveAccelRate;
bool onGround;
idVec3 oldOrigin;
idVec3 lastPainDir;
rvAIAction actionBlasterAttack;
virtual bool CheckActions ( void );
virtual int FilterTactical ( int availableTactical );
virtual const char* GetIdleAnimName ( void );
virtual void OnDeath ( void );
private:
// General states
stateResult_t State_Fall ( const stateParms_t& parms );
// Legs States
stateResult_t State_Legs_Move ( const stateParms_t& parms );
// Torso States
stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms );
stateResult_t State_Torso_Open ( const stateParms_t& parms );
stateResult_t State_Torso_Close ( const stateParms_t& parms );
stateResult_t State_Torso_Pain ( const stateParms_t& parms );
CLASS_STATES_PROTOTYPE ( rvMonsterConvoyGround );
};
CLASS_DECLARATION( idAI, rvMonsterConvoyGround )
END_CLASS
/*
================
rvMonsterConvoyGround::rvMonsterConvoyGround
================
*/
rvMonsterConvoyGround::rvMonsterConvoyGround ( ) {
shots = 0;
isOpen = false;
vehicleCollision = false;
moveCurrentAnimRate = 1.0f;
}
void rvMonsterConvoyGround::InitSpawnArgsVariables ( void )
{
minShots = spawnArgs.GetInt ( "minShots" );
maxShots = spawnArgs.GetInt ( "maxShots" );
moveAccelRate = spawnArgs.GetFloat ( "moveAccelRate", ".1" );
moveAnimRateMin = spawnArgs.GetFloat ( "moveMinAnimRate", "1" );
moveAnimRateRange = spawnArgs.GetFloat ( "moveMaxAnimRate", "10" ) - moveAnimRateMin;
}
/*
================
rvMonsterConvoyGround::Spawn
================
*/
void rvMonsterConvoyGround::Spawn ( void ) {
actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK );
InitSpawnArgsVariables();
aifl.disableLook = true;
onGround = true;
}
/*
================
rvMonsterConvoyGround::Prethink
================
*/
void rvMonsterConvoyGround::Postthink ( void ) {
/* FIXME
if ( onGround && !physicsObj.HasGroundContacts ( ) ) {
onGround = false;
InterruptState ( "State_Fall" );
}
*/
idAI::Postthink ( );
}
/*
================
rvMonsterConvoyGround::Save
================
*/
bool rvMonsterConvoyGround::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
lastPainDir = dir;
return idAI::Pain ( inflictor, attacker, damage, dir, location );
}
/*
================
rvMonsterConvoyGround::Save
================
*/
void rvMonsterConvoyGround::Save ( idSaveGame *savefile ) const {
savefile->WriteInt ( shots );
//minShots and maxShots are set in the Restore
savefile->WriteBool ( isOpen );
savefile->WriteBool ( vehicleCollision );
savefile->WriteBool ( onGround );
savefile->WriteFloat ( moveCurrentAnimRate );
savefile->WriteVec3 ( oldOrigin );
savefile->WriteVec3 ( lastPainDir );
actionBlasterAttack.Save ( savefile );
}
/*
================
rvMonsterConvoyGround::Restore
================
*/
void rvMonsterConvoyGround::Restore ( idRestoreGame *savefile ) {
savefile->ReadInt ( shots );
savefile->ReadBool ( isOpen );
savefile->ReadBool ( vehicleCollision );
savefile->ReadBool ( onGround );
savefile->ReadFloat ( moveCurrentAnimRate );
savefile->ReadVec3 ( oldOrigin );
savefile->ReadVec3 ( lastPainDir );
actionBlasterAttack.Restore ( savefile );
InitSpawnArgsVariables();
}
/*
================
rvMonsterConvoyGround::CanMove
================
*/
bool rvMonsterConvoyGround::CanMove ( void ) const {
if ( isOpen ) {
return false;
}
return idAI::CanMove ( );
}
/*
================
rvMonsterConvoyGround::CanTurn
================
*/
bool rvMonsterConvoyGround::CanTurn ( void ) const {
if ( isOpen ) {
return false;
}
return idAI::CanTurn ( );
}
/*
================
rvMonsterConvoyGround::OnDeath
================
*/
void rvMonsterConvoyGround::OnDeath ( void ) {
idVec3 fxOrg;
idVec3 up;
idMat3 fxAxis;
//center it
fxOrg = GetPhysics()->GetCenterMass();
//point it up
up.Set( 0, 0, 1 );
fxAxis = up.ToMat3();
//if we can play it at the joint, do that
jointHandle_t axisJoint = animator.GetJointHandle ( "axis" );
if ( axisJoint != INVALID_JOINT ) {
idMat3 junk;
animator.GetJointLocalTransform( axisJoint, gameLocal.GetTime(), fxOrg, junk );
fxOrg = renderEntity.origin + (fxOrg*renderEntity.axis);
}
gameLocal.PlayEffect ( spawnArgs, "fx_death", fxOrg, fxAxis );
idAI::OnDeath ( );
}
/*
================
rvMonsterConvoyGround::AdjustHealthByDamage
================
*/
void rvMonsterConvoyGround::AdjustHealthByDamage ( int damage ) {
if ( isOpen || vehicleCollision ) {
idAI::AdjustHealthByDamage ( damage );
} else {
PlayEffect ( "fx_shieldHit", animator.GetJointHandle ( "axis" ) );
}
}
void rvMonsterConvoyGround::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location )
{
vehicleCollision = false;
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false );
if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) {
vehicleCollision = true;
}
idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}
/*
================
rvMonsterConvoyGround::Spawn
================
*/
bool rvMonsterConvoyGround::CheckActions ( void ) {
if ( isOpen ) {
if ( move.fl.moving ) {
/*
|| !CheckAction_RangedAttack( &actionBlasterAttack, -1 )
|| enemy.range > actionBlasterAttack.maxRange
|| enemy.range < actionBlasterAttack.minRange
|| (!move.fl.moving && (gameLocal.GetTime()-move.startTime) > 3000 ) ) {
*/
StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 );
PerformAction ( "Torso_Close", 4, true );
return true;
}
if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
return true;
}
} else {
// Open up if we have stopped and have an enemy
if ( !move.fl.moving && physicsObj.HasGroundContacts ( ) && enemy.ent && legsAnim.IsIdle ( ) && CheckTactical ( AITACTICAL_RANGED ) ) {
StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 );
PerformAction ( "Torso_Open", 4, true );
return true;
}
}
return idAI::CheckActions ( );
}
/*
================
rvMonsterConvoyGround::CheckPainActions
================
*/
bool rvMonsterConvoyGround::CheckPainActions ( void ) {
if ( isOpen ) {
return false;
}
return idAI::CheckPainActions ( );
}
/*
================
rvMonsterConvoyGround::GetIdleAnimName
================
*/
const char* rvMonsterConvoyGround::GetIdleAnimName ( void ) {
// Start idle animation
if ( isOpen ) {
return "idle_open";
}
return "idle";
}
/*
================
rvMonsterConvoyGround::FilterTactical
================
*/
int rvMonsterConvoyGround::FilterTactical ( int availableTactical ) {
return idAI::FilterTactical ( availableTactical );
}
/*
=====================
rvMonsterConvoyGround::GetDebugInfo
=====================
*/
void rvMonsterConvoyGround::GetDebugInfo ( debugInfoProc_t proc, void* userData ) {
// Base class first
idAI::GetDebugInfo ( proc, userData );
proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData );
proc ( "rvMonsterConvoyGround", "moveAnimRate", va("%g", moveCurrentAnimRate ), userData );
proc ( "rvMonsterConvoyGround", "isOpen", isOpen ? "true" : "false", userData );
}
/*
===============================================================================
States
===============================================================================
*/
CLASS_STATES_DECLARATION ( rvMonsterConvoyGround )
STATE ( "State_Fall", rvMonsterConvoyGround::State_Fall )
STATE ( "Torso_Open", rvMonsterConvoyGround::State_Torso_Open )
STATE ( "Torso_Close", rvMonsterConvoyGround::State_Torso_Close )
STATE ( "Torso_BlasterAttack", rvMonsterConvoyGround::State_Torso_BlasterAttack )
STATE ( "Torso_Pain", rvMonsterConvoyGround::State_Torso_Pain )
STATE ( "Legs_Move", rvMonsterConvoyGround::State_Legs_Move )
END_CLASS_STATES
/*
================
rvMonsterConvoyGround::State_Fall
================
*/
stateResult_t rvMonsterConvoyGround::State_Fall ( const stateParms_t& parms ) {
enum {
STAGE_INIT, // Initialize fall stage
STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground
STAGE_IMPACT, // Handle drop turret impact, switch to combat state
STAGE_WAITDONE,
STAGE_DONE
};
switch ( parms.stage ) {
case STAGE_INIT:
StopMove ( MOVE_STATUS_DONE );
StopAnimState ( ANIMCHANNEL_LEGS );
StopAnimState ( ANIMCHANNEL_TORSO );
StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL );
PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true );
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayCycle ( ANIMCHANNEL_TORSO, "idle", 0 );
oldOrigin = physicsObj.GetOrigin ( );
return SRESULT_STAGE(STAGE_WAITIMPACT);
case STAGE_WAITIMPACT:
if ( physicsObj.HasGroundContacts ( ) ) {
return SRESULT_STAGE(STAGE_IMPACT);
}
return SRESULT_WAIT;
case STAGE_IMPACT:
StopSound ( SND_CHANNEL_VOICE, false );
StopEffect ( "fx_droptrail" );
PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() );
if ( (physicsObj.GetOrigin ( ) - oldOrigin).LengthSqr() > Square(128.0f) ) {
PlayAnim ( ANIMCHANNEL_TORSO, "land", 0 );
return SRESULT_STAGE ( STAGE_WAITDONE );
}
return SRESULT_STAGE ( STAGE_DONE );
case STAGE_WAITDONE:
if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
return SRESULT_STAGE ( STAGE_DONE );
}
return SRESULT_WAIT;
case STAGE_DONE:
onGround = true;
SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle" );
SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle" );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterConvoyGround::State_Torso_BlasterAttack
================
*/
stateResult_t rvMonsterConvoyGround::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_ATTACK,
STAGE_WAIT
};
switch ( parms.stage ) {
case STAGE_INIT:
shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale;
return SRESULT_STAGE ( STAGE_ATTACK );
case STAGE_ATTACK:
PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames );
shots--;
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) {
return SRESULT_DONE;
}
return SRESULT_STAGE ( STAGE_ATTACK );
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterConvoyGround::State_Torso_Open
================
*/
stateResult_t rvMonsterConvoyGround::State_Torso_Open ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "extend_legs", parms.blendFrames );
isOpen = true;
aifl.disableLook = false;
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterConvoyGround::State_Torso_Close
================
*/
stateResult_t rvMonsterConvoyGround::State_Torso_Close ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_WAIT,
};
switch ( parms.stage ) {
case STAGE_INIT:
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, "retract_legs", parms.blendFrames );
return SRESULT_STAGE ( STAGE_WAIT );
case STAGE_WAIT:
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
isOpen = false;
aifl.disableLook = true;
ForceTacticalUpdate();
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterConvoyGround::State_Torso_Pain
================
*/
stateResult_t rvMonsterConvoyGround::State_Torso_Pain ( const stateParms_t& parms ) {
enum {
STAGE_START,
STAGE_END
};
switch ( parms.stage ) {
case STAGE_START:
// Force the orientation to the direction we got hit from so the animation looks correct
OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true );
TurnToward ( physicsObj.GetOrigin() - lastPainDir * 128.0f );
move.current_yaw = move.ideal_yaw;
// Just in case the pain anim wasnt set before we got here.
if ( !painAnim.Length ( ) ) {
painAnim = "pain";
}
DisableAnimState ( ANIMCHANNEL_LEGS );
PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames );
return SRESULT_STAGE ( STAGE_END );
case STAGE_END:
if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
/*
================
rvMonsterConvoyGround::State_Legs_Move
================
*/
stateResult_t rvMonsterConvoyGround::State_Legs_Move ( const stateParms_t& parms ) {
enum {
STAGE_INIT,
STAGE_MOVE
};
switch ( parms.stage ) {
case STAGE_INIT:
move.fl.allowAnimMove = true;
move.fl.allowPrevAnimMove = false;
move.fl.running = true;
move.currentDirection = MOVEDIR_FORWARD;
// TODO: Looks like current anim rate never gets reset, so they do not correctly accelerate from a stop
// unfortunately, adding this change (with a decent acceleration factor) caused them to do lots of
// not-so-good looking short moves.
// moveCurrentAnimRate = 0;
oldOrigin = physicsObj.GetOrigin ( );
PlayCycle ( ANIMCHANNEL_LEGS, "run", 0 );
StartSound( "snd_move", SND_CHANNEL_BODY3, 0, false, NULL );
return SRESULT_STAGE ( STAGE_MOVE );
case STAGE_MOVE:
// If not moving forward just go back to idle
if ( !move.fl.moving || !CanMove() ) {
StopSound( SND_CHANNEL_BODY3, 0 );
PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
return SRESULT_DONE;
}
// If on the ground update the animation rate based on the normal of the ground plane
if ( !ai_debugHelpers.GetBool ( ) && physicsObj.HasGroundContacts ( ) ) {
float rate;
idVec3 dir;
dir = (physicsObj.GetOrigin ( ) - oldOrigin);
if ( DistanceTo ( move.moveDest ) < move.walkRange ) {
rate = moveAnimRateMin;
} else if ( dir.Normalize ( ) > 0.0f ) {
rate = idMath::ClampFloat ( -0.7f, 0.7f, physicsObj.GetGravityNormal ( ) * dir ) / 0.7f;
rate = moveAnimRateMin + moveAnimRateRange * (1.0f + rate) / 2.0f;
} else {
rate = moveAnimRateMin + moveAnimRateRange * 0.5f;
}
moveCurrentAnimRate += ((rate - moveCurrentAnimRate) * moveAccelRate);
animator.CurrentAnim ( ANIMCHANNEL_LEGS )->SetPlaybackRate ( gameLocal.time, moveCurrentAnimRate );
}
oldOrigin = physicsObj.GetOrigin ( );
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}