Files
DoomRTX/neo/doom3/game/ai/AI_monster_base.cpp
T
2026-05-23 20:50:12 -07:00

1175 lines
39 KiB
C++

// AI_monster_base.cpp
// Native state-machine replacement for ai_monster_base.script.
#include "precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
rvmMonsterBase::rvmMonsterBase() {
run_distance = 0.0f;
walk_turn = 360.0f;
run = false;
ambush = false;
ignoreEnemies = false;
stay_on_attackpath = false;
idle_sight_fov = true;
customBlendOut = 0.0f;
current_path = NULL;
next_path = NULL;
resurrect = false;
ignore_lostcombat = false;
blocked = false;
ignore_sight = false;
}
void rvmMonsterBase::Init( idAI* aiOwner ) {
rvmAIObject::Init( aiOwner );
ignoreEnemies = false;
run_distance = 0.0f;
walk_turn = 360.0f;
ambush = GetIntKey( "ambush" ) != 0;
ignore_lostcombat = GetIntKey( "ignore_lostcombat" ) != 0;
stay_on_attackpath = GetIntKey( "stay_on_attackpath" ) != 0;
idle_sight_fov = true;
run = false;
blocked = false;
ignore_sight = false;
}
stateResult_t rvmMonsterBase::RunStateByName( const char* stateName, stateParms_t* parms ) {
AI_DISPATCH_STATE( "Torso_CustomCycle", Torso_CustomCycle );
AI_DISPATCH_STATE( "Torso_CustomAnim", Torso_CustomAnim );
AI_DISPATCH_STATE( "Torso_Sight", Torso_Sight );
AI_DISPATCH_STATE( "Torso_Death", Torso_Death );
AI_DISPATCH_STATE( "Torso_Idle", Torso_Idle );
AI_DISPATCH_STATE( "Legs_Idle", Legs_Idle );
AI_DISPATCH_STATE( "Legs_Death", Legs_Death );
AI_DISPATCH_STATE( "state_Begin", state_Begin );
AI_DISPATCH_STATE( "state_Spawner", state_Spawner );
AI_DISPATCH_STATE( "state_Resurrect", state_Resurrect );
AI_DISPATCH_STATE( "state_Killed", state_Killed );
AI_DISPATCH_STATE( "state_Dead", state_Dead );
AI_DISPATCH_STATE( "state_Combat", state_Combat );
AI_DISPATCH_STATE( "state_LostCombat", state_LostCombat );
AI_DISPATCH_STATE( "state_FollowAlternatePath", state_FollowAlternatePath );
AI_DISPATCH_STATE( "monster_begin", monster_begin );
AI_DISPATCH_STATE( "wait_for_enemy", wait_for_enemy );
AI_DISPATCH_STATE( "sight_enemy", sight_enemy );
AI_DISPATCH_STATE( "walk_on_trigger", walk_on_trigger );
AI_DISPATCH_STATE( "wake_on_trigger", wake_on_trigger );
AI_DISPATCH_STATE( "wake_on_enemy", wake_on_enemy );
AI_DISPATCH_STATE( "wake_on_attackcone", wake_on_attackcone );
AI_DISPATCH_STATE( "idle_followPathEntities", idle_followPathEntities );
AI_DISPATCH_STATE( "path_corner", path_corner );
AI_DISPATCH_STATE( "path_anim", path_anim );
AI_DISPATCH_STATE( "path_cycleanim", path_cycleanim );
AI_DISPATCH_STATE( "path_turn", path_turn );
AI_DISPATCH_STATE( "path_wait", path_wait );
AI_DISPATCH_STATE( "path_waitfortrigger", path_waitfortrigger );
AI_DISPATCH_STATE( "path_hide", path_hide );
AI_DISPATCH_STATE( "path_show", path_show );
AI_DISPATCH_STATE( "path_attack", path_attack );
AI_DISPATCH_STATE( "combat_wander", combat_wander );
AI_DISPATCH_STATE( "combat_turret_node", combat_turret_node );
AI_DISPATCH_STATE( "combat_attack_cone", combat_attack_cone );
AI_DISPATCH_STATE( "combat_ainode", combat_ainode );
return rvmAIObject::RunStateByName( stateName, parms );
}
stateResult_t rvmMonsterBase::Torso_CustomCycle( stateParms_t* parms ) {
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::Torso_CustomAnim( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT };
switch ( parms->stage ) {
case STAGE_START:
parms->stage = STAGE_WAIT;
return SRESULT_WAIT;
case STAGE_WAIT:
if ( AnimDone( ANIMCHANNEL_TORSO, idMath::Ftoi( customBlendOut ) ) ) {
FinishAction( "customAnim" );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", idMath::Ftoi( customBlendOut ) );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::Torso_Sight( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT };
switch ( parms->stage ) {
case STAGE_START: {
if ( !GetIntKey( "walk_on_sight" ) ) {
OverrideAnim( ANIMCHANNEL_LEGS );
}
const char* animname = GetKey( "on_activate" );
if ( animname && animname[0] && HasAnim( ANIMCHANNEL_TORSO, animname ) ) {
PlayAnim( ANIMCHANNEL_TORSO, animname );
parms->stage = STAGE_WAIT;
return SRESULT_WAIT;
}
FinishAction( "sight" );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", GetIntKey( "on_activate_blend", "4" ) );
return SRESULT_DONE;
}
case STAGE_WAIT:
if ( AnimDone( ANIMCHANNEL_TORSO, GetIntKey( "on_activate_blend", "4" ) ) ) {
FinishAction( "sight" );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", GetIntKey( "on_activate_blend", "4" ) );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::Torso_Death( stateParms_t* parms ) {
FinishAction( "dead" );
return SRESULT_DONE;
}
stateResult_t rvmMonsterBase::Torso_Idle( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
IdleAnim( ANIMCHANNEL_TORSO, "idle" );
parms->stage = 1;
}
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::Legs_Idle( stateParms_t* parms ) {
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::Legs_Death( stateParms_t* parms ) {
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::state_Begin( stateParms_t* parms ) {
return SRESULT_DONE;
}
stateResult_t rvmMonsterBase::state_Spawner( stateParms_t* parms ) {
enum { STAGE_START, STAGE_LOOP };
switch ( parms->stage ) {
case STAGE_START:
HideOwner();
ClearActivated();
parms->stage = STAGE_LOOP;
return SRESULT_WAIT;
case STAGE_LOOP:
if ( Activated() && CanBecomeSolid() ) {
ClearActivated();
// Spawn-copy behavior is project-specific; keep this state non-blocking.
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::monster_begin( stateParms_t* parms ) {
enum { STAGE_START, STAGE_HIDDEN_WAIT_TRIGGER, STAGE_HIDDEN_WAIT_SOLID, STAGE_DELAY, STAGE_WAKE, STAGE_SIGHT };
switch ( parms->stage ) {
case STAGE_START:
run = false;
ignore_sight = GetIntKey( "no_sight" ) != 0;
if ( !GetIntKey( "ignore_flashlight" ) ) {
WakeOnFlashlight( true );
}
if ( GetIntKey( "hide" ) ) {
HideOwner();
ClearActivated();
parms->stage = STAGE_HIDDEN_WAIT_TRIGGER;
return SRESULT_WAIT;
}
scratchEndTime = DelayTime( GetFloatKey( "wait" ) );
parms->stage = STAGE_DELAY;
return SRESULT_WAIT;
case STAGE_HIDDEN_WAIT_TRIGGER:
if ( !Activated() && !Pain() ) {
return SRESULT_WAIT;
}
ClearActivated();
parms->stage = STAGE_HIDDEN_WAIT_SOLID;
return SRESULT_WAIT;
case STAGE_HIDDEN_WAIT_SOLID:
if ( !CanBecomeSolid() ) {
return SRESULT_WAIT;
}
ShowOwner();
scratchEndTime = DelayTime( GetFloatKey( "wait" ) );
parms->stage = STAGE_DELAY;
return SRESULT_WAIT;
case STAGE_DELAY:
if ( Time() < scratchEndTime ) {
return SRESULT_WAIT;
}
if ( idEntity* enemyEnt = GetEntityKey( "enemy" ) ) {
SetEnemy( enemyEnt );
}
parms->stage = STAGE_WAKE;
return SRESULT_WAIT;
case STAGE_WAKE:
if ( GetEnemy() || Pain() || Activated() || checkForEnemy( idle_sight_fov ) ) {
trigger_wakeup_targets();
ignore_sight = false;
WakeOnFlashlight( false );
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
parms->stage = STAGE_SIGHT;
}
return SRESULT_WAIT;
case STAGE_SIGHT:
if ( !ActionDone( "sight" ) ) {
return SRESULT_WAIT;
}
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::state_Resurrect( stateParms_t* parms ) {
enum { STAGE_START, STAGE_SOLID };
switch ( parms->stage ) {
case STAGE_START:
if ( owner ) {
owner->AI_DEAD = false;
}
StopMove();
HideOwner();
if ( owner ) {
owner->StopRagdoll();
// restorePosition() was a DoomScript helper; this idAI branch has no
// public RestorePosition() native. StopRagdoll() is the available
// native restore hook here.
}
parms->stage = STAGE_SOLID;
return SRESULT_WAIT;
case STAGE_SOLID:
if ( !CanBecomeSolid() ) {
return SRESULT_WAIT;
}
if ( owner ) {
owner->SetHealth( GetFloatKey( "health", "1" ) );
owner->TurnTo( GetFloatKey( "angle" ) );
owner->ClearBurn();
owner->AllowDamage();
}
SetState( "state_Begin" );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::wait_for_enemy( stateParms_t* parms ) {
enum { STAGE_START, STAGE_LOOP };
switch ( parms->stage ) {
case STAGE_START:
if ( owner ) {
owner->AI_PAIN = false;
}
StopMove();
parms->stage = STAGE_LOOP;
return SRESULT_WAIT;
case STAGE_LOOP:
if ( Pain() || GetEnemy() || checkForEnemy( idle_sight_fov ) ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::state_Killed( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT_DEAD_ACTION };
switch ( parms->stage ) {
case STAGE_START:
StopMove();
waitActionName = "dead";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 0 );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );
parms->stage = STAGE_WAIT_DEAD_ACTION;
return SRESULT_WAIT;
case STAGE_WAIT_DEAD_ACTION:
if ( !ActionDone( "dead" ) ) {
return SRESULT_WAIT;
}
SetState( "state_Dead" );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::state_Dead( stateParms_t* parms ) {
enum { STAGE_START, STAGE_BURN_DELAY, STAGE_REMOVE_DELAY, STAGE_RESURRECT_HOLD };
switch ( parms->stage ) {
case STAGE_START:
scratchEndTime = DelayTime( GetFloatKey( "burnaway" ) );
if ( GetFloatKey( "burnaway" ) > 0.0f && owner ) {
owner->PreBurn();
parms->stage = STAGE_BURN_DELAY;
} else {
scratchEndTime = DelayTime( 3.0f );
parms->stage = STAGE_REMOVE_DELAY;
}
return SRESULT_WAIT;
case STAGE_BURN_DELAY:
if ( Time() < scratchEndTime ) {
return SRESULT_WAIT;
}
if ( owner ) {
owner->Burn();
StartSoundKey( "snd_burn", SND_CHANNEL_BODY );
}
scratchEndTime = DelayTime( 3.0f );
parms->stage = STAGE_REMOVE_DELAY;
return SRESULT_WAIT;
case STAGE_REMOVE_DELAY:
if ( Time() < scratchEndTime ) {
return SRESULT_WAIT;
}
if ( resurrect ) {
HideOwner();
if ( owner ) {
owner->StopRagdoll();
// restorePosition() was a DoomScript helper; this idAI branch has no
// public RestorePosition() native. StopRagdoll() is the available
// native restore hook here.
}
parms->stage = STAGE_RESURRECT_HOLD;
return SRESULT_WAIT;
}
RemoveOwner();
return SRESULT_DONE;
case STAGE_RESURRECT_HOLD:
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::sight_enemy( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT };
switch ( parms->stage ) {
case STAGE_START:
FaceEnemy();
if ( GetKey( "on_activate" )[0] ) {
SetNeverDormant( true );
if ( GetIntKey( "walk_on_sight" ) ) {
MoveToEnemy();
}
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
parms->stage = STAGE_WAIT;
return SRESULT_WAIT;
}
return SRESULT_DONE;
case STAGE_WAIT:
if ( ActionDone( "sight" ) ) {
SetNeverDormant( GetFloatKey( "neverdormant" ) != 0.0f );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
void rvmMonsterBase::enemy_dead() {
if ( owner ) {
owner->AI_ENEMY_DEAD = false;
}
checkForEnemy( false );
if ( !GetEnemy() ) {
SetState( "state_Idle" );
} else {
SetState( "state_Combat" );
}
}
stateResult_t rvmMonsterBase::walk_on_trigger( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT_ACTIVATED, STAGE_DONE };
switch ( parms->stage ) {
case STAGE_START:
AllowMovement( false );
playCustomCycle( GetKey( "anim" ), 4 );
parms->stage = STAGE_WAIT_ACTIVATED;
return SRESULT_WAIT;
case STAGE_WAIT_ACTIVATED:
if ( !Activated() && !Pain() && !checkForEnemy( true ) ) {
return SRESULT_WAIT;
}
ClearActivated();
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::wake_on_trigger( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT_ACTIVATED, STAGE_DONE };
switch ( parms->stage ) {
case STAGE_START:
AllowMovement( false );
playCustomCycle( GetKey( "anim" ), 4 );
parms->stage = STAGE_WAIT_ACTIVATED;
return SRESULT_WAIT;
case STAGE_WAIT_ACTIVATED:
if ( !Activated() && !Pain() ) {
return SRESULT_WAIT;
}
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::wake_on_enemy( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT_ENEMY, STAGE_DONE };
switch ( parms->stage ) {
case STAGE_START:
AllowMovement( false );
playCustomCycle( GetKey( "anim" ), 4 );
parms->stage = STAGE_WAIT_ENEMY;
return SRESULT_WAIT;
case STAGE_WAIT_ENEMY:
if ( !Pain() && !Activated() && !checkForEnemy( true ) ) {
return SRESULT_WAIT;
}
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::wake_on_attackcone( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WAIT_CONE, STAGE_DONE };
switch ( parms->stage ) {
case STAGE_START:
AllowMovement( false );
playCustomCycle( GetKey( "anim" ), 4 );
parms->stage = STAGE_WAIT_CONE;
return SRESULT_WAIT;
case STAGE_WAIT_CONE: {
idEntity* enemyEnt = owner ? owner->FindEnemyInCombatNodes() : NULL;
if ( enemyEnt ) {
SetEnemy( enemyEnt );
}
if ( !Activated() && !Pain() && !GetEnemy() ) {
return SRESULT_WAIT;
}
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
}
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::idle_followPathEntities( stateParms_t* parms ) {
// Path-entity execution is kept as a non-blocking state. The current node is advanced
// one frame at a time; unsupported nodes are skipped instead of suspending the VM.
enum { STAGE_START, STAGE_NODE, STAGE_NODE_ACTION };
switch ( parms->stage ) {
case STAGE_START:
if ( !current_path.GetEntity() ) {
current_path = owner ? owner->RandomPath() : NULL;
}
parms->stage = STAGE_NODE;
return SRESULT_WAIT;
case STAGE_NODE:
if ( !current_path.GetEntity() ) {
return SRESULT_DONE;
}
next_path = idPathCorner::RandomPath( current_path.GetEntity(), NULL );
// Resolve common node class names into action states.
scratchString = current_path.GetEntity()->GetEntityDefName();
if ( !scratchString.Icmp( "path_corner" ) ) {
StartAction( "path_corner" );
} else if ( !scratchString.Icmp( "path_anim" ) ) {
StartAction( "path_anim" );
} else if ( !scratchString.Icmp( "path_cycleanim" ) ) {
StartAction( "path_cycleanim" );
} else if ( !scratchString.Icmp( "path_turn" ) ) {
StartAction( "path_turn" );
} else if ( !scratchString.Icmp( "path_wait" ) ) {
StartAction( "path_wait" );
} else if ( !scratchString.Icmp( "path_waitfortrigger" ) ) {
StartAction( "path_waitfortrigger" );
} else if ( !scratchString.Icmp( "path_hide" ) ) {
StartAction( "path_hide" );
} else if ( !scratchString.Icmp( "path_show" ) ) {
StartAction( "path_show" );
} else if ( !scratchString.Icmp( "path_attack" ) ) {
StartAction( "path_attack" );
} else {
current_path = next_path;
return SRESULT_WAIT;
}
parms->stage = STAGE_NODE_ACTION;
return SRESULT_WAIT;
case STAGE_NODE_ACTION:
if ( InAction() ) {
return SRESULT_WAIT;
}
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
current_path = next_path;
parms->stage = STAGE_NODE;
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::path_corner( stateParms_t* parms ) {
enum { STAGE_START, STAGE_MOVE };
idEntity* node = current_path.GetEntity();
if ( !node ) {
return SRESULT_DONE;
}
switch ( parms->stage ) {
case STAGE_START:
run = node->spawnArgs.GetBool( "run", run ? "1" : "0" );
scratchString = node->spawnArgs.GetString( "anim", "" );
if ( scratchString.Length() ) {
playCustomCycle( scratchString.c_str(), 4 );
}
MoveToEntity( node );
parms->stage = STAGE_MOVE;
return SRESULT_WAIT;
case STAGE_MOVE:
if ( InfluenceActive() ) {
StopMove();
return SRESULT_WAIT;
}
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
if ( MoveDone() || DestinationUnreachable() ) {
if ( scratchString.Length() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::path_anim( stateParms_t* parms ) {
enum { STAGE_START, STAGE_TURN, STAGE_ANIM };
idEntity* node = current_path.GetEntity();
if ( !node ) {
return SRESULT_DONE;
}
switch ( parms->stage ) {
case STAGE_START:
scratchString = node->spawnArgs.GetString( "anim", "" );
customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" );
if ( node->spawnArgs.GetString( "angle", "" )[0] ) {
TurnTo( node->spawnArgs.GetFloat( "angle" ) );
parms->stage = STAGE_TURN;
return SRESULT_WAIT;
}
playCustomAnim( scratchString.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ), customBlendOut );
parms->stage = STAGE_ANIM;
return SRESULT_WAIT;
case STAGE_TURN:
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
if ( !FacingIdeal() ) {
return SRESULT_WAIT;
}
playCustomAnim( scratchString.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ), customBlendOut );
parms->stage = STAGE_ANIM;
return SRESULT_WAIT;
case STAGE_ANIM:
if ( checkForEnemy( true ) || AnimDone( ANIMCHANNEL_TORSO, idMath::Ftoi( customBlendOut ) ) ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", idMath::Ftoi( customBlendOut ) );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::path_cycleanim( stateParms_t* parms ) {
enum { STAGE_START, STAGE_TURN, STAGE_HOLD };
idEntity* node = current_path.GetEntity();
if ( !node ) {
return SRESULT_DONE;
}
switch ( parms->stage ) {
case STAGE_START:
scratchString = node->spawnArgs.GetString( "anim", "" );
customBlendOut = node->spawnArgs.GetInt( "blend_out", "0" );
TurnTo( node->GetPhysics()->GetAxis()[0].ToYaw() );
parms->stage = STAGE_TURN;
return SRESULT_WAIT;
case STAGE_TURN:
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
if ( !FacingIdeal() ) {
return SRESULT_WAIT;
}
playCustomCycle( scratchString.c_str(), node->spawnArgs.GetInt( "blend_in", "0" ) );
scratchEndTime = node->spawnArgs.GetFloat( "wait", "0" ) > 0.0f ? DelayTime( node->spawnArgs.GetFloat( "wait" ) ) : 0.0f;
ClearActivated();
parms->stage = STAGE_HOLD;
return SRESULT_WAIT;
case STAGE_HOLD:
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
if ( scratchEndTime > 0.0f ) {
if ( Time() < scratchEndTime ) {
return SRESULT_WAIT;
}
} else if ( !Activated() ) {
return SRESULT_WAIT;
}
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", idMath::Ftoi( customBlendOut ) );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::path_turn( stateParms_t* parms ) {
idEntity* node = current_path.GetEntity();
if ( !node ) {
return SRESULT_DONE;
}
if ( parms->stage == 0 ) {
TurnTo( node->GetPhysics()->GetAxis()[0].ToYaw() );
parms->stage = 1;
}
if ( checkForEnemy( true ) || FacingIdeal() ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::path_wait( stateParms_t* parms ) {
idEntity* node = current_path.GetEntity();
if ( !node ) {
return SRESULT_DONE;
}
if ( parms->stage == 0 ) {
scratchEndTime = DelayTime( node->spawnArgs.GetFloat( "wait", "0" ) );
parms->stage = 1;
}
if ( checkForEnemy( true ) || Time() >= scratchEndTime ) {
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::path_waitfortrigger( stateParms_t* parms ) {
if ( parms->stage == 0 ) {
ClearActivated();
parms->stage = 1;
}
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
if ( Activated() ) {
ClearActivated();
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
stateResult_t rvmMonsterBase::path_hide( stateParms_t* parms ) {
HideOwner();
return SRESULT_DONE;
}
stateResult_t rvmMonsterBase::path_show( stateParms_t* parms ) {
if ( !CanBecomeSolid() ) {
return SRESULT_WAIT;
}
ShowOwner();
return SRESULT_DONE;
}
stateResult_t rvmMonsterBase::path_attack( stateParms_t* parms ) {
enum { STAGE_START, STAGE_COMBAT };
idEntity* node = current_path.GetEntity();
switch ( parms->stage ) {
case STAGE_START:
if ( !node ) {
return SRESULT_DONE;
}
scratchEntity = node->spawnArgs.GetString( "enemy", "" )[0] ? gameLocal.FindEntity( node->spawnArgs.GetString( "enemy" ) ) : NULL;
if ( !scratchEntity.GetEntity() ) {
return SRESULT_DONE;
}
SetEnemy( scratchEntity.GetEntity() );
LocateEnemy();
ClearActivated();
parms->stage = STAGE_COMBAT;
return SRESULT_WAIT;
case STAGE_COMBAT:
if ( Activated() || !scratchEntity.GetEntity() || EnemyDead() ) {
StopMove();
return SRESULT_DONE;
}
if ( combat_chase() ) {
return SRESULT_WAIT;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::state_Combat( stateParms_t* parms ) {
FaceEnemy();
if ( EnemyInFOV() ) {
LookAtEnemy( 1.0f );
}
if ( InfluenceActive() ) {
return SRESULT_WAIT;
}
if ( EnemyDead() ) {
enemy_dead();
return SRESULT_DONE;
}
const int attack_flags = check_attacks();
if ( attack_flags != 0 && do_attack( attack_flags ) ) {
return SRESULT_WAIT;
}
if ( !combat_chase() ) {
LocateEnemy();
if ( !combat_chase() ) {
combat_lost();
}
}
return SRESULT_WAIT;
}
bool rvmMonsterBase::check_blocked() {
if ( Blocked() ) {
Wander();
return false;
}
if ( MoveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT && owner ) {
owner->KickObstacles( owner->GetObstacle(), GetFloatKey( "kick_force", "60" ) );
}
return false;
}
bool rvmMonsterBase::combat_chase() {
bool do_run = ( !EnemyVisible() || EnemyRange() > run_distance );
MoveToEnemy();
if ( MoveDone() || DestinationUnreachable() ) {
return false;
}
if ( EnemyDead() ) {
enemy_dead();
return true;
}
if ( EnemyInFOV() ) {
LookAtEnemy( 1.0f );
}
const int attack_flags = check_attacks();
if ( attack_flags != 0 && do_attack( attack_flags ) ) {
return true;
}
if ( check_blocked() ) {
return true;
}
if ( !EnemyVisible() || EnemyRange() > run_distance ) {
do_run = true;
}
const float delta = GetTurnDelta();
run = !(( delta > walk_turn ) || ( delta < -walk_turn )) && do_run;
return true;
}
bool rvmMonsterBase::checkTurretAttack() {
return CanHitEnemy();
}
stateResult_t rvmMonsterBase::combat_turret_node( stateParms_t* parms ) {
// Long turret-node script is represented as a resumable attack-node state.
// It reaches the node, runs the subclass turret animation, and releases movement.
enum { STAGE_START, STAGE_MOVE, STAGE_ATTACK };
idEntity* node = scratchEntity.GetEntity();
switch ( parms->stage ) {
case STAGE_START:
if ( !node ) {
return SRESULT_DONE;
}
AllowMovement( true );
run = true;
MoveToEntity( node );
parms->stage = STAGE_MOVE;
return SRESULT_WAIT;
case STAGE_MOVE:
if ( !node || DestinationUnreachable() || CheckForMelee() || EnemyDead() ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
AllowMovement( true );
return SRESULT_DONE;
}
if ( !MoveDone() ) {
if ( EnemyInFOV() ) {
LookAtEnemy( 1.0f );
}
return SRESULT_WAIT;
}
FaceEnemy();
AllowMovement( false );
waitActionName = "turret_attack";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_TurretAttack", 4 );
parms->stage = STAGE_ATTACK;
return SRESULT_WAIT;
case STAGE_ATTACK:
if ( !ActionDone( "turret_attack" ) ) {
if ( EnemyInFOV() ) {
LookAtEnemy( 1.0f );
}
return SRESULT_WAIT;
}
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
void rvmMonsterBase::checkNodeAnim( idEntity* node, const char* prefix, const char* anim ) {
idStr fullName = prefix;
fullName += "_";
fullName += anim;
if ( !HasAnim( ANIMCHANNEL_TORSO, fullName.c_str() ) && owner && node ) {
gameLocal.Warning( "AI node '%s' specifies missing anim '%s' on monster '%s'", node->GetName(), fullName.c_str(), owner->GetName() );
}
}
stateResult_t rvmMonsterBase::combat_attack_cone( stateParms_t* parms ) {
enum { STAGE_START, STAGE_MOVE, STAGE_WAIT, STAGE_OUT, STAGE_FIRE, STAGE_IN };
idEntity* node = scratchEntity.GetEntity();
switch ( parms->stage ) {
case STAGE_START:
if ( !node ) {
return SRESULT_DONE;
}
run = true;
MoveToEntity( node );
parms->stage = STAGE_MOVE;
return SRESULT_WAIT;
case STAGE_MOVE:
if ( !node || DestinationUnreachable() || CheckForMelee() || EnemyDead() || ( owner && !owner->EnemyInCombatCone( node, false ) ) ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
return SRESULT_DONE;
}
if ( !MoveDone() ) {
if ( EnemyInFOV() ) {
LookAtEnemy( 1.0f );
}
return SRESULT_WAIT;
}
scratchString = node->spawnArgs.GetString( "anim", "" );
if ( !scratchString.Length() ) {
return SRESULT_DONE;
}
SetAnimPrefix( scratchString.c_str() );
checkNodeAnim( node, scratchString.c_str(), "out" );
checkNodeAnim( node, scratchString.c_str(), "fire" );
checkNodeAnim( node, scratchString.c_str(), "in" );
checkNodeAnim( node, scratchString.c_str(), "wait" );
TurnTo( node->GetPhysics()->GetAxis()[0].ToYaw() );
AllowMovement( false );
playCustomCycle( "wait", 4 );
scratchEndTime = DelayTime( node->spawnArgs.GetFloat( "min_wait", "0" ) + RandomFloat( node->spawnArgs.GetFloat( "max_wait", "0" ) - node->spawnArgs.GetFloat( "min_wait", "0" ) ) );
parms->stage = STAGE_WAIT;
return SRESULT_WAIT;
case STAGE_WAIT:
if ( !node || CheckForMelee() || EnemyDead() || ( owner && !owner->EnemyInCombatCone( node, false ) ) ) {
parms->stage = STAGE_IN;
return SRESULT_WAIT;
}
if ( Time() < scratchEndTime || !FacingIdeal() ) {
if ( EnemyInFOV() ) {
LookAtEnemy( 1.0f );
}
return SRESULT_WAIT;
}
AllowMovement( true );
playCustomAnim( "out", 4, 0 );
parms->stage = STAGE_OUT;
return SRESULT_WAIT;
case STAGE_OUT:
if ( !AnimDone( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_WAIT;
}
AllowMovement( false );
playCustomAnim( "fire", 0, 0 );
scratchCount = idMath::Ftoi( RandomFloat( node ? node->spawnArgs.GetFloat( "num_shots", "1" ) : 1.0f ) ) + 1;
parms->stage = STAGE_FIRE;
return SRESULT_WAIT;
case STAGE_FIRE:
if ( !AnimDone( ANIMCHANNEL_TORSO, 0 ) ) {
return SRESULT_WAIT;
}
scratchCount -= 1.0f;
if ( scratchCount > 0.0f && node && !EnemyDead() ) {
playCustomAnim( "fire", 0, 0 );
return SRESULT_WAIT;
}
AllowMovement( true );
playCustomAnim( "in", 0, 4 );
parms->stage = STAGE_IN;
return SRESULT_WAIT;
case STAGE_IN:
if ( !AnimDone( ANIMCHANNEL_TORSO, 4 ) ) {
return SRESULT_WAIT;
}
AllowMovement( true );
SetAnimPrefix( "" );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::combat_ainode( stateParms_t* parms ) {
idEntity* node = scratchEntity.GetEntity();
if ( node && !idStr::Icmp( node->spawnArgs.GetString( "classname" ), "ai_attackcone_turret" ) ) {
return combat_turret_node( parms );
}
return combat_attack_cone( parms );
}
void rvmMonsterBase::combat_lost() {
if ( !ignore_lostcombat ) {
SetState( "state_LostCombat" );
}
}
stateResult_t rvmMonsterBase::state_LostCombat( stateParms_t* parms ) {
monster_base_lost_combat();
return SRESULT_WAIT;
}
void rvmMonsterBase::monster_base_lost_combat() {
if ( EnemyVisible() || InfluenceActive() ) {
SetState( "state_Combat" );
} else {
ClearEnemy();
SetState( "state_Idle" );
}
}
stateResult_t rvmMonsterBase::combat_wander( stateParms_t* parms ) {
enum { STAGE_START, STAGE_WANDER };
switch ( parms->stage ) {
case STAGE_START:
scratchMinTime = DelayTime( 0.2f );
scratchEndTime = DelayTime( 3.0f );
Wander();
parms->stage = STAGE_WANDER;
return SRESULT_WAIT;
case STAGE_WANDER:
if ( InfluenceActive() || check_attacks() || Time() >= scratchEndTime || ( Time() > scratchMinTime && CanReachEnemy() && ( EnemyInFOV() || Pain() ) ) ) {
SetState( "state_Combat" );
return SRESULT_DONE;
}
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterBase::state_FollowAlternatePath( stateParms_t* parms ) {
enum { STAGE_START, STAGE_PATH };
switch ( parms->stage ) {
case STAGE_START:
if ( InAnimState( ANIMCHANNEL_TORSO, "Torso_CustomCycle" ) || InAnimState( ANIMCHANNEL_TORSO, "Torso_CustomAnim" ) ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 8 );
}
ignoreEnemies = true;
StartAction( "idle_followPathEntities" );
parms->stage = STAGE_PATH;
return SRESULT_WAIT;
case STAGE_PATH:
if ( InAction() ) {
return SRESULT_WAIT;
}
ignoreEnemies = false;
SetState( "state_Idle" );
return SRESULT_DONE;
}
return SRESULT_ERROR;
}
int rvmMonsterBase::check_attacks() {
return 0;
}
bool rvmMonsterBase::do_attack( int attack_flags ) {
return false;
}
void rvmMonsterBase::playCustomCycle( const char* animname, float blendTime ) {
SetAnimState( ANIMCHANNEL_TORSO, "Torso_CustomCycle", idMath::Ftoi( blendTime ) );
OverrideAnim( ANIMCHANNEL_LEGS );
PlayCycle( ANIMCHANNEL_TORSO, animname );
}
void rvmMonsterBase::playCustomAnim( const char* animname, float blendIn, float blendOut ) {
customBlendOut = blendOut;
SetAnimState( ANIMCHANNEL_TORSO, "Torso_CustomAnim", idMath::Ftoi( blendIn ) );
OverrideAnim( ANIMCHANNEL_LEGS );
PlayAnim( ANIMCHANNEL_TORSO, animname );
}
void rvmMonsterBase::trigger_wakeup_targets() {
if ( !owner ) {
return;
}
const idKeyValue* kv = owner->spawnArgs.MatchPrefix( "wakeup_target", NULL );
while ( kv ) {
idEntity* ent = gameLocal.FindEntity( kv->GetValue() );
if ( ent ) {
const idEventDef* activateEvent = ent->FindEventDef( "activate", "e" );
if ( activateEvent ) {
ent->ProcessEvent( activateEvent, owner );
}
} else {
gameLocal.Warning( "Unknown wakeup_target '%s' on entity '%s'", kv->GetValue().c_str(), owner->GetName() );
}
kv = owner->spawnArgs.MatchPrefix( "wakeup_target", kv );
}
}
bool rvmMonsterBase::checkForEnemy( bool use_fov ) {
if ( InfluenceActive() ) {
return false;
}
if ( Pain() ) {
ambush = false;
}
idEntity* enemyEnt = NULL;
if ( ignoreEnemies ) {
if ( stay_on_attackpath ) {
return false;
}
enemyEnt = GetEnemy();
if ( !enemyEnt && owner ) {
enemyEnt = owner->FindEnemy( false );
}
if ( !enemyEnt ) {
return false;
}
const idBounds& b = owner->GetPhysics()->GetBounds();
const float dist = ( ( b[1] - b[0] ).x * 1.414f ) + 16.0f;
if ( EnemyRange() > dist ) {
return false;
}
} else {
if ( GetEnemy() ) {
return true;
}
if ( !ignore_sight && owner ) {
enemyEnt = owner->FindEnemy( use_fov ? 1 : 0 );
}
if ( !enemyEnt ) {
if ( ambush ) {
return false;
}
enemyEnt = owner ? owner->HeardSound( true ) : NULL;
if ( !enemyEnt ) {
return false;
}
}
}
ignoreEnemies = false;
ambush = false;
idle_sight_fov = false;
SetEnemy( enemyEnt );
return true;
}
void rvmMonsterBase::archvile_minion() {
HideOwner();
resurrect = true;
if ( owner ) {
owner->AI_DEAD = true;
}
}
bool rvmMonsterBase::can_resurrect() {
return owner && owner->AI_DEAD && owner->IsHidden() && CanBecomeSolid();
}
void rvmMonsterBase::monster_resurrect( idEntity* enemyEnt ) {
if ( owner ) {
owner->AI_DEAD = false;
}
SetEnemy( enemyEnt ? enemyEnt : Player() );
SetState( "state_Resurrect" );
}
void rvmMonsterBase::follow_alternate_path1() {
current_path = GetEntityKey( "alt_path1" );
SetState( "state_FollowAlternatePath" );
}
void rvmMonsterBase::follow_alternate_path2() {
current_path = GetEntityKey( "alt_path2" );
SetState( "state_FollowAlternatePath" );
}
void rvmMonsterBase::follow_alternate_path3() {
current_path = GetEntityKey( "alt_path3" );
SetState( "state_FollowAlternatePath" );
}