/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
/***********************************************************************************************
*** Confidential - Westwood Studios ***
***********************************************************************************************
* *
* Project Name : Commando *
* *
* $Archive:: /Commando/Code/Combat/soldierobserver.cpp $*
* *
* $Author:: Byon_g $*
* *
* $Modtime:: 12/17/01 3:06p $*
* *
* $Revision:: 112 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "soldierobserver.h"
#include "soldier.h"
#include "action.h"
#include "debug.h"
#include "crandom.h"
#include "cover.h"
#include "wwaudio.h"
#include "persistfactory.h"
#include "combatchunkid.h"
#include "combat.h"
#include "gameobjobserver.h"
#include "gameobjmanager.h"
#include "weapons.h"
#include "conversationmgr.h"
#include "pathfind.h"
#include "timemgr.h"
#include "weaponbag.h"
#include "wwprofile.h"
#include "playertype.h"
#define THINK_RATE 1
#define THINK_ID 100123
#define INNATE_ACTION_ID 9000000
/*
**
*/
const char * SoldierAIStateNames[SoldierObserverClass::NUM_SOLDIER_AI_STATES] =
{
"Relaxed Idle", //SOLDIER_AI_RELAXED_IDLE = 0,
"Alert Idle", //SOLDIER_AI_ALERT_IDLE,
"Footsteps Heard", //SOLDIER_AI_FOOTSTEPS_HEARD,
"Bullet Heard", //SOLDIER_AI_BULLET_HEARD,
"Gunshot Heard", //SOLDIER_AI_GUNSHOT_HEARD,
"Enemy Seen" //SOLDIER_AI_ENEMY_SEEN,
};
int StatePriorities[ SoldierObserverClass::NUM_SOLDIER_AI_STATES ] =
{
10, // SOLDIER_AI_RELAXED_IDLE = 0,
20, // SOLDIER_AI_ALERTED_IDLE,
50, // SOLDIER_AI_FOOTSTEPS_HEARD,
50, // SOLDIER_AI_BULLET_HEARD,
70, // SOLDIER_AI_GUNSHOT_HEARD,
90, // SOLDIER_AI_ENEMY_SEEN,
};
typedef struct {
float StateDuration; // How long we should stay in this state
int NextState; // State to enter when we complete this state
} SoldierAIStateData;
SoldierAIStateData StateData[SoldierObserverClass::NUM_SOLDIER_AI_STATES] = {
{9999, SoldierObserverClass::SOLDIER_AI_RELAXED_IDLE},
{180, SoldierObserverClass::SOLDIER_AI_RELAXED_IDLE},
{6, SoldierObserverClass::SOLDIER_AI_CONDITIONAL_IDLE},
{20, SoldierObserverClass::SOLDIER_AI_ALERT_IDLE},
{60, SoldierObserverClass::SOLDIER_AI_ALERT_IDLE}, // {60, SoldierObserverClass::SOLDIER_AI_BULLET_HEARD},
{60, SoldierObserverClass::SOLDIER_AI_BULLET_HEARD},
};
/*
** Speech
*/
const char* _SpeakEnemySeen[] =
{
"ThereHeIs02",
"KillHim02",
"GetHim02",
"ForKane02",
};
const int _SpeakEnemySeenCount = (sizeof(_SpeakEnemySeen) / sizeof(_SpeakEnemySeen[0])) ;
const char* Pick_Speak_EnemySeen(void)
{
int index = FreeRandom.Get_Int(_SpeakEnemySeenCount);
return _SpeakEnemySeen[index];
}
Vector3 Random_Vector(float size)
{
Vector3 vect( FreeRandom.Get_Float( -size, size ),
FreeRandom.Get_Float( -size, size ), 0 );
return vect;
}
/*
**
*/
SoldierObserverClass::SoldierObserverClass( void ) :
State( SOLDIER_AI_RELAXED_IDLE ),
StateTimer( 0 ),
HomeRadius( 9999999 ),
ActionTimer( 0 ),
CoverPosition( NULL ),
CoveredAttack( false ),
LastEvent( SOLDIER_AI_RELAXED_IDLE ),
ConversationTimer( WWMath::Random_Float( 1.0F, 10.0F ) ),
IsAlerted( false ),
Aggressiveness( 0.5f ),
TakeCoverProbability( 0.5f ),
IsStationary( false ),
LastWeaponIndex( 0 )
{
}
SoldierObserverClass::~SoldierObserverClass( void )
{
Release_Cover_Position();
}
/*
**
*/
void SoldierObserverClass::Release_Cover_Position( void )
{
if ( CoverPosition != NULL ) {
CoverManager::Release_Cover( CoverPosition );
CoverPosition = NULL;
}
}
/*
**
*/
SimplePersistFactoryClass _SoldierObserverPersistFactory;
const PersistFactoryClass & SoldierObserverClass::Get_Factory (void) const
{
return _SoldierObserverPersistFactory;
}
enum {
CHUNKID_PARENT = 410001836,
CHUNKID_VARIABLES,
CHUNKID_ENEMY_OBJ_REF,
MICROCHUNKID_STATE = 1,
MICROCHUNKID_STATE_TIMER,
MICROCHUNKID_HOME_LOCATION,
MICROCHUNKID_ALERT_POSITION,
MICROCHUNKID_ACTION_TIMER,
XXXMICROCHUNKID_ANIMATING,
XXXMICROCHUNKID_MOVING,
MICROCHUNKID_COVER_POSITION,
MICROCHUNKID_COVERED_ATTACK,
MICROCHUNKID_HOME_RADIUS,
XXXMICROCHUNKID_ESCORT_OFFSET,
MICROCHUNKID_CONVERSATION_TIMER,
MICROCHUNKID_IS_ALERTED,
MICROCHUNKID_AGGRESSIVENESS,
MICROCHUNKID_TAKE_COVER_PROBABILITY,
MICROCHUNKID_IS_STATIONARY,
MICROCHUNKID_LAST_WEAPON_INDEX,
};
bool SoldierObserverClass::Save (ChunkSaveClass &csave)
{
csave.Begin_Chunk( CHUNKID_PARENT );
PersistentGameObjObserverClass::Save( csave );
csave.End_Chunk();
if ( CombatManager::Are_Observers_Active() ) {
csave.Begin_Chunk( CHUNKID_VARIABLES );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE_TIMER, StateTimer );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_HOME_LOCATION, HomeLocation );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_HOME_RADIUS, HomeRadius );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ALERT_POSITION, AlertPosition );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ACTION_TIMER, ActionTimer );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_COVER_POSITION, CoverPosition );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_COVERED_ATTACK, CoveredAttack );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CONVERSATION_TIMER, ConversationTimer );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_ALERTED, IsAlerted );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_AGGRESSIVENESS, Aggressiveness );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TAKE_COVER_PROBABILITY, TakeCoverProbability );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_STATIONARY, IsStationary );
WRITE_MICRO_CHUNK( csave, MICROCHUNKID_LAST_WEAPON_INDEX, LastWeaponIndex );
csave.End_Chunk();
if ( EnemyObject != NULL ) {
csave.Begin_Chunk( CHUNKID_ENEMY_OBJ_REF );
EnemyObject.Save( csave );
csave.End_Chunk();
}
}
// Don't need to save SubStateString, or LastEvent
return true;
}
bool SoldierObserverClass::Load (ChunkLoadClass &cload)
{
while (cload.Open_Chunk()) {
switch(cload.Cur_Chunk_ID()) {
case CHUNKID_PARENT:
PersistentGameObjObserverClass::Load( cload );
break;
case CHUNKID_VARIABLES:
while (cload.Open_Micro_Chunk()) {
switch(cload.Cur_Micro_Chunk_ID()) {
READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE, State );
READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE_TIMER, StateTimer );
READ_MICRO_CHUNK( cload, MICROCHUNKID_HOME_LOCATION, HomeLocation );
READ_MICRO_CHUNK( cload, MICROCHUNKID_HOME_RADIUS, HomeRadius );
READ_MICRO_CHUNK( cload, MICROCHUNKID_ALERT_POSITION, AlertPosition );
READ_MICRO_CHUNK( cload, MICROCHUNKID_ACTION_TIMER, ActionTimer );
READ_MICRO_CHUNK( cload, MICROCHUNKID_COVER_POSITION, CoverPosition );
READ_MICRO_CHUNK( cload, MICROCHUNKID_COVERED_ATTACK, CoveredAttack );
READ_MICRO_CHUNK( cload, MICROCHUNKID_CONVERSATION_TIMER, ConversationTimer );
READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_ALERTED, IsAlerted );
READ_MICRO_CHUNK( cload, MICROCHUNKID_AGGRESSIVENESS, Aggressiveness );
READ_MICRO_CHUNK( cload, MICROCHUNKID_TAKE_COVER_PROBABILITY, TakeCoverProbability );
READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_STATIONARY, IsStationary );
READ_MICRO_CHUNK( cload, MICROCHUNKID_LAST_WEAPON_INDEX, LastWeaponIndex );
default:
Debug_Say(("Unhandled Variable Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
break;
}
cload.Close_Micro_Chunk();
}
break;
case CHUNKID_ENEMY_OBJ_REF:
EnemyObject.Load( cload );
break;
default:
Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
break;
}
cload.Close_Chunk();
}
if ( CoverPosition ) {
REQUEST_POINTER_REMAP( (void **)&CoverPosition );
}
return true;
}
/*
**
*/
void SoldierObserverClass::Attach( GameObject * obj )
{
// Warning, Attach may not be called on loaded scripts
}
void SoldierObserverClass::Detach( GameObject * obj )
{
//
// Clear the soldier's internal innate observer pointer
//
SmartGameObj *smart_game_obj = obj->As_SmartGameObj();
if ( smart_game_obj != NULL ) {
SoldierGameObj *soldier = smart_game_obj->As_SoldierGameObj();
if ( soldier != NULL ) {
if ( soldier->Get_Innate_Observer () == this ) {
soldier->Clear_Innate_Observer();
}
}
}
GameObjObserverManager::Delete_Register( this );
}
void SoldierObserverClass::Created( GameObject* obj )
{
SmartGameObj* smart = obj->As_SmartGameObj();
WWASSERT( smart != NULL );
// Debug_Say(("Innate soldier [%d] created\n", obj->Get_ID()));
smart->Get_Position( &HomeLocation );
// Enable enemy sensory
smart->Set_Enemy_Seen_Enabled(true);
obj->Start_Observer_Timer( Get_ID(), THINK_RATE, THINK_ID );
Aggressiveness = smart->As_SoldierGameObj()->Get_Definition().InnateAggressiveness;
// Modify aggressiveness based on difficulty
switch ( CombatManager::Get_Difficulty_Level() ) {
case 0:
if ( Aggressiveness > 0.25f ) {
Aggressiveness -= 0.25f;
}
break;
case 1:
// no change
break;
case 2:
Aggressiveness += 0.25f;
if ( Aggressiveness > 1 ) {
Aggressiveness = 1;
}
break;
};
TakeCoverProbability = smart->As_SoldierGameObj()->Get_Definition().InnateTakeCoverProbability;
IsStationary = smart->As_SoldierGameObj()->Get_Definition().InnateIsStationary;
}
void SoldierObserverClass::Destroyed(GameObject* obj)
{
// Debug_Say(("Innate soldier [%d] destroyed\n", obj->Get_ID()));
Release_Cover_Position();
}
void SoldierObserverClass::Timer_Expired(GameObject* obj, int timer_id)
{
SmartGameObj* smart = obj->As_SmartGameObj();
WWASSERT( smart != NULL );
SoldierGameObj* soldier = smart->As_SoldierGameObj();
WWASSERT(soldier != NULL);
if (timer_id == THINK_ID) {
obj->Start_Observer_Timer( Get_ID(), THINK_RATE, THINK_ID );
Think(soldier);
}
}
void SoldierObserverClass::Killed(GameObject* obj, GameObject* killer)
{
// Debug_Say(("Innate soldier [%d] killed by [%d]\n", obj->Get_ID(), killer ? killer->Get_ID() : -1));
}
void SoldierObserverClass::Damaged(GameObject* obj, GameObject* damager, float amount)
{
// Debug_Say(("Innate soldier [%d] damaged by [%d]\n", obj->Get_ID(), damager ? damager->Get_ID() : -1));
if ( obj->Is_Hibernating() ) {
return;
}
SmartGameObj* smart = obj->As_SmartGameObj();
WWASSERT( smart != NULL );
SoldierGameObj* soldier = smart->As_SoldierGameObj();
WWASSERT(soldier != NULL);
if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_BULLET_HEARD)) {
//Debug_Say(( "Damaged\n" ));
Vector3 pos;
soldier->Get_Position( &pos );
if ( damager != NULL ) {
if ( FreeRandom.Get_Float() < 0.8 ) { // 80% chance of knowing the source
damager->Get_Position( &pos );
}
}
Set_State(soldier, SOLDIER_AI_BULLET_HEARD, pos);
}
// If I am attacking an object, and I am damaged by a closer object, switch targets
if ( State == SOLDIER_AI_ENEMY_SEEN ) {
GameObject * enemy = EnemyObject;
if ( damager != NULL && enemy != damager ) {
Vector3 my_pos;
soldier->Get_Position( &my_pos );
Vector3 pos;
float target_range = 1000000;
if ( enemy ) {
enemy->Get_Position( &pos );
pos -= my_pos;
target_range = pos.Length();
}
damager->Get_Position( &pos );
pos -= my_pos;
if ( pos.Length() < target_range ) {
PhysicalGameObj * pdamager = damager->As_PhysicalGameObj();
if ( pdamager && pdamager->Is_Enemy( soldier ) ) {
Debug_Say(( "Switching to nearer target\n" ));
EnemyObject = damager;
ActionTimer = 0; // Act on it now
}
}
}
}
}
void SoldierObserverClass::Sound_Heard(GameObject* obj, const CombatSound& sound)
{
if ( obj->Is_Hibernating() ) {
return;
}
SmartGameObj* smart = obj->As_SmartGameObj();
WWASSERT( smart != NULL );
SoldierGameObj* soldier = smart->As_SoldierGameObj();
WWASSERT(soldier != NULL);
PhysicalGameObj * creator = NULL;
if ( sound.Creator != NULL ) {
creator = sound.Creator->As_PhysicalGameObj();
}
bool state_changed = false;
switch (sound.Type) {
case SOUND_TYPE_BULLET_HIT:
{
if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_BULLET_HEARD)) {
Vector3 pos;
pos = sound.Position;
if ( creator && FreeRandom.Get_Float() < 0.7 ) { // 70% chance of knowing the source
creator->Get_Position( &pos );
}
state_changed = Set_State(soldier, SOLDIER_AI_BULLET_HEARD, pos);
}
}
break;
case SOUND_TYPE_GUNSHOT:
// Don't hear friendly
if ( (creator == NULL) || !soldier->Is_Teammate( creator ) ) {
if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_GUNSHOT_HEARD)) {
state_changed = Set_State(soldier, SOLDIER_AI_GUNSHOT_HEARD, sound.Position);
}
}
break;
case SOUND_TYPE_FOOTSTEPS:
if ( (creator == NULL) || !soldier->Is_Teammate( creator ) ) {
if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_FOOTSTEP_HEARD)) {
//Debug_Say(( "Heard Footsteps\n" ));
state_changed = Set_State(soldier, SOLDIER_AI_FOOTSTEPS_HEARD, sound.Position);
}
}
break;
default:
// Debug_Say(("Innate soldier [%d] unrecognized sound!\n", obj->Get_ID()));
break;
}
if ( state_changed ) {
Notify_Neighbors_Sound( soldier, sound );
}
}
void SoldierObserverClass::Enemy_Seen(GameObject* obj, GameObject* enemy)
{
if ( obj->Is_Hibernating() ) {
return;
}
// WWASSERT( obj != enemy );
// only see the enemy if it has health
WWASSERT( enemy );
PhysicalGameObj * p_enemy = enemy->As_PhysicalGameObj();
if ( p_enemy == NULL || p_enemy->Get_Defense_Object()->Get_Health() <= 0 ) {
// Debug_Say(( "I see dead people\n" ));
return;
}
GameObject * curr_enemy = EnemyObject;
if ( curr_enemy != NULL && curr_enemy != enemy ) {
// take the closer enemy
Vector3 old_pos;
curr_enemy->Get_Position( &old_pos );
Vector3 new_pos;
enemy->Get_Position( &new_pos );
Vector3 my_pos;
obj->Get_Position( &my_pos );
old_pos -= my_pos;
new_pos -= my_pos;
if ( new_pos.Length2() > old_pos.Length2() ) {
// Debug_Say(( "New guy is too far away!\n" ));
return;
}
// Debug_Say(( "Found New guy!!\n" ));
}
SmartGameObj* smart = obj->As_SmartGameObj();
WWASSERT( smart != NULL );
SoldierGameObj* soldier = smart->As_SoldierGameObj();
WWASSERT(soldier != NULL);
// Debug_Say(("Innate soldier [%d] seen enemy [%d]\n", obj->Get_ID(), enemy->Get_ID()));
bool state_changed = false;
if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_ENEMY_SEEN)) {
//Debug_Say(( "Seen Enemy\n" ));
state_changed = Set_State(soldier, SOLDIER_AI_ENEMY_SEEN, Vector3(0,0,0), enemy);
}
if ( state_changed ) {
Notify_Neighbors_Enemy( soldier, enemy );
}
}
void SoldierObserverClass::Custom( GameObject * obj, int type, int param, GameObject * sender )
{
if ( type == CUSTOM_EVENT_ATTACK_ARRIVED ) {
// Debug_Say(( "Attack Arrvied event %d\n", param ));
}
}
void SoldierObserverClass::Action_Complete( GameObject * obj, int action_id, ActionCompleteReason complete_reason )
{
if ( action_id == INNATE_ACTION_ID ) {
SubStateString += "\nComplete";
// ActionTimer = MIN( ActionTimer, 10 ); // When he completes, only stay in the action state for 5 more seconds
// if I had an enemy target, who was just killed...
PhysicalGameObj* enemy = (PhysicalGameObj*)EnemyObject.Get_Ptr();
if ( enemy && State == SOLDIER_AI_ENEMY_SEEN ) {
if ( enemy->Get_Defense_Object()->Get_Health() <= 0 ) {
// Debug_Say(( "Target dead, moving on\n" ));
StateTimer = 100000; // Leave current state
EnemyObject = NULL;
}
}
if ( complete_reason == ACTION_COMPLETE_NORMAL ||
complete_reason == ACTION_COMPLETE_PATH_BAD_START ||
complete_reason == ACTION_COMPLETE_PATH_BAD_DEST )
{
if ( complete_reason == ACTION_COMPLETE_PATH_BAD_DEST ) {
// Pick a new action within 1 to 2 seconds
ActionTimer = MIN( ActionTimer, WWMath::Random_Float( 1.0F, 2.0F ) );
} else {
// Pick a new action within 2 to 3 seconds
ActionTimer = MIN( ActionTimer, WWMath::Random_Float( 2.0F, 3.0F ) );
}
if ( State > SOLDIER_AI_ALERT_IDLE ) {
StateTimer = 100000; // Leave current state
}
}
}
}
/*
**
*/
void SoldierObserverClass::State_Changed( SoldierGameObj * soldier )
{
switch ( State ) {
case SOLDIER_AI_RELAXED_IDLE:
IsAlerted = false;
break;
case SOLDIER_AI_FOOTSTEPS_HEARD:
// Leave it was it was
break;
case SOLDIER_AI_ALERT_IDLE:
case SOLDIER_AI_BULLET_HEARD:
case SOLDIER_AI_GUNSHOT_HEARD:
case SOLDIER_AI_ENEMY_SEEN:
IsAlerted = true;
break;
}
// Remeber the last selected weapon
if ( soldier->Get_Weapon_Bag()->Get_Index() != 0 ) {
LastWeaponIndex = soldier->Get_Weapon_Bag()->Get_Index();
}
if ( IsAlerted ) {
soldier->Get_Weapon_Bag()->Select_Index( LastWeaponIndex );
} else {
soldier->Get_Weapon_Bag()->Select_Index( 0 );
}
if ( State == SOLDIER_AI_ALERT_IDLE ) {
soldier->Get_Human_State()->Drop_Weapon();
}
ActionTimer = 0;
}
/*
**
*/
bool SoldierObserverClass::Set_State( SoldierGameObj * soldier, int state, const Vector3& location, GameObject* enemy)
{
// WWASSERT( soldier != enemy );
LastEvent = state;
int old_state = State;
// If this is a more extreme state, act surprised!
if (StatePriorities[State] < StatePriorities[state]) {
// If we have seen the enemy then make a comment.
if (state == SOLDIER_AI_ENEMY_SEEN) {
// Speak(obj, Pick_Speak_EnemySeen());
} else if (state != SOLDIER_AI_FOOTSTEPS_HEARD) {
// Duck
// Action_Play_Animation(obj, "s_a_human.h_a_j21c");
// Speak(obj, "Q_Huh02");
}
// Debug_Say(("Switching to State %d from %d\n", state, State));
AlertPosition = location;
if ( State != state ) {
State = state;
State_Changed( soldier );
}
// Debug_Say(( "New Innate State!\n" ));
}
// If this is not a less extreme state, reset state timer
if (StatePriorities[State] <= StatePriorities[state]) {
if ( State != state ) {
State = state;
State_Changed( soldier );
}
StateTimer = 0;
if ( ( state == SOLDIER_AI_BULLET_HEARD ) ||
( state == SOLDIER_AI_GUNSHOT_HEARD ) ) {
if ( ActionTimer > 1 ) {
ActionTimer = 1;
}
}
// ActionTimer = 0; no
#if 01
// Only take the new pos if closer
Vector3 my_pos;
soldier->Get_Position(&my_pos);
Vector3 old_pos = AlertPosition - my_pos;
Vector3 new_pos = location - my_pos;
if ( old_pos.Length2() > new_pos.Length2() )
#endif
{
AlertPosition = location;
}
if ( enemy != NULL ) {
EnemyObject = enemy->As_PhysicalGameObj();
} else {
EnemyObject = NULL;
}
Think(soldier, (State != old_state) );
}
//
// Reset the conversation timer if the state has changed
//
if ( old_state != State ) {
if ( State == SOLDIER_AI_ENEMY_SEEN ) {
ConversationTimer = WWMath::Random_Float( 5.0F, 30.0F );
} else {
ConversationTimer = WWMath::Random_Float( 20.0F, 60.0F );
}
}
//
// Decide which (if any) dialogue the soldier should say
//
if ( ( old_state == SOLDIER_AI_RELAXED_IDLE ) || ( old_state == SOLDIER_AI_ALERT_IDLE ) ) {
if ( State == SOLDIER_AI_FOOTSTEPS_HEARD ||
State == SOLDIER_AI_BULLET_HEARD ||
State == SOLDIER_AI_GUNSHOT_HEARD)
{
soldier->Say_Dialogue( DIALOG_STATE_FROM_IDLE_TO_SEARCH );
} else if ( State == SOLDIER_AI_ENEMY_SEEN ) {
soldier->Say_Dialogue( DIALOG_STATE_FROM_IDLE_TO_COMBAT );
}
} else if ( old_state == SOLDIER_AI_ENEMY_SEEN ) {
if ( State == SOLDIER_AI_FOOTSTEPS_HEARD ||
State == SOLDIER_AI_BULLET_HEARD ||
State == SOLDIER_AI_GUNSHOT_HEARD)
{
soldier->Say_Dialogue( DIALOG_STATE_FROM_COMBAT_TO_SEARCH );
} else if ( ( old_state == SOLDIER_AI_RELAXED_IDLE ) || ( old_state == SOLDIER_AI_ALERT_IDLE ) ) {
soldier->Say_Dialogue( DIALOG_STATE_FROM_COMBAT_TO_IDLE );
}
} else {
if ( State == SOLDIER_AI_ENEMY_SEEN ) {
soldier->Say_Dialogue( DIALOG_STATE_FROM_SEARCH_TO_COMBAT );
} else if ( ( old_state == SOLDIER_AI_RELAXED_IDLE ) || ( old_state == SOLDIER_AI_ALERT_IDLE ) ) {
soldier->Say_Dialogue( DIALOG_STATE_FROM_SEARCH_TO_IDLE );
}
}
return (old_state != State);
}
void SoldierObserverClass::Notify_Neighbors_Sound( SoldierGameObj * soldier, const CombatSound & sound )
{
// Notify nearby neighbors of my knowledge
// for all physicalgameobjs
Vector3 my_pos;
soldier->Get_Position( &my_pos );
SLNode *objnode;
for ( objnode = GameObjManager::Get_Smart_Game_Obj_List()->Head(); objnode; objnode = objnode->Next()) {
SmartGameObj *obj = objnode->Data()->As_SmartGameObj();
if ( obj ) {
if ( obj == soldier ) continue;
if ( obj->Get_Player_Type() != soldier->Get_Player_Type() ) continue;
Vector3 obj_pos;
obj->Get_Position( &obj_pos );
obj_pos -= my_pos;
if ( obj_pos.Length() <= 5 ) {
// Notify him of my info
const GameObjObserverList & observer_list = obj->Get_Observers();
for( int index = 0; index < observer_list.Count(); index++ ) {
observer_list[ index ]->Sound_Heard( obj, sound );
}
}
}
}
}
void SoldierObserverClass::Notify_Neighbors_Enemy( SoldierGameObj * soldier, GameObject * enemy )
{
// Notify nearby neighbors of my knowledge
// for all physicalgameobjs
Vector3 my_pos;
soldier->Get_Position( &my_pos );
SLNode *objnode;
for ( objnode = GameObjManager::Get_Smart_Game_Obj_List()->Head(); objnode; objnode = objnode->Next()) {
SmartGameObj *obj = objnode->Data()->As_SmartGameObj();
if ( obj ) {
if ( obj == soldier ) continue;
if ( obj->Get_Player_Type() != soldier->Get_Player_Type() ) continue;
Vector3 obj_pos;
obj->Get_Position( &obj_pos );
obj_pos -= my_pos;
if ( obj_pos.Length() <= 5 ) {
// Notify him of my info
const GameObjObserverList & observer_list = obj->Get_Observers();
for( int index = 0; index < observer_list.Count(); index++ ) {
observer_list[ index ]->Enemy_Seen( obj, enemy );
}
}
}
}
}
void SoldierObserverClass::Poked( GameObject * obj, GameObject * poker )
{
if ( obj != NULL && obj->As_PhysicalGameObj() != NULL ) {
PhysicalGameObj *physical_obj = obj->As_PhysicalGameObj();
if ( physical_obj->As_SoldierGameObj() != NULL ) {
bool face_poker = false;
//
// Get the soldier to say one of its pre-canned remarks
//
SoldierGameObj *soldier = physical_obj->As_SoldierGameObj();
float duration = 2.0F;
switch ( State )
{
case SOLDIER_AI_RELAXED_IDLE:
case SOLDIER_AI_ALERT_IDLE:
soldier->Say_Dialogue( DIALOG_ON_POKE_IDLE );
face_poker = true;
break;
case SOLDIER_AI_FOOTSTEPS_HEARD:
case SOLDIER_AI_BULLET_HEARD:
case SOLDIER_AI_GUNSHOT_HEARD:
soldier->Say_Dialogue( DIALOG_ON_POKE_SEARCH );
face_poker = true;
break;
case SOLDIER_AI_ENEMY_SEEN:
soldier->Say_Dialogue( DIALOG_ON_POKE_COMBAT );
break;
}
//
// Increment the duration by a small random amount
//
duration += WWMath::Random_Float( 0.5F, 2.0F );
PhysicalGameObj *physical_poker_obj = poker->As_PhysicalGameObj();
if ( physical_poker_obj != NULL ) {
//
// Get the position of the player who poked us
//
Vector3 poker_pos = physical_poker_obj->Get_Bullseye_Position();
//
// Make the soldier turn his body to face the speaker
//
if ( face_poker ) {
ActionParamsStruct parameters;
parameters.Priority = 20;
parameters.ObserverID = Get_ID();
parameters.Set_Face_Location( poker_pos, duration );
soldier->Get_Action()->Face_Location( parameters );
}
//
// Turn the soldier's head to look at the speaker
//
soldier->Look_At( poker_pos, duration );
}
}
}
return ;
}
void SoldierObserverClass::Think( SoldierGameObj * soldier, bool is_new_state )
{
WWPROFILE( "SoldierObserver Think" );
WWASSERT( soldier != NULL );
//
// We can't switch states if the soldier is "busy". Busy usually
// means he's going up an elevator, climbing a ladder, or opening a door.
//
if ( soldier->Get_Action()->Is_Busy() ) {
return ;
}
// Check how long we have been in this state, and if we should switch
StateTimer += 1;
if (StateTimer >= StateData[State].StateDuration) {
// Debug_Say(("Soldier relaxing from state %d to %d\n", State, StateData[State].NextState));
Action_Reset( soldier ); // reset the old state
State = StateData[State].NextState;
if ( State == SOLDIER_AI_CONDITIONAL_IDLE ) {
State = ( IsAlerted ) ? SOLDIER_AI_ALERT_IDLE : SOLDIER_AI_RELAXED_IDLE;
}
State_Changed( soldier );
StateTimer = 0;
}
if ( ( State != SOLDIER_AI_RELAXED_IDLE ) && ( State != SOLDIER_AI_ALERT_IDLE ) ) {
soldier->Reset_Loiter_Delay();
}
// Check if time for another state action
if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_ACTIONS)) {
ActionTimer -= 1;
if (ActionTimer <= 0) {
State_Act(soldier, is_new_state);
}
}
//
// Allow this unit to start a conversation regardless of the state he's in
//
if ( soldier->Is_Human_Controlled () == false && soldier->Is_In_Conversation () == false ) {
//
// Is it time to start a new conversation?
//
ConversationTimer -= 1.0F;
if (ConversationTimer <= 0) {
//
// Don't start an innate conversation if there are already
// conversations taking place
//
#ifdef ENABLE_INNATE_CONVERSATIONS
if ( ConversationMgrClass::Get_Active_Conversation_Count () == 0 ) {
ConversationMgrClass::Start_Conversation( soldier );
}
#endif //ENABLE_INNATE_CONVERSATIONS
Reset_Conversation_Timer();
}
}
}
void SoldierObserverClass::Get_Information( StringClass & string )
{
StringClass temp;
string += "-----------\n";
temp.Format( "State: %s\n", SoldierAIStateNames[ State ] );
string += temp;
temp.Format( "Sub: %s\n", SubStateString );
string += temp;
if ( IsStationary ) {
string += "Stationary\n";
}
if ( State == SOLDIER_AI_ENEMY_SEEN ) {
temp.Format( "Attacking: %d\n", EnemyObject.Get_Ptr() ? EnemyObject.Get_Ptr()->Get_ID() : 0 );
string += temp;
}
temp.Format( "Last: %s\n", SoldierAIStateNames[ LastEvent ] );
string += temp;
temp.Format( "Act Time: %f\n", ActionTimer );
string += temp;
if ( CoverPosition != NULL ) {
temp.Format( "Has Cover Pos\n" );
string += temp;
}
}
void SoldierObserverClass::Reset_Conversation_Timer( void )
{
ConversationTimer = WWMath::Random_Float( 20.0F, 60.0F );
}
/***********************************************************************************************
** Innate AI State Actions
***********************************************************************************************/
void SoldierObserverClass::State_Act( SoldierGameObj * soldier, bool is_new_state )
{
WWASSERT( soldier != NULL );
switch (State) {
case SOLDIER_AI_RELAXED_IDLE:
State_Act_Idle( soldier );
break;
case SOLDIER_AI_FOOTSTEPS_HEARD:
State_Act_Footsteps_Heard( soldier );
break;
case SOLDIER_AI_ALERT_IDLE:
case SOLDIER_AI_BULLET_HEARD:
State_Act_Bullet_Heard( soldier, is_new_state );
break;
case SOLDIER_AI_GUNSHOT_HEARD:
State_Act_Gunshot_Heard( soldier );
break;
case SOLDIER_AI_ENEMY_SEEN:
State_Act_Attack( soldier );
break;
}
}
#define IDLE_WALK_DISTANCE 6
#define IDLE_WALK_SPEED 0.3f // was 0.2f
#define ALERTED_RUN_DISTANCE 6
#define ALERTED_RUN_SPEED 0.9f
#define ALERTED_RUN_RANGE 2
/*
**
*/
void SoldierObserverClass::State_Act_Idle( SoldierGameObj * soldier )
{
ActionTimer = 5; // Will probably be overridden
Vector3 position;
soldier->Get_Position(&position);
Release_Cover_Position();
if ( FreeRandom.Get_Float() < 0.5f ) { // 50% chance of facing a random direction
SubStateString = "Facing Random";
position += Random_Vector(1);
if ( COMBAT_STAR ) {
SubStateString = "Facing Star";
COMBAT_STAR->Get_Position( &position );
}
Action_Face_Location( soldier, position, AI_STATE_IDLE );
ActionTimer = FreeRandom.Get_Float(4, 6); //Delay 4 to 6 seconds
} else if ( !IsStationary ) { // 50% chance of walking to a random point
SubStateString = "Random Walk";
PathfindClass *pathfind = PathfindClass::Get_Instance(); // Lookup a safe random position to walk to
if ( pathfind->Find_Random_Spot( position, IDLE_WALK_DISTANCE, &position ) ) {
Action_Goto_Location( soldier, position, AI_STATE_IDLE, IDLE_WALK_SPEED, 0.3f );
}
ActionTimer = FreeRandom.Get_Float(7, 10); //Delay 7 to 10 seconds
}
Look_Random( soldier, ActionTimer );
}
/*
**
*/
void SoldierObserverClass::State_Act_Footsteps_Heard( SoldierGameObj * soldier )
{
ActionTimer = FreeRandom.Get_Float(3, 4); //Delay 7 to 10 seconds
Release_Cover_Position();
bool walking = false;
if ( FreeRandom.Get_Float() < Aggressiveness && !IsStationary ) {
SubStateString = "Walk To Footsteps";
Vector3 position;
PathfindClass *pathfind = PathfindClass::Get_Instance();
if ( pathfind->Find_Random_Spot( AlertPosition, 2, &position ) ) {
Action_Goto_Location( soldier, position, AI_STATE_IDLE, IDLE_WALK_SPEED, 0.5f );
walking = true;
}
}
if ( !walking ) {
SubStateString = "Face Footsteps";
Action_Face_Location( soldier, AlertPosition, AI_STATE_SEARCH );
}
Look_Random( soldier, 0 ); // No random looking!
}
/*
**
*/
void SoldierObserverClass::State_Act_Bullet_Heard( SoldierGameObj * soldier, bool is_new_state )
{
ActionTimer = FreeRandom.Get_Float(5,10);
ActionTimer = FreeRandom.Get_Float(2,4);
soldier->Get_Human_State()->Raise_Weapon(); // Keep the gun up
Vector3 current_position;
soldier->Get_Position(¤t_position);
Vector3 walk_position;
#define RANGE 10 // Dive with probability tied to bullet distance; 100% at 10m, 0 percent at 20m
float range = (current_position - AlertPosition).Length();
float percent = WWMath::Clamp( (1 - ((range-RANGE)/RANGE)), 0, 1 );
bool dive = is_new_state && (FreeRandom.Get_Float() < percent) && !IsStationary;
if ( dive ) {
SubStateString = "Dive Away"; // Dive away
ActionTimer = FreeRandom.Get_Float(2,4);
Vector3 other_dir = 2*current_position - AlertPosition;
Action_Dive( soldier, other_dir );
} else {
bool done = false;
if ( !IsStationary ) {
done = Take_Cover( soldier );
}
if ( !done ) {
if ( FreeRandom.Get_Float() < TakeCoverProbability || IsStationary ) {
SubStateString = "Face Random Crouched"; // Face and crouch
walk_position = current_position + Random_Vector(1);
if ( COMBAT_STAR ) {
SubStateString = "Face Star Crouched";
COMBAT_STAR->Get_Position( &walk_position );
}
Action_Face_Location( soldier, walk_position, AI_STATE_IDLE, true );
} else {
SubStateString = "Run To";
// Lookup a safe random position to run to
PathfindClass *pathfind = PathfindClass::Get_Instance();
if ( pathfind->Find_Random_Spot( AlertPosition, ALERTED_RUN_DISTANCE, &walk_position ) ) {
Action_Goto_Location( soldier, walk_position, AI_STATE_SEARCH, ALERTED_RUN_SPEED, ALERTED_RUN_RANGE, true );
}
}
}
}
Look_Random( soldier, ActionTimer );
}
/*
**
*/
void SoldierObserverClass::State_Act_Gunshot_Heard( SoldierGameObj * soldier )
{
soldier->Get_Human_State()->Raise_Weapon(); // Keep the gun up
Look_Random( soldier, 0 );
ActionTimer = FreeRandom.Get_Float( 5, 10 );
if ( !IsStationary ) {
bool done = Take_Cover( soldier, true, AlertPosition );
if ( !done ) {
SubStateString = "Run To Random";
PathfindClass *pathfind = PathfindClass::Get_Instance();
//
// Lookup a safe random position to run to
//
Vector3 position = AlertPosition;
if (pathfind->Find_Random_Spot( AlertPosition, ALERTED_RUN_DISTANCE, &position )) {
Action_Goto_Location_Facing(soldier, position, AI_STATE_SEARCH, AlertPosition, ALERTED_RUN_SPEED, ALERTED_RUN_RANGE);
} else {
//
// Wasn't possible to run to a random location near the alert position,
// so simply move around your local area a little bit and look at the alert position
//
Vector3 curr_pos;
soldier->Get_Position (&curr_pos);
if (pathfind->Find_Random_Spot( curr_pos, IDLE_WALK_DISTANCE, &position )) {
Action_Goto_Location_Facing( soldier, position, AI_STATE_SEARCH, AlertPosition, IDLE_WALK_SPEED, 0.5F );
}
//Release_Cover_Position();
//SubStateString = "Face gunshot";
//Action_Face_Location( soldier, AlertPosition, AI_STATE_SEARCH );
}
}
} else {
Release_Cover_Position();
SubStateString = "Face gunshot";
Action_Face_Location( soldier, AlertPosition, AI_STATE_SEARCH );
}
}
/*
**
*/
void SoldierObserverClass::State_Act_Attack( SoldierGameObj * soldier )
{
WWASSERT( soldier != NULL );
soldier->Get_Human_State()->Raise_Weapon(); // Keep the gun up
ActionTimer = FreeRandom.Get_Float( 5, 8 );
Look_Random( soldier, 0 );
#define RUN_SPEED 0.9f
#define WALK_SPEED 0.2f
bool done = false;
// OK, here's the attack plan....
// Periodically (every 5 seconds?), pick a new attack action. 4 Attack options are:
// If in a attack point, jump back to the cover
// If in a cover spot, possibily jump out and attack (goto attack point)
// If there is a new cover spot available, goto it
// Pick a random point and goto it
PhysicalGameObj* enemy = (PhysicalGameObj*)EnemyObject.Get_Ptr();
// WWASSERT( soldier != enemy );
if ( enemy == NULL ) {
StateTimer = 100000; // Leave this state
return;
}
Vector3 enemy_pos;
enemy->Get_Position(&enemy_pos);
float weapon_range = 60.0f;
if ( soldier->Get_Weapon() ) {
weapon_range = soldier->Get_Weapon()->Get_Range();
}
Vector3 current_position;
soldier->Get_Position(¤t_position);
if ( CoverPosition != NULL ) {
if ( CoveredAttack == true ) {
SubStateString = "Return to Cover";
Vector3 cover_position = CoverPosition->Get_Transform().Get_Translation();
Action_Attack_Object( soldier, enemy, weapon_range, CoverPosition->Get_Crouch(), cover_position, 0.5f );
CoveredAttack = false;
ActionTimer = 5; // for 3 seconds
done = true;
} else if ( FreeRandom.Get_Float() < Aggressiveness ) {
if ( soldier->Get_Weapon() != NULL ) {
/*
** Start a covered attack.
** Pick an attack point, and go there, attacking at the target
*/
SubStateString = "Covered Attack";
Vector3 attack_point = CoverPosition->Get_Attack_Position( enemy_pos );
Action_Attack_Object( soldier, enemy, weapon_range, false, attack_point, 0.5f );
CoveredAttack = true;
ActionTimer = 5; // for 3 seconds
done = true;
}
}
}
if ( CoverPosition != NULL ) {
if ( !CoverManager::Is_Cover_Safe( CoverPosition, enemy_pos ) ) {
Release_Cover_Position();
CoveredAttack = false;
done = false;
}
}
float effective_range = 20;
if ( soldier->Get_Weapon() ) {
effective_range = soldier->Get_Weapon()->Get_Effective_Range();
}
if ( !done && ( FreeRandom.Get_Float() < TakeCoverProbability ) && !IsStationary ) { // 25% chance of not finding cover
Release_Cover_Position(); // Give us the option of selecting our current spot
CoverEntryClass * cover = CoverManager::Request_Cover(current_position, enemy_pos, effective_range);
if (cover != NULL) { // Yes, take it
SubStateString = "Take Cover";
CoverPosition = cover;
Vector3 cover_position = cover->Get_Transform().Get_Translation();
Action_Attack_Object( soldier, enemy, weapon_range, CoverPosition->Get_Crouch(), cover_position, 0.5f );
ActionTimer = 15;
done = true;
}
}
if ( FreeRandom.Get_Float() < Aggressiveness && !IsStationary ) { // Charge Attack
Vector3 pos;
enemy->Get_Position( &pos );
SubStateString = "Charge Attack";
ActionTimer = 10;
bool kneel = ( FreeRandom.Get_Float() < (1-Aggressiveness) );
Action_Attack_Object( soldier, enemy, weapon_range, kneel, pos, 5 );
done = true;
}
if ( !done ) {
Vector3 best_pos = current_position;
float best_distance = 100000;
for (int i = 0; i < 3; i++) {
// Lookup a safe random position to run to
Vector3 pos = current_position;
PathfindClass *pathfind = PathfindClass::Get_Instance();
if ( pathfind->Find_Random_Spot( current_position, effective_range*2, &pos ) ) {
float distance = (pos - enemy_pos).Length();
if (WWMath::Fabs(distance - effective_range) < best_distance) {
best_distance = WWMath::Fabs(distance - effective_range);
best_pos = pos;
}
}
}
if ( IsStationary ) {
best_pos = current_position;
}
SubStateString = "Random Run Attack";
ActionTimer = 10;
bool kneel = ( FreeRandom.Get_Float() < (1-Aggressiveness) );
Action_Attack_Object( soldier, enemy, weapon_range, kneel, best_pos, 0.5f );
}
}
/***********************************************************************************************
** Innate AI Actions
***********************************************************************************************/
void SoldierObserverClass::Action_Reset( SoldierGameObj * soldier )
{
WWASSERT( soldier != NULL );
soldier->Get_Action()->Reset( StatePriorities[ State ] );
}
void SoldierObserverClass::Action_Face_Location( SoldierGameObj * soldier, const Vector3 & location, SoldierAIState ai_state, bool crouched )
{
WWASSERT( soldier != NULL );
ActionParamsStruct parameters;
parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, ai_state );
parameters.Set_Face_Location( location, 2 );
parameters.MoveCrouched = crouched;
soldier->Get_Action()->Face_Location( parameters );
}
void SoldierObserverClass::Action_Goto_Location( SoldierGameObj * soldier, const Vector3 & location, SoldierAIState ai_state, float speed, float distance, bool crouched )
{
WWASSERT( soldier != NULL );
Vector3 move_position = location;
Stay_Within_Home( soldier, &move_position, &distance );
ActionParamsStruct parameters;
parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, ai_state );
parameters.Set_Movement( move_position, speed, distance, crouched );
soldier->Get_Action()->Goto( parameters );
}
void SoldierObserverClass::Action_Goto_Location_Facing( SoldierGameObj * soldier, const Vector3 & location, SoldierAIState ai_state, const Vector3 & facing_pos, float speed, float distance, bool crouched )
{
WWASSERT( soldier != NULL );
Vector3 move_position = location;
Stay_Within_Home( soldier, &move_position, &distance );
ActionParamsStruct parameters;
parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, ai_state );
parameters.Set_Movement( move_position, speed, distance, crouched );
parameters.ForceFacing = true;
parameters.FaceLocation = facing_pos;
soldier->Get_Action()->Goto( parameters );
}
void SoldierObserverClass::Action_Attack_Object( SoldierGameObj * soldier, PhysicalGameObj * enemy, float range,
bool kneel, const Vector3 & move_location, float arrived_distance )
{
WWASSERT( soldier != NULL );
Vector3 move_position = move_location;
Stay_Within_Home( soldier, &move_position, &arrived_distance );
if ( IsStationary ) { // Don't move if stationary
arrived_distance = 10000;
}
if ( enemy != NULL && soldier->Get_Weapon() != NULL ) {
ActionParamsStruct parameters;
parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, AI_STATE_COMBAT );
parameters.Set_Movement( move_position, RUN_SPEED, arrived_distance, kneel );
parameters.Set_Attack( enemy, range, FreeRandom.Get_Float(2), true );
parameters.AttackCrouched = kneel;
parameters.AttackWanderAllowed = !IsStationary;
// 50% chance of looking where you are running, for a bit
if ( FreeRandom.Get_Float() < 0.5 ) {
parameters.LookLocation = move_position + Vector3( 0,0,1 );
parameters.LookDuration = FreeRandom.Get_Float( 0.3f, 1 );
}
soldier->Get_Action()->Attack( parameters );
}
}
void SoldierObserverClass::Action_Dive( SoldierGameObj * soldier, const Vector3 & location )
{
WWASSERT( soldier != NULL );
ActionParamsStruct parameters;
parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, AI_STATE_SEARCH );
parameters.MoveLocation = location;
soldier->Get_Action()->Dive( parameters );
}
void SoldierObserverClass::Stay_Within_Home( SoldierGameObj * soldier, Vector3 * location, float * distance )
{
// Whenever we move, if we are outside our home radius, override the move to move back inside
Vector3 home_diff;
soldier->Get_Position( &home_diff );
home_diff -= HomeLocation;
home_diff.Z = 0; // don't consider Z
if ( home_diff.Length() > HomeRadius ) {
*location = HomeLocation;
*distance = HomeRadius;
}
}
bool SoldierObserverClass::Take_Cover( SoldierGameObj * soldier, bool force_face, const Vector3 & face_pos )
{
if ( CoverPosition != NULL ) {
if ( !CoverManager::Is_Cover_Safe( CoverPosition, AlertPosition ) ) {
Release_Cover_Position();
}
}
// Possibily take a cover spot
if ( FreeRandom.Get_Float() < TakeCoverProbability ) {
Release_Cover_Position(); // Give us the option of selecting our current spot
float cover_range = 20;
Vector3 current_position;
soldier->Get_Position(¤t_position);
CoverEntryClass * cover = CoverManager::Request_Cover(current_position, AlertPosition, cover_range); // Can we find a cover spot?
if (cover != NULL) { // Yes, take it
SubStateString = "Take Cover";
CoverPosition = cover;
Vector3 cover_position = cover->Get_Transform().Get_Translation();
if ( force_face ) {
Action_Goto_Location_Facing(soldier, cover_position, AI_STATE_SEARCH, face_pos, ALERTED_RUN_SPEED, 0.5, cover->Get_Crouch() );
} else {
Action_Goto_Location(soldier, cover_position, AI_STATE_SEARCH, ALERTED_RUN_SPEED, 0.5, cover->Get_Crouch() );
}
return true;
}
}
return false;
}
void SoldierObserverClass::Look_Random( SoldierGameObj * soldier, float duration )
{
soldier->Look_Random( duration );
}