Files
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
Complete disaster-recovery snapshot: engine/game source, game data assets,
VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive.
Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false,
no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2026-06-24 21:28:16 -05:00

5063 lines
119 KiB
C++

//===========================================================================//
// File: CombatAI.cpp //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 09/21/99 PDT Created File //
// Question: why is this even here when we have SourceSafe? ... //
//---------------------------------------------------------------------------//
// Copyright (C) 1999, Microsoft //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#include "mw4headers.hpp"
#include <Adept\AdeptHeaders.hpp>
#include "gameinfo.hpp"
#include "mw4.hpp"
#include "combatai.hpp"
#include "VehicleInterface.hpp"
#include <Adept\Application.hpp>
#include "MWPlayer.hpp"
#include "MWDamageObject.hpp"
#include "weapon.hpp"
#include "AI_Moods.hpp"
#include "Airplane.hpp"
#include "Mech.hpp"
#include <Adept\Map.hpp>
#pragma warning (push)
#include <stlport\stack>
#pragma warning (pop)
#include "MWMission.hpp"
#include "AI_Weapons.hpp"
#include "AI_Groups.hpp"
#include "Sensor.hpp" // temporary
#include "AI_Graveyard.hpp"
#include "AI_FireStyle.hpp"
#include "HeatManager.hpp"
#include "Building.hpp"
#include "MWEntityManager.hpp"
#include <Adept\CollisionVolume.hpp>
#include "MWDebugHelper.hpp"
#include "AI_Damage.hpp"
#include "LongTomWeapon.hpp"
#include "AI_Statistics.hpp"
#include "Narc.hpp"
#include "SubsystemClassData.hpp"
#include <Adept\Site.hpp> // TODO: remove -- unnecessary
using namespace MW4AI;
using namespace MechWarrior4;
const Stuff::Scalar offset_size__to_hit = 0.4f;
const Stuff::Scalar fire_offset_multiplier__to_hit = 1.2f;
const Stuff::Scalar min_speed_kph_considered_moving_quickly = 50.0f;
const Stuff::Scalar friendly_fire_multiplier = 2.5f;
const Stuff::Scalar bad_area_radius = 20.0f;
const Stuff::Scalar mission_fog_margin_multiplier = 0.9f;
const Stuff::Scalar min_node_radius_used_for_attack_interval = 80.0f;
const Stuff::Scalar auto_full_throttle_range = 250.0f;
const Stuff::Scalar initial_friendly_fire_delay = 2.0f;
const Stuff::Scalar max_friendly_fire_delay = 8.0f;
const Stuff::Scalar being_targeted_dissipate_time = 1.0f;
const Stuff::Scalar torso_centering_duration = 5.0f;
const Stuff::Scalar look_decay_time = 3.0f;
const Stuff::Scalar miss_offset_usage_limit = 3.5;
const Stuff::Scalar can_see_result_refresh_time = 2.0f;
const Stuff::Scalar max_miss_offset_distance = 8.0f;
const Stuff::Scalar multi_torso_raycast_failure_refresh = 3.2f;
const Stuff::Scalar no_path_warning_remove_time = 10.0f;
const Stuff::Scalar min_target_switch_time = 12.0f;
const Stuff::Scalar target_switch_score_multiplier = 0.7f;
const Stuff::Scalar target_switch_shot_by_multiplier = 1.5f;
const Stuff::Scalar min_squared_distance_check_angle_to_miss = 80.0f * 80.0f;
const Stuff::Scalar min_attack_interval_update = 5.0f;
const Stuff::Scalar max_fire_source_cache_refresh = 2.0f;
const Stuff::Scalar nearest_enemy_refresh = 4.0f;
const Stuff::Scalar min_move_request_wait = 2.5f;
const int fire_attempts__to_hit = 4;
const int max_fire_attempts__to_miss = 7;
const int max_fire_attempts__to_hit = 2;
const int max_fire_attempts__to_hit_total = 8;
const int max_blocked_move_points_size = 12;
const int max_most_recent_tactics_list_size = 4;
const int max_opportunity_fire_attempts = 2;
__int64 tCombatAITime;
__int64 tCombatAITime_UpdateSquad;
__int64 tCombatAITime_UpdateMovement;
__int64 tCombatAITime_UpdateAttacking;
__int64 tCombatAITime_UpdateTactic;
__int64 tCombatAITime_ShotWithin;
__int64 tCombatAITime_UpdateFiring;
__int64 tCombatAITime_RandomExtraneousCrap;
__int64 tCombatAITime_Firing_OpportunityFire;
__int64 tCombatAITime_FireWithCurrentQuery;
__int64 tCombatAITime_NotifyShot;
__int64 tCombatAITime_SituationalAnalysis;
__int64 tCombatAITime_SituationalAnalysis_Generate;
__int64 tCombatAITime_SituationalAnalysis_Evaluate;
__int64 tCombatAITime_SituationalAnalysis_PointRemoval;
__int64 tCombatAITime_FireToHit;
__int64 tCombatAITime_FireToMiss;
__int64 tCombatAITime_WastedFire;
__int64 tCombatAITime_WeaponFiring;
__int64 tCombatAITime_Track;
__int64 tCombatAITime_TorsoTwisting;
__int64 tCombatAITime_GetNearest;
__int64 tCombatAITime_FindObject;
__int64 tCombatAITime_CanSee;
__int64 tWeaponFiring;
__int64 tCombatAITime_RayCasting;
__int64 tRayCasting;
int tFireToMissRetries;
Stuff::Scalar GetFogDistance()
{
if (MWMission::GetInstance() == 0)
{
return (100000);
}
const Adept::Mission::GameModel* game_model = MWMission::GetInstance()->GetGameModel();
return (game_model->m_generalFogEnd * mission_fog_margin_multiplier);
}
bool CanSeeThroughFog(const Stuff::Point3D& point1, const Stuff::Point3D& point2, bool ignore_fog)
{
Stuff::Scalar fog_distance = 1000;
if (ignore_fog == false)
{
fog_distance = GetFogDistance();
}
return (GetApproximateLength(point1,point2) < fog_distance);
}
void GetSensorList(const MechWarrior4::Sensor::sensorDataArray& sensed_data,
std::vector<MWObject*>& objects)
{
{for (int i = 0;
i < sensed_data.GetLength();
++i)
{
MechWarrior4::SensorData* d = sensed_data[i];
if (d != 0)
{
Adept::Entity* current = d->object.GetCurrent();
if ((current != 0) &&
(current->IsDestroyed() == false) &&
(current->IsDerivedFrom(MWObject::DefaultData) == true))
{
objects.push_back(Cast_Object(MWObject*,current));
}
}
}}
}
CombatAI::PatienceMonitor::PatienceMonitor(int maximum)
: m_Maximum(maximum)
, m_Patience(0)
, m_Frustration(1)
{
}
bool CombatAI::PatienceMonitor::Check()
{
if (m_Frustration > 1)
{
--m_Frustration;
}
if (m_Patience > 0)
{
--m_Patience;
return (false);
}
return (true);
}
void CombatAI::PatienceMonitor::Notify(bool success)
{
if (success == true)
{
if (m_Frustration > 1)
{
--m_Frustration;
}
}
else
{
++m_Frustration;
m_Frustration *= 2;
m_Patience += m_Frustration;
if (m_Patience > m_Maximum)
{
m_Patience = m_Maximum;
}
if (m_Frustration > m_Maximum)
{
m_Frustration = m_Maximum;
}
}
}
//#############################################################################
//############################### CombatAI #############################
//#############################################################################
CombatAI::ClassData* CombatAI::DefaultData = NULL;
const Receiver::MessageEntry
CombatAI::MessageEntries[]=
{
MESSAGE_ENTRY(CombatAI, DebugText),
};
void CombatAI::InitializeClass()
{
Verify(!DefaultData);
DefaultData =
new ClassData(CombatAIClassID,
"MechWarrior4::CombatAI",
inherited::DefaultData,
ELEMENTS(MessageEntries), MessageEntries,
(Entity::Factory)Make,
(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
ExecutionStateEngine::DefaultData,
(Entity::GameModel::Factory)GameModel::ConstructGameModel,
NULL,
(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
(Entity::GameModel::ModelWrite)GameModel::WriteToText,
(Entity::GameModel::ModelSave)GameModel::SaveGameModel);
Register_Object(DefaultData);
#if !defined(NO_TIMERS)
AddStatistic("CombatAI", "%", gos_timedata, (void*)&tCombatAITime, 0);
AddStatistic("+- Extra crap", "%", gos_timedata, (void*)&tCombatAITime_RandomExtraneousCrap, 0);
AddStatistic("+- UpdateSquad", "%", gos_timedata, (void*)&tCombatAITime_UpdateSquad, 0);
AddStatistic("+- UpdateMovement", "%", gos_timedata, (void*)&tCombatAITime_UpdateMovement, 0);
AddStatistic("+- UpdateAttacking", "%", gos_timedata, (void*)&tCombatAITime_UpdateAttacking, 0);
AddStatistic(" +- Update Tactic", "%", gos_timedata, (void*)&tCombatAITime_UpdateTactic, 0);
AddStatistic(" +- ShotWithin", "%", gos_timedata, (void*)&tCombatAITime_ShotWithin, 0);
AddStatistic(" +- Firing", "%", gos_timedata, (void*)&tCombatAITime_UpdateFiring, 0);
AddStatistic(" +- Opportunity", "%", gos_timedata, (void*)&tCombatAITime_Firing_OpportunityFire, 0);
AddStatistic(" +- Fire-To-Hit", "%", gos_timedata, (void*)&tCombatAITime_FireToHit, 0);
AddStatistic(" +- Fire-To-Miss", "%", gos_timedata, (void*)&tCombatAITime_FireToMiss, 0);
AddStatistic(" +- Wasted Fire", "%", gos_timedata, (void*)&tCombatAITime_WastedFire, 0);
AddStatistic(" +- FireWithCurrentQuery", "%", gos_timedata, (void*)&tCombatAITime_FireWithCurrentQuery, 0);
AddStatistic(" +- Ray Casting", "%", gos_timedata, (void*)&tCombatAITime_RayCasting, 0);
AddStatistic(" +- Weapon Firing Time", "%", gos_timedata, (void*)&tCombatAITime_WeaponFiring, 0);
AddStatistic(" +- CanSee","%",gos_timedata, (void*)&tCombatAITime_CanSee, 0);
AddStatistic(" +- Track", "%", gos_timedata, (void*)&tCombatAITime_Track, 0);
AddStatistic(" +- Torso Twisting", "%", gos_timedata, (void*)&tCombatAITime_TorsoTwisting, 0);
AddStatistic(" +- GetNearest", "%", gos_timedata, (void*)&tCombatAITime_GetNearest, 0);
AddStatistic(" +- Situational Analysis", "%", gos_timedata, (void*)&tCombatAITime_SituationalAnalysis, 0);
AddStatistic(" +- Generate", "%", gos_timedata, (void*)&tCombatAITime_SituationalAnalysis_Generate, 0);
AddStatistic(" +- Evaluate", "%", gos_timedata, (void*)&tCombatAITime_SituationalAnalysis_Evaluate, 0);
AddStatistic(" +- Point Removal", "%", gos_timedata, (void*)&tCombatAITime_SituationalAnalysis_PointRemoval, 0);
AddStatistic("+- FindObject", "%", gos_timedata, (void*)&tCombatAITime_FindObject, 0);
AddStatistic("CombatAI Notify Shot", "%", gos_timedata, (void*)&tCombatAITime_NotifyShot, 0);
AddStatistic("Fire-To-Miss retries", "%", gos_DWORD, (void*)&tFireToMissRetries, Stat_AutoReset);
#endif
}
void CombatAI::TerminateClass()
{
Unregister_Object(DefaultData);
delete DefaultData;
DefaultData = NULL;
}
CombatAI* CombatAI::Make(CreateMessage *message,ReplicatorID *base_id)
{
Check_Object(message);
AutoHeap local_heap (g_CombatAIHeap);
CombatAI *new_entity = new CombatAI(DefaultData, message, base_id, NULL);
Check_Object(new_entity);
new_entity->SyncMatrices(true);
return new_entity;
}
CombatAI::CombatAI(ClassData *class_data,
CreateMessage *message,
ReplicatorID *base_id,
ElementRenderer::Element *element)
: inherited(class_data,message,base_id,element)
, m_LastAttackIntervalRefresh(0)
, m_Interface(*this)
, m_HitResult(FireData::HIT_NOTHING)
, m_OverridingMovement(false)
, m_AttackThrottle(100.0f)
, m_MinFiringDelay(0)
, m_MaxFiringDelay(0)
, m_LastTimeProjectileShotAtMe(0)
, m_ThrottleOverrideStopTime(0)
, m_HoldingFire(false)
, m_NumArmsBlownOff(0)
, m_NumLegsBlownOff(0)
, m_InternalArmorBreached(false)
, m_LastUnableToFireTime(0)
, m_LastFireTime(0)
, m_ForceDestinationRecalc(false)
, m_ApproximateTimeBetweenFrames(0)
, m_LastFrameTime(0)
, m_LastFriendlyFireTime(0)
, m_FriendlyFireDelay(0)
, m_LastTimeTargeted(0)
, m_TimeBeingTargeted(0)
, m_LastTimeRammedTarget(0)
, m_LastTimeJumped(0)
, m_SquadTargetingRadius(0)
, m_ExplicitFireStretch(0)
, m_WaitingForHeatToReachMinSkill(false)
, m_SearchLightController(*this)
, m_LookState(LOOK_CENTER)
, m_LastLookTime((Stuff::Scalar)gos_GetElapsedTime())
, m_LeftArmExists(true)
, m_RightArmExists(true)
, m_AttackRadiusMultiplier(0)
, m_LastOffsetToMiss(0,0,0)
, m_MissOffsetStartTime(0)
, m_JumpDuration(0)
, m_Last_Till_Value(0)
, m_CheapShotTarget(-2)
, m_CheapShotShouldHit(false)
, m_CheapShotShouldSpendAmmo(true)
, m_LastTimeMoved((Stuff::Scalar)gos_GetElapsedTime())
, m_PerWeaponRayCasting(false)
, m_LastTimeSwitchedTargets((Stuff::Scalar)gos_GetElapsedTime())
, m_AutoTargetingEnabled(true)
, m_LastTimeLookChanged(0)
, m_MinSpeed(0)
, m_GoalPitch(0)
, m_GoalYaw(0)
, m_MinAttackInterval(0)
, m_MaxAttackInterval(0)
, m_CurrentVulnerableComponent(0)
, m_FiringAttemptMonitor(12)
, m_OpportunityFireMonitor(12)
, m_LastAttackOrderDamageRating(0)
, m_ShutDownDueToHeat(false)
, m_MoveRequestFinishedTime(0)
, m_IgnoreFog(false)
{
AutoHeap local_heap (g_CombatAIHeap);
Check_Pointer(this);
Check_Object(message);
if (Network::GetInstance()->AmIServer())
{
m_LastTargetedAlignment = GetSelf().GetAlignment();
m_LastPos = (Point3D)GetSelf().GetLocalToWorld();
m_LastMoveDest.Zero();
m_LastProjectileOrigin.Zero();
m_ExplicitFirePoint.Zero();
m_LastAimPoint.Zero();
m_CachedNearest[0] = -2;
m_CachedNearestTime[0] = 0;
m_CachedNearest[1] = -2;
m_CachedNearestTime[2] = 0;
UpdateFrameTiming();
Tactics::Registrar::IncrementRefCount(m_Interface);
if (UserConstants::Instance() != 0)
{
m_SquadTargetingRadius = UserConstants::Instance()->Get(UserConstants::lancemate_can_attack_radius);
}
if (IsLaserTurret() == true)
{
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(GetSelf().weaponChain));
MechWarrior4::Weapon* weapon = 0;
while ((weapon = i.ReadAndNext()) != 0)
{
weapon->SetNonrandomWaitTimes(true);
}
}
}
#ifdef LAB_ONLY
m_LastFireRejected = false;
#endif
}
CombatAI::~CombatAI()
{
AutoHeap local_heap (g_CombatAIHeap);
if (Network::GetInstance()->AmIServer())
{
Tactics::Registrar::DecrementRefCount();
}
}
void CombatAI::DebugTextMessageHandler(Adept::ReceiverDataMessageOf<int> *message)
{
Check_Object(this);
Check_Object(message);
Verify(message->messageID == DebugTextMessageID);
}
void CombatAI::TestInstance() const
{
Verify(IsDerivedFrom(DefaultData));
}
void CombatAI::Reset(bool reset_movement)
{
AutoHeap local_heap (g_CombatAIHeap);
if (GetAttackState() == ATTACKING)
{
StopAttacking();
}
Target(0);
if (reset_movement == true)
{
StopMoving();
}
}
void CombatAI::OrderAttack(bool fOverrideCurrentMove)
{
AutoHeap local_heap (g_CombatAIHeap);
if (Target() == 0)
{
return;
}
if ((m_Tactic.GetPointer() != 0) &&
(m_Tactic->GetExplicit() == false))
{
return;
}
OrderAttackTactic(Tactics::Registrar::GetInstance().SelectTactic(m_Interface,m_MostRecentTactics),fOverrideCurrentMove,false);
}
void CombatAI::OrderAttackTactic(Tactics::TacticID tactic, bool fOverrideCurrentMove, bool is_explicit)
{
AutoHeap local_heap (g_CombatAIHeap);
if (Target() == 0)
{
return;
}
if (GetAttackState() == ATTACKING)
{
if (m_Tactic->GetID() == tactic)
{
return;
}
if ((is_explicit == true) &&
(m_Tactic->GetIgnoreExplicitTactic() == tactic))
{
return;
}
}
if ((Target() != 0) &&
(CanAct() == true))
{
if ((GetAttackState() != ATTACKING) ||
(m_Tactic->GetID() != tactic))
{
m_LastAttackOrderPosition.Assimilate(new Stuff::LinearMatrix4D(GetSelf().GetLocalToWorld()));
m_LastTimeJumped = (Stuff::Scalar)gos_GetElapsedTime();
m_LastAttackOrderDamageRating = MW4AI::Damage::GetDamageRating(GetSelf());
if (GetAttackState() == ATTACKING)
{
StopAttacking();
}
StartAttacking(tactic,fOverrideCurrentMove,is_explicit);
}
}
else
{
if (GetAttackState() != NOT_ATTACKING)
{
StopAttacking();
}
}
}
void CombatAI::OrderStopAttacking()
{
AutoHeap local_heap (g_CombatAIHeap);
m_LastAttackOrderPosition.Delete();
StopAttacking();
}
void CombatAI::OrderShootPoint(const Stuff::Point3D& point, int stretch)
{
AutoHeap local_heap (g_CombatAIHeap);
if (CanAct() == true)
{
m_LastAttackOrderPosition.Assimilate(new Stuff::LinearMatrix4D(GetSelf().GetLocalToWorld()));
if (GetAttackState() != NOT_ATTACKING)
{
StopAttacking();
}
m_ExplicitFirePoint = point;
m_ExplicitFireStretch = stretch;
}
}
void CombatAI::OrderJump(Stuff::Scalar duration)
{
AutoHeap local_heap (g_CombatAIHeap);
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == false)
{
return;
}
Mech* mech = Cast_Object(Mech*,&GetSelf());
Check_Object(mech);
if (mech->GetJumpJet() == 0)
{
return;
}
mech->JumpRequest();
m_LastTimeJumped = (Stuff::Scalar)gos_GetElapsedTime();
m_JumpDuration = duration;
}
void CombatAI::SetFallDampingEnabled(bool enabled)
{
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == false)
{
return;
}
Mech* mech = Cast_Object(Mech*,&GetSelf());
if (enabled == true)
{
mech->EnableFallDamping((Stuff::Point3D)Target()->GetLocalToWorld());
}
else
{
mech->DisableFallDamping();
}
}
void CombatAI::OrderJump()
{
AutoHeap local_heap (g_CombatAIHeap);
// OrderJump(0.8f);
if (RequestJump(m_Last_Till_Value) == true)
{
m_LastTimeJumped = (Stuff::Scalar)gos_GetElapsedTime();
m_JumpDuration = 1.0f;
}
}
void CombatAI::OrderStopJumping()
{
m_JumpDuration = 0;
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == true)
{
Mech* mech = Cast_Object(Mech*,&GetSelf());
Check_Object(mech);
mech->StopJumpRequest();
}
}
void CombatAI::UpdateJumping()
{
AutoHeap local_heap (g_CombatAIHeap);
if (m_JumpDuration == 0)
{
return;
}
if ((m_LastTimeJumped + m_JumpDuration < gos_GetElapsedTime()) &&
(GetSelf().IsDerivedFrom(Mech::DefaultData) == true))
{
Mech* mech = Cast_Object(Mech*,&GetSelf());
Check_Object(mech);
mech->StopJumpRequest();
m_JumpDuration = 0;
}
}
void CombatAI::Update(Stuff::Time till)
{
AutoHeap local_heap (g_CombatAIHeap);
#ifdef LAB_ONLY
ThisIsAMethodToSetABreakpointInIfCurrentVehicle();
#endif
COMBAT_LOGIC("Update");
TIME_FUNCTION(tCombatAITime);
m_Last_Till_Value = (Stuff::Scalar)till;
#ifdef LAB_ONLY
if (MWMission::GetInstance() != 0)
{
DiagnosticsInterface::GetInstance().SetExecutingObject(&GetSelf());
}
#endif
if (GetSelf().IsDestroyed() == true)
{
return;
}
tWeaponFiring = 0;
tRayCasting = 0;
{
TIME_FUNCTION(tCombatAITime_RandomExtraneousCrap);
UpdateFrameTiming();
if (ShouldRun(true) == true)
{
UpdateSquad();
UpdateFriendlyFireTiming();
UpdateBeingTargeted();
UpdateWaitingForHeatToReachZero();
m_SearchLightController.Update();
UpdateLookState();
UpdateJumping();
UpdateNoPathWarnings();
UpdateGoalPitchAndYaw();
if (Target() == 0)
{
m_DistanceFromTargetSquared = 0;
}
else
{
Stuff::Point3D delta;
delta.Subtract((Stuff::Point3D)GetSelf().GetLocalToWorld(),
(Stuff::Point3D)GetTarget().GetLocalToWorld());
delta.y = 0;
m_DistanceFromTargetSquared = delta.GetLengthSquared();
}
}
}
UpdateMoving();
UpdateAttacking();
tCombatAITime -= tWeaponFiring;
#ifdef LAB_ONLY
if (MWMission::GetInstance() != 0)
{
DiagnosticsInterface::GetInstance().SetExecutingObject(0);
}
#endif
}
void CombatAI::PreCollisionExecute(Stuff::Time till)
{
extern int g_nMR;
if (g_nMR == 2)
return;
AI_LOGIC("Pre-Collision::CombatAI");
#if defined(LAB_ONLY)
if (!instanceName)
MWGameInfo::g_LastMWObject[0] = '\0';
else
{
strncpy(MWGameInfo::g_LastMWObject, instanceName, sizeof(MWGameInfo::g_LastMWObject)-1);
MWGameInfo::g_LastMWObject[sizeof(MWGameInfo::g_LastMWObject)-1] = '\0';
}
MWGameInfo::g_LastMWObjectPos = GetLocalToWorld();
#endif
Update(till);
inherited::PreCollisionExecute(till);
#ifdef LAB_ONLY
if ((CombatTacticInterface::g_DisableMovement == true) &&
(m_TimeNotMoving == 0))
{
StopMoving();
}
#endif
}
void CombatAI::PostCollisionExecute(Time till)
{
extern int g_nMR;
if (g_nMR == 2)
return;
AI_LOGIC("Post-Collision::CombatAI");
inherited::PostCollisionExecute(till);
if (m_CheapShotTarget != -2)
{
Adept::Entity* entity = NameTable::GetInstance()->FindData(m_CheapShotTarget);
if ((entity == 0) ||
(entity->IsDestroyed() == true) ||
(entity->IsDerivedFrom(MechWarrior4::MWObject::DefaultData) == false))
{
return;
}
MWObject* mwobject = Cast_Object(MWObject*,entity);
Check_Object(mwobject);
CheapShot(*mwobject,m_CheapShotShouldHit,m_CheapShotShouldSpendAmmo);
m_CheapShotTarget = -2;
}
}
void CombatAI::UpdateNoPathWarnings()
{
{for (std::vector<Stuff::Scalar>::iterator i = m_MostRecentNoPathWarnings.begin();
i != m_MostRecentNoPathWarnings.end();
++i)
{
if ((*i) + no_path_warning_remove_time < (Stuff::Scalar)gos_GetElapsedTime())
{
m_MostRecentNoPathWarnings.erase(i);
UpdateNoPathWarnings();
return;
}
}}
}
void CombatAI::UpdateSquad()
{
AutoHeap local_heap (g_CombatAIHeap);
COMBAT_LOGIC("Update Squad");
TIME_FUNCTION(tCombatAITime_UpdateSquad);
{for (MWObject::GroupList::const_iterator i = GetSelf().GetGroups().begin();
i != GetSelf().GetGroups().end();
++i)
{
gosREPORT((Adept::Mission::GetInstance() != 0),"The mission object does not exist");
gosREPORT((Adept::Mission::GetInstance()->IsDerivedFrom(MechWarrior4::MWMission::DefaultData) == true),"The mission object is not the correct type");
MechWarrior4::MWMission* mission = Cast_Object(MechWarrior4::MWMission*,Adept::Mission::GetInstance());
Check_Object(mission);
if (mission->GetGroupContainer().GroupExists(*i) == true)
{
Group& g = mission->GetGroupContainer().GetGroup(*i);
g.UpdateAI(*this);
}
}}
if (m_SquadOrders.GetPointer() != 0)
{
m_SquadOrders->Update();
}
}
void CombatAI::UpdateMoving()
{
COMBAT_LOGIC("Update Movement");
TIME_FUNCTION(tCombatAITime_UpdateMovement);
UpdateThrottleOverride();
Point3D pos(GetSelf().GetLocalToWorld());
if (m_LastPos == pos)
{
m_TimeNotMoving += (Stuff::Scalar)m_ApproximateTimeBetweenFrames;
}
else
{
m_TimeNotMoving = 0;
m_LastPos = pos;
m_LastTimeMoved = (Stuff::Scalar)gos_GetElapsedTime();
}
}
void CombatAI::UpdateAttacking()
{
COMBAT_LOGIC("Update Attacking");
TIME_FUNCTION(tCombatAITime_UpdateAttacking);
if (GetAttackState() == ATTACKING)
{
if ((Target() == 0) || // if we have no target, we're not attacking and we return
(CanAct() == false))
{
StopAttacking();
return;
}
if (Target()->IsDestroyed() == true)
{
StopAttacking();
Target(0);
return;
}
if (m_Tactic->ShouldAbandon(m_Interface) == true)
{
MW4AI::Tactics::TacticID tactic_to_ignore((MW4AI::Tactics::TacticID)0);
if (m_Tactic->ShouldAbandonImmediately() == true)
{
tactic_to_ignore = m_Tactic->GetID();
}
bool overriding_movement = m_OverridingMovement;
Stuff::Scalar last_unable_to_fire_time(m_LastUnableToFireTime);
StopAttacking();
StartAttacking(Tactics::Registrar::GetInstance().SelectTactic(m_Interface,m_MostRecentTactics),overriding_movement,false);
m_LastUnableToFireTime = last_unable_to_fire_time;
if (tactic_to_ignore != (MW4AI::Tactics::TacticID)0)
{
m_Tactic->SetIgnoreExplicitTactic(tactic_to_ignore);
}
}
if (ShouldRun(true) == true)
{
UpdateCurrentVulnerableComponent();
}
// TODO: change this elite skill level to its own user constant
{
TIME_FUNCTION(tCombatAITime_UpdateTactic);
m_Tactic->Update(m_Interface);
if (ShouldRun(true) == true)
{
m_Tactic->UpdateSatisfaction();
}
}
UpdateAutoTargeting();
}
else
{
if (m_ExplicitFirePoint.GetLengthSquared() != 0)
{
AttackExplicitFirePoint();
return;
}
if (Target() != 0)
{
TrackToPoint(m_LastAimPoint);
TurnToPoint(m_LastAimPoint);
return;
}
if ((m_GoalYaw == 0) &&
(m_GoalPitch == 0))
{
if (m_CurMove == 0)
{
CenterTorso();
}
else
{
if (m_CurMove->MoveType() != MOVE_LOOKOUT)
{
CenterTorso();
}
}
}
}
tCombatAITime_UpdateAttacking -= tWeaponFiring;
}
void CombatAI::AttackExplicitFirePoint()
{
AutoHeap local_heap (g_CombatAIHeap);
Verify(m_ExplicitFirePoint.GetLengthSquared() != 0);
if (m_TimeNotMoving == 0)
{
StopMoving();
}
m_LastAimPoint = m_ExplicitFirePoint;
Stuff::LinearMatrix4D my_los;
GetSelf().GetLineOfSight(my_los);
bool facing_point = MatrixFacesPoint(my_los,m_LastAimPoint,GetMaxFireCheatAngle());
if (facing_point == true)
{
OffsetTargetPoint(m_LastAimPoint,(Stuff::Scalar)m_ExplicitFireStretch);
}
TrackToPoint(m_LastAimPoint);
TurnToPoint(m_LastAimPoint);
if (facing_point == true)
{
FireStyles::MaximumFire maximum_fire;
FireAtAimPoint(maximum_fire,0,0,MW4AI::FIRE_FROM_ANYWHERE,my_los,0,0,true);
}
}
bool CombatAI::TryToFireAt(MW4AI::FireStyles::FireStyle& style, MWObject& who, bool should_hit)
{
if (m_Deactive == true)
{
m_CheapShotTarget = who.objectID;
m_CheapShotShouldHit = should_hit;
m_CheapShotShouldSpendAmmo = ShouldSpendAmmoAgainst(who,style);
return (true);
}
if (should_hit == true)
{
if (TryToHit(who,style) == true)
{
tCombatAITime_UpdateFiring -= tWeaponFiring;
return (true);
}
}
else
{
if (TryToMiss(who,style) == true)
{
tCombatAITime_UpdateFiring -= tWeaponFiring;
return (true);
}
}
return (false);
}
bool CombatAI::MightBeAbleToFireAt(MW4AI::FireStyles::FireStyle& style, MWObject& who)
{
m_SavedWeapons.clear();
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(GetSelf().weaponChain));
MechWarrior4::Weapon* weapon = 0;
while ((weapon = i.ReadAndNext()) != 0)
{
m_SavedWeapons.push_back(weapon);
}
if (GetWeaponsThatCanFire(m_SavedWeapons,
ShouldSpendAmmoAgainst(who,style),
m_MinFiringDelay,
m_MaxFiringDelay) == false)
{
return (false);
}
const Stuff::Point3D los(who.GetLineOfSight());
if (GetWeaponsThatCanFireAt(m_SavedWeapons,los) == false)
{
return (false);
}
return (GetWeaponsThatCanFireFrom(m_SavedWeapons,GetBestFireSource(who)));
}
void CombatAI::TryToFire(MW4AI::FireStyles::FireStyleID style_id)
{
AILOGFUNC("CombatAI::TryToFire");
if (Target() == 0)
{
return;
}
{
COMBAT_LOGIC("Update Attacking::Firing");
TIME_FUNCTION(tCombatAITime_UpdateFiring);
if ((GetHoldingFire() == true) ||
(ShouldHoldFireToAvoidFriendlyFire() == true) ||
(GetSelf().vehicleShutDown == true))
{
return;
}
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == true)
{
Mech* mech = Cast_Object(Mech*,&GetSelf());
mech->NullArms();
}
FireStyles::FireStyle* style = 0;
if (style_id == FireStyles::FIRE_MAXIMUM)
{
style = &m_MaximumFire;
}
else
{
style = &m_OpportunityFire;
}
if (GetSelf().GetTorsos().size() > 1)
{
MultiTurretFire(*style);
return;
}
SetLookState(*style);
if ((style->ShouldFireAtPrimaryTarget() == true) &&
(m_FiringAttemptMonitor.Check() == true) &&
(MightBeAbleToFireAt(*style,GetTarget()) == true))
{
if (((m_Tactic.GetPointer() != 0) && (m_Tactic->ShouldIgnoreLOS() == true)) ||
(CanSee(GetTarget(),GetMaxFireCheatAngle()) == true))
{
bool should_hit(ShotShouldHit(GetTarget()));
if (TryToFireAt(*style,GetTarget(),should_hit) == true)
{
m_FiringAttemptMonitor.Notify(true);
return;
}
m_FiringAttemptMonitor.Notify(false);
}
else
{
m_LastUnableToFireTime = 0;
}
}
else
{
m_LastUnableToFireTime = (Stuff::Scalar)gos_GetElapsedTime();
}
OpportunityFire(*style);
}
}
void CombatAI::OpportunityFire(MW4AI::FireStyles::FireStyle& style)
{
TIME_FUNCTION(tCombatAITime_Firing_OpportunityFire);
if (m_OpportunityFireMonitor.Check() == false)
{
return;
}
if ((CanOpportunityFire(style) == false) ||
(MW4AI::AnyWeaponReady(GetSelf(),m_MinFiringDelay,m_MaxFiringDelay) == false))
{
return;
}
m_SecondaryTargets.clear();
InitializeSecondaryTargets(m_SecondaryTargets);
if (m_SecondaryTargets.size() == 0)
{
return;
}
Stuff::LinearMatrix4D my_los;
GetSelf().GetLineOfSight(my_los);
int attempts = 0;
while (m_SecondaryTargets.size() > 0)
{
secondary_target_list::iterator target = SelectSecondaryTarget(m_SecondaryTargets);
Verify(target != m_SecondaryTargets.end());
MWObject& who = **target;
Check_Object(&who);
m_SecondaryTargets.erase(target);
if (MightBeAbleToFireAt(style,who) == true)
{
Stuff::Point3D mwobject_pos(who.GetLocalToWorld());
if ((MatrixFacesPoint(my_los,mwobject_pos,GetMaxFireCheatAngle()) == true) &&
(CanSeeThroughFog((Stuff::Point3D)my_los,mwobject_pos,GetIgnoreFog()) == true))
{
if (TryToFireAt(style,who,ShotShouldHit(who)) == true)
{
m_OpportunityFireMonitor.Notify(true);
return;
}
}
}
++attempts;
if (attempts >= max_opportunity_fire_attempts)
{
break;
}
}
m_OpportunityFireMonitor.Notify(false);
}
void AddVolumeToList(Adept::CollisionVolume& collision_volume, MWObject& who, std::list<CombatAI::AimPoint>& list_out)
{
OBB& obb = collision_volume.m_worldSpaceBounds;
CombatAI::AimPoint temp((Stuff::Point3D)obb.localToParent,&who);
list_out.push_back(temp);
Stuff::ChainIteratorOf<CollisionVolume*> i(&collision_volume.m_children);
CollisionVolume* volume = 0;
while ((volume = i.ReadAndNext()) != 0)
{
Check_Object(volume);
AddVolumeToList(*volume,who,list_out);
}
}
bool CombatAI::TryToHitAtPoints(MWObject& who, MW4AI::FireStyles::FireStyle& style, const std::list<CombatAI::AimPoint>& aim_points, bool can_hit_dead_components)
{
MW4AI::FireSource fire_source = GetBestFireSource(GetTarget());
Stuff::LinearMatrix4D source_matrix = GetFireSourceMatrix(fire_source);
int num_tries(0);
int num_times_hit_something_else(0);
const int max_hit_something_else(6);
{for (std::list<AimPoint>::const_iterator i = aim_points.begin();
i != aim_points.end();
++i)
{
Stuff::Scalar offset_size = offset_size__to_hit;
m_LastAimPoint = (*i).aim_point;
if (MatrixFacesPoint(source_matrix,m_LastAimPoint,GetMaxFireCheatAngle()) == false)
{
continue;
}
int max_points_searched = fire_attempts__to_hit;
if (IsMovingQuickly(who) == true)
{
max_points_searched = 1; // we want to hit the exact point when the target is moving fast
}
{for (int i_guesses = 0;
i_guesses < max_points_searched;
++i_guesses)
{
++num_tries;
FireData fire_data(GetSelf(),(Stuff::Point3D)source_matrix,m_LastAimPoint);
fire_data.Project();
switch (fire_data.GetHitResult(&who,can_hit_dead_components))
{
case FireData::HIT_TARGET:
{
m_HitResult = FireData::HIT_TARGET;
Fire(who,
(*i).entity,
fire_source,
source_matrix,
m_LastAimPoint,
style,
true);
return (true);
}
case FireData::HIT_SOMETHING_ELSE:
{
++num_times_hit_something_else;
if ((num_tries > max_hit_something_else) &&
(num_times_hit_something_else == num_tries))
{
return (false);
}
continue;
}
}
if (num_tries > max_fire_attempts__to_hit_total * fire_attempts__to_hit)
{
return (false);
}
if (i_guesses + 1 < max_points_searched)
{
m_LastAimPoint = (*i).aim_point;
OffsetTargetPoint(m_LastAimPoint,offset_size);
offset_size *= fire_offset_multiplier__to_hit;
}
}}
}}
return (false);
}
void AddDefaultAimPoints(MWObject& who, component_vector& components, std::list<CombatAI::AimPoint>& aim_points)
{
CollisionVolume* cv = who.GetSolidVolume();
if (cv == 0)
{
cv = who.GetHierarchicalVolume();
}
if (cv != 0)
{
LinearMatrix4D world_bounds;
world_bounds.Multiply(cv->m_localSpaceBounds.localToParent, who.GetLocalToWorld());
CombatAI::AimPoint temp((Stuff::Point3D)world_bounds,&who);
aim_points.push_back(temp);
}
if ((components.size() == 1) &&
(who.IsDerivedFrom(Vehicle::DefaultData) == true))
{
CombatAI::AimPoint temp2((Stuff::Point3D)who.GetLocalToWorld(),&who);
aim_points.push_back(temp2);
}
}
void CombatAI::GetAimPoints(MWObject& who, std::list<CombatAI::AimPoint>& aim_points)
{
component_vector components;
DamageObjectChainToVector(who.damageObjects,components);
if (components.size() < 2)
{
AddDefaultAimPoints(who,components,aim_points);
return;
}
do
{
Adept::DamageObject* best_next_component = PickComponent(who,components); // TODO: also use random, least damaged, etc. ...
if (best_next_component != 0)
{
AimPoint temp((Stuff::Point3D)best_next_component->parentEntity->GetLocalToWorld(),
best_next_component->parentEntity);
aim_points.push_back(temp);
component_vector::iterator found = std::find(components.begin(),components.end(),best_next_component);
Verify(found != components.end());
components.erase(found);
}
}
while (components.size() > 0);
if (aim_points.size() == 0)
{
AddDefaultAimPoints(who,components,aim_points);
}
}
bool CombatAI::TryToHit(MWObject& who, MW4AI::FireStyles::FireStyle& style)
{
AILOGFUNC("CombatAI::TryToHit");
#ifdef LAB_ONLY
__int64 startTime = GetCycles();
#endif
COMBAT_LOGIC("Update Attacking::Firing::To Hit");
TIME_FUNCTION(tCombatAITime_FireToHit);
Check_Object(&who);
std::list<CombatAI::AimPoint> aim_points;
GetAimPoints(who,aim_points);
if (TryToHitAtPoints(who,style,aim_points) == true)
{
#ifdef LAB_ONLY
if (m_LastFireRejected == true)
{
tCombatAITime_WastedFire += GetCycles() - startTime;
}
#endif
LOG_AND_RETURN(true);
}
if (who.GetHierarchicalVolume() != 0)
{
aim_points.clear();
CollisionVolume* collision_volume = who.GetHierarchicalVolume();
AddVolumeToList(*collision_volume,who,aim_points);
if (TryToHitAtPoints(who,style,aim_points,true) == true)
{
#ifdef LAB_ONLY
if (m_LastFireRejected == true)
{
tCombatAITime_WastedFire += GetCycles() - startTime;
}
#endif
LOG_AND_RETURN(true);
}
}
LOG_AND_RETURN(false);
}
Stuff::Point3D CombatAI::GetNewAimPoint_ToMiss(MWObject& who, const Stuff::Point3D& self_pos)
{
CollisionVolume* cv = who.GetSolidVolume();
if (cv == 0)
{
cv = who.GetHierarchicalVolume();
}
Stuff::Scalar radius(0);
Point3D center;
if (cv == 0)
{
radius = 15.0f * (1.0f + Stuff::Random::GetFraction());
center = who.GetLineOfSight();
}
else
{
radius = cv->m_worldSpaceBounds.sphereRadius * ((Stuff::Random::GetFraction() * 0.5f) + 1.0f);
center = (Point3D)cv->m_worldSpaceBounds.localToParent;
}
Stuff::Point3D delta;
delta.Subtract(center,self_pos);
if (Small_Enough(delta.GetLengthSquared()) == true)
{
return (center);
}
delta.Normalize(delta);
delta *= radius;
Stuff::Point3D rv(center);
if (Stuff::Random::GetFraction() < 0.5f)
{
rv.x -= delta.z;
rv.z += delta.x;
}
else
{
rv.x += delta.z;
rv.z -= delta.x;
}
rv.y += (Stuff::Random::GetFraction() - 0.5f) * (radius * 0.6f);
return (rv);
}
bool CombatAI::TryToMiss(MWObject& who, MW4AI::FireStyles::FireStyle& style)
{
AILOGFUNC("CombatAI::TryToMiss");
#ifdef LAB_ONLY
__int64 startTime = GetCycles();
#endif
COMBAT_LOGIC("Update Attacking::Firing::To Miss");
TIME_FUNCTION(tCombatAITime_FireToMiss);
int attempts = 0;
Stuff::Point3D self_pos(GetSelf().GetLocalToWorld());
Stuff::Point3D target_pos(who.GetLocalToWorld());
Stuff::Point3D weapon_center(MW4AI::GetWeaponCenter(GetSelf()));
bool check_shot_angle = GetLengthSquared(target_pos,self_pos) < min_squared_distance_check_angle_to_miss;
Stuff::Scalar cheat_angle = GetMaxFireCheatAngle();
m_LastAimPoint = GetNewAimPoint_ToMiss(who,self_pos);
MW4AI::FireSource fire_source = GetBestFireSource(who);
Stuff::LinearMatrix4D source_matrix(GetFireSourceMatrix(fire_source));
Stuff::Scalar min_line_length = GetApproximateLength(self_pos,target_pos) - max_miss_offset_distance;
while (1)
{
if ((attempts == 0) &&
(m_LastOffsetToMiss.GetLengthSquared() != 0))
{
m_LastAimPoint = target_pos;
m_LastAimPoint += m_LastOffsetToMiss;
}
else
{
m_LastOffsetToMiss.Subtract(m_LastAimPoint,target_pos);
}
if ((check_shot_angle == false) ||
(MatrixFacesPoint(source_matrix,m_LastAimPoint,cheat_angle) == true))
{
FireData fire_data(GetSelf(),(Stuff::Point3D)source_matrix,m_LastAimPoint);
fire_data.Project();
Scalar m = min_line_length;
if (attempts == max_fire_attempts__to_miss)
{
m -= 40.0f; // on last attempt, we ignore the line length (in cases where target cannot be missed without hitting something else)
}
if (fire_data.GetLine().m_length > m)
{
m_HitResult = fire_data.GetHitResult(0);
if (m_HitResult == FireData::HIT_TARGET)
{
m_HitResult = FireData::HIT_NOTHING; // TODO: m_HitResult seems to be getting used for two different things!
if ((attempts == 0) &&
(m_LastOffsetToMiss.GetLengthSquared() != 0))
{
if (m_MissOffsetStartTime == 0)
{
m_MissOffsetStartTime = (Stuff::Scalar)gos_GetElapsedTime();
}
else
{
if ((IsMovingQuickly(who) == true) ||
(m_MissOffsetStartTime + miss_offset_usage_limit < gos_GetElapsedTime()))
{
m_MissOffsetStartTime = 0;
m_LastOffsetToMiss.Zero();
}
}
}
Fire(who,0,fire_source,source_matrix,m_LastAimPoint,style,false);
#ifdef LAB_ONLY
if (m_LastFireRejected == true)
{
tCombatAITime_WastedFire += GetCycles() - startTime;
}
#endif
tFireToMissRetries += attempts;
LOG_AND_RETURN(true);
}
}
}
m_LastAimPoint = GetNewAimPoint_ToMiss(who,self_pos);
m_MissOffsetStartTime = 0;
m_LastOffsetToMiss.Zero();
++attempts;
if (attempts > max_fire_attempts__to_miss)
{
tFireToMissRetries += attempts;
LOG_AND_RETURN(false);
}
}
}
bool CombatAI::Fire(MWObject& who,
Adept::Entity* component,
MW4AI::FireSource fire_source,
const Stuff::LinearMatrix4D& source_matrix,
const Stuff::Point3D& target_point,
MW4AI::FireStyles::FireStyle& style,
bool should_hit)
{
AILOGFUNC("CombatAI::Fire");
Check_Object(&who);
{
COMBAT_LOGIC("Update Attacking::Firing::With Current Query");
TIME_FUNCTION(tCombatAITime_FireWithCurrentQuery); // NOTE: you must have a valid current query coming in to this method!
Verify(m_HitResult != FireData::HIT_SOMETHING_ELSE);
m_LastUnableToFireTime = 0;
Vehicle* vehicle = 0;
if (who.IsDerivedFrom(Vehicle::DefaultData) == true)
{
vehicle = Cast_Object(Vehicle*,&who);
Check_Object(vehicle);
}
Adept::Entity* t = 0;
if (should_hit == true)
{
t = &who;
}
FireAtAimPoint(style,&who,component,fire_source,source_matrix,t,vehicle,ShouldSpendAmmoAgainst(who,style));
}
tCombatAITime_FireWithCurrentQuery -= tWeaponFiring;
return (true);
}
void CombatAI::FireAtAimPoint(MW4AI::FireStyles::FireStyle& style,
MWObject* who,
Adept::Entity* component,
MW4AI::FireSource fire_source,
const Stuff::LinearMatrix4D& source_matrix,
Adept::Entity* who_to_hit,
Vehicle* vehicle_shooting_at,
bool can_spend_ammo)
{
FireData fire_data(GetSelf(),
(Stuff::Point3D)source_matrix,
m_LastAimPoint);
Stuff::Scalar cur_heat = 0;
Stuff::Scalar max_heat = 100000;
HeatManager* hm = GetSelf().m_heatManager;
if (hm != 0)
{
cur_heat = hm->EstimateMaxCurrentHeat();
max_heat = hm->GetMaxHeat();
}
FireParamPackage params(fire_data,
who,
m_MinFiringDelay,
m_MaxFiringDelay,
who_to_hit,
vehicle_shooting_at,
component,
m_ApproximateTimeBetweenFrames,
cur_heat,
max_heat,
can_spend_ammo,
SuicideIsOK(),
fire_source,
CanFireNARC(),
CanFireHeatGeneratingWeapons(),
m_PerWeaponRayCasting);
if (LineMightHitFriendlyUnits(fire_data.GetLine()) == true)
{
#ifdef LAB_ONLY
m_LastFireRejected = true;
#endif
m_FiringAttemptMonitor.Notify(false);
return;
}
WeaponUpdate* weapon_update = new WeaponUpdate;
weapon_update->originator.Add(&(GetSelf()));
if (who_to_hit)
weapon_update->target.Add(who_to_hit);
else
weapon_update->target.Remove();
style.Execute(params,GetSelf().weaponChain,who_to_hit,*weapon_update);
if (weapon_update->weaponFired == 0)
{
m_FiringAttemptMonitor.Notify(false);
#ifdef LAB_ONLY
m_LastFireRejected = true;
#endif
}
#ifdef LAB_ONLY
else
{
m_LastFireRejected = false;
}
#endif
MWEntityManager* entity_manager = Cast_Object(MWEntityManager*,Adept::EntityManager::GetInstance());
entity_manager->AIWeaponUpdate(weapon_update);
m_LastFireTime = (Stuff::Scalar)gos_GetElapsedTime();
}
void CombatAI::MultiTurretFire(MW4AI::FireStyles::FireStyle& style)
{
secondary_target_list targets;
targets.push_back(&GetTarget());
InitializeSecondaryTargets(targets);
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(GetSelf().weaponChain));
MechWarrior4::Weapon* weapon = 0;
while ((weapon = i.ReadAndNext()) != 0)
{
if ((weapon->ReadyToFire() == true) &&
(weapon->GetGameModel() != 0))
{
bool skip = false;
{for (std::list<CachedTorsoRayCastFailure>::iterator i = m_CachedTorsoRayCastFailures.begin();
i != m_CachedTorsoRayCastFailures.end();
++i)
{
if ((*i).weapon == weapon)
{
if ((*i).time + multi_torso_raycast_failure_refresh > (Stuff::Scalar)gos_GetElapsedTime())
{
skip = true;
}
break;
}
}}
if (skip == true)
{
continue;
}
Stuff::LinearMatrix4D weapon_matrix(weapon->sitePointer->GetLocalToWorld());
Stuff::UnitVector3D unit_forward;
weapon_matrix.GetLocalForwardInWorld(&unit_forward);
Stuff::Line3D line((Stuff::Point3D)weapon_matrix,
unit_forward,
weapon->GetGameModel()->maxDistance);
MWObject* nearest = &GetTarget();
if (nearest == 0)
{
return;
}
bool should_hit(ShotShouldHit(*nearest));
MWObject* desired_hit_result = nearest;
Stuff::Point3D target_point;
if (should_hit == true)
{
component_vector components;
DamageObjectChainToVector(nearest->damageObjects,components);
Adept::DamageObject* damage_object = GetComponent_MostDamaged(*nearest,components);
if ((damage_object != 0) &&
(damage_object->parentEntity != 0))
{
target_point = damage_object->parentEntity->GetLocalToWorld();
}
else
{
target_point = nearest->GetLineOfSight();
}
}
else
{
Stuff::Point3D self_pos(GetSelf().GetLocalToWorld());
Stuff::Point3D target_pos(nearest->GetLocalToWorld());
target_point = GetNewAimPoint_ToMiss(*nearest,self_pos);
desired_hit_result = 0;
}
m_LastAimPoint = target_point;
if ((MW4AI::WeaponCanFire(*weapon,
GetApproximateLength(target_point,(Stuff::Point3D)weapon_matrix),
m_MinFiringDelay,
m_MaxFiringDelay) == true) &&
(MatrixFacesPoint(weapon_matrix,target_point,GetMaxFireCheatAngle()) == true))
{
Stuff::Point3D forward(unit_forward);
forward *= 4.0f;
forward += (Stuff::Point3D)weapon_matrix;
FireData fire_data(GetSelf(),*weapon,target_point);
fire_data.GetQuery().m_raySource = 0;
fire_data.GetLine().m_origin = forward;
fire_data.Project();
if (fire_data.GetHitResult(desired_hit_result) == MW4AI::FireData::HIT_TARGET)
{
if (should_hit == true)
{
MW4AI::SetRaySourceToBestComponent(fire_data.GetQuery());
}
else
{
fire_data.GetQuery().m_raySource = 0;
}
//weapon->FireWeapon(&fire_data.GetQuery(),100.0f,Stuff::Point3D::Identity);
MW4AI::FireSource fire_source = GetBestFireSource(GetTarget());
Stuff::LinearMatrix4D source_matrix = GetFireSourceMatrix(fire_source);
Fire(*nearest,
0,
fire_source,
source_matrix,
m_LastAimPoint,
style,
true);
}
else
{
std::list<CachedTorsoRayCastFailure>::iterator found = m_CachedTorsoRayCastFailures.end();
{for (std::list<CachedTorsoRayCastFailure>::iterator i = m_CachedTorsoRayCastFailures.begin();
i != m_CachedTorsoRayCastFailures.end();
++i)
{
if ((*i).weapon == weapon)
{
found = i;
break;
}
}}
if (found == m_CachedTorsoRayCastFailures.end())
{
CachedTorsoRayCastFailure ctrcf;
ctrcf.weapon = weapon;
ctrcf.time = (Stuff::Scalar)gos_GetElapsedTime() + Stuff::Random::GetFraction();
m_CachedTorsoRayCastFailures.push_back(ctrcf);
}
else
{
(*found).time = (Stuff::Scalar)gos_GetElapsedTime() + Stuff::Random::GetFraction();
}
}
}
}
}
}
void CombatAI::StartAttacking(Tactics::TacticID tactic, bool fOverrideCurrentMove, bool is_explicit)
{
Verify(GetAttackState() != ATTACKING);
Verify(Target() != 0);
while (m_MostRecentTactics.size() > max_most_recent_tactics_list_size)
{
m_MostRecentTactics.erase(m_MostRecentTactics.begin());
}
m_MostRecentTactics.push_back(tactic);
m_Tactic = Tactics::CreateTactic(m_Interface,tactic);
m_Tactic->SetExplicit(is_explicit);
m_OverridingMovement = fOverrideCurrentMove;
m_ExplicitFirePoint.Zero();
m_ExplicitFireStretch = 0;
m_LastUnableToFireTime = 0;
m_LastTimeSwitchedTargets = (Stuff::Scalar)gos_GetElapsedTime();
if (m_OverridingMovement == true)
{
StopMoving();
}
SetWeaponsToMaxWaitValue(GetSelf());
}
void CombatAI::StopAttacking()
{
// Verify(GetAttackState() != NOT_ATTACKING);
if (m_Tactic.GetPointer() != 0)
{
m_Tactic.Delete();
}
m_LastAimPoint.Zero();
m_ExplicitFirePoint.Zero();
m_ExplicitFireStretch = 0;
m_LastMoveOrderPosition.Delete();
m_LastUnableToFireTime = 0;
KillTracking();
m_OverridingMovement = false;
ResetTorso();
}
void CombatAI::AddStatsToString(std::string& s)
{
AutoHeap local_heap (g_CombatAIHeap);
MoverAI::AddStatsToString(s);
if (Target() != 0)
{
if (GetAttackState() == ATTACKING)
{
s += "ATTACKING\n";
s += "TACTIC: ";
s += m_Tactic->GetName();
{for (std::vector<CanSeeResult>::const_iterator i = m_CanSeeResults.begin();
i != m_CanSeeResults.end();
++i)
{
if ((*i).m_Entity == GetTarget().objectID)
{
if ((*i).m_Result == MW4AI::FireData::HIT_TARGET)
{
s += " CanSee: TRUE!";
}
else
{
s += " CanSee: false";
}
break;
}
}}
// s += " (";
// m_Tactic->AddStatsToString(m_Interface,s);
// s += ")";
// TODO: put some kind of diagnostics back in
}
else
{
s += "NOT ATTACKING";
}
}
s += "\nGunnery skill: ";
s += IntToString(GunnerySkill());
s += "\n";
}
Stuff::Scalar CombatAI::CalcTorsoTwistDemand(const Point3D& point, Torso& torso, bool for_multi_torso)
{
const Stuff::Scalar TWIST_LEFT = 1;
const Stuff::Scalar TWIST_RIGHT = -1;
Stuff::Scalar body_yaw;
if (for_multi_torso == true)
{
body_yaw = YawToPoint(torso.twistJoint->GetLocalToWorld(),point);
}
else
{
body_yaw = YawToPoint(GetSelf().GetLocalToWorld(),point);
}
const Stuff::Scalar twist_max(GetModifiedMaxTorsoTwistSpeed(torso));
// if we can rotate 360, or the point is in front of us, turn based on torso only
if (
(
(torso.GetGameModel() != 0) &&
(torso.GetGameModel()->twistRadius > (Pi - 0.02f))
)
||
(Stuff::Fabs(body_yaw) < (Pi_Over_2))
)
{
// special case for when we are twisted past 180 degrees
if ((Stuff::Fabs(body_yaw) < (Pi_Over_2)) &&
(Stuff::Fabs(torso.yawValue) > (Pi_Over_2)))
{
if (torso.yawValue < 0)
{
// MSL 5.05
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == true)
{
// MSL 5.00 - Added if ShutDown then TursoTwist is set to 0
if (m_ShutDownDueToHeat == true)
{
return (0);
}
}
return (TWIST_LEFT * twist_max);
}
return (TWIST_RIGHT * twist_max);
}
const Stuff::Scalar torso_yaw = YawToPoint(torso.GetTwistJointMatrix(),point);
Stuff::Scalar twist_rate = twist_max * Stuff::Fabs(torso_yaw);
Clamp(twist_rate,0,1);
// MSL 5.05
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == true)
{
// MSL 5.00 - Added if ShutDown then TursoTwist is set to 0
if (m_ShutDownDueToHeat == true)
{
return (0);
}
}
if (torso_yaw < 0)
{
return (TWIST_RIGHT * twist_rate);
}
return (TWIST_LEFT * twist_rate);
}
// otherwise, turn based on body
if (body_yaw > 0)
{
return (TWIST_LEFT * twist_max);
}
return (TWIST_RIGHT * twist_max);
}
Stuff::Scalar CombatAI::GetModifiedMaxTorsoTwistSpeed(Torso& torso)
{
Stuff::Scalar elite((Stuff::Scalar)EliteSkill());
Verify(UserConstants::Instance() != 0);
Stuff::Scalar max_speed(torso.GetTwistSpeed());
Stuff::Scalar min_skill(UserConstants::Instance()->Get(UserConstants::torso_twist_use_full_multiplier));
if (elite >= min_skill)
{
return (max_speed);
}
Stuff::Scalar normalized_skill(elite / min_skill);
Stuff::Scalar min_speed(UserConstants::Instance()->Get(UserConstants::torso_twist_multiplier_at_0));
min_speed *= max_speed;
return (min_speed + ((max_speed - min_speed) * normalized_skill));
}
bool CombatAI::CanTrack()
{
if ((GetSelf().GetTorso() == 0) ||
(CanAct() == false))
{
return (false);
}
if ((m_CurMove != 0) &&
(m_CurMove->MoveType() == MOVE_LOOKOUT))
{
return (false);
}
return (true);
}
void CombatAI::TrackToPoint(const Point3D& point)
{
Execute_TrackToPoint(point);
}
void CombatAI::Execute_TrackToPoint(const Point3D& point)
{
COMBAT_LOGIC("Update Attacking::Torso Twisting");
TIME_FUNCTION(tCombatAITime_TorsoTwisting);
if (CanTrack() == false)
{
return;
}
KillTracking();
Stuff::Point3D aim_point(point);
bool calc_independent_targets = false;
secondary_target_list targets;
if (GetSelf().GetTorsos().size() > 1)
{
calc_independent_targets = true;
targets.push_back(&GetTarget());
InitializeSecondaryTargets(targets);
}
{for (std::vector<Torso*>::const_iterator i = GetSelf().GetTorsos().begin();
i != GetSelf().GetTorsos().end();
++i)
{
Torso* torso = *i;
Check_Object(torso);
if (calc_independent_targets == true)
{
Stuff::Point3D joint_pos(torso->twistJoint->GetLocalToWorld());
Stuff::Point3D my_pos(GetSelf().GetLocalToWorld());
my_pos.y = joint_pos.y;
Stuff::Point3D diff;
diff.Subtract(joint_pos,my_pos);
diff.y = 0;
if (Small_Enough(diff.GetLengthSquared()) == true)
{
return;
}
Stuff::UnitVector3D direction(diff);
Stuff::Line3D line(joint_pos,
direction,
1000.0f);
MWObject* nearest_enemy = 0;
if ((Target() != 0) &&
(Target()->IsDerivedFrom(MWObject::DefaultData) == true))
{
nearest_enemy = Cast_Object(MWObject*,Target());
}
if (nearest_enemy != 0)
{
aim_point = (Stuff::Point3D)(nearest_enemy->GetLineOfSight());
}
}
Stuff::Point3D long_tom_pitch_point(GetLongTomPitchPoint());
if (long_tom_pitch_point.GetLengthSquared() != 0)
{
torso->PitchToPoint(long_tom_pitch_point);
}
else
{
torso->PitchToPoint(aim_point);
}
torso->yawDemand = CalcTorsoTwistDemand(aim_point,*torso,calc_independent_targets);
}}
}
bool CombatAI::CanTurn()
{
if ((CanAct() == false) ||
(CanMove() == false))
{
return (false);
}
if ((m_OverridingMovement == false) &&
(m_ExplicitFirePoint.GetLengthSquared() == 0))
{
return (false);
}
if (GetSelf().IsDerivedFrom(Airplane::DefaultData) == true)
{
return (true);
}
if ((GetSelf_AsVehicle() != 0) &&
(m_LastMoveDest == Vector3D(0,0,0)) &&
(m_TimeNotMoving > 0.5f))
{
if ((m_OverridingMovement == true) ||
(m_ExplicitFirePoint.GetLengthSquared() != 0))
{
if ((GetSelf_AsVehicle() == 0) ||
(Small_Enough(GetSelf_AsVehicle()->currentSpeedKPH) == true))
{
return (true);
}
}
}
return (false);
}
void CombatAI::TurnToPoint(const Point3D& point, bool must_be_torso_twisted)
{
if (CanTurn() == false)
{
return;
}
if (GetSelf().IsDerivedFrom(Airplane::DefaultData) == true)
{
Airplane* airplane = Cast_Object(Airplane*,&(GetSelf()));
if ((airplane->executionState->GetState() != Airplane::ExecutionStateEngine::NeverExecuteState) &&
(airplane->executionState->GetState() != Airplane::ExecutionStateEngine::AlwaysExecuteState) &&
(airplane->executionState->GetState() != Airplane::ExecutionStateEngine::TaxiState))
{
Execute_TurnToPoint(point);
}
return;
}
const Torso::GameModel *model = GetSelf().GetTorso()->GetGameModel();
Check_Object(model);
Stuff::Scalar twist_radius = model->twistRadius;
if (twist_radius > Pi_Over_4)
{
twist_radius = Pi_Over_4;
}
else
{
twist_radius *= 0.8f;
}
if ((must_be_torso_twisted == false) ||
(GetSelf().GetTorso() == 0))
{
Execute_TurnToPoint(point);
return;
}
if (Fabs(GetSelf().GetTorso()->yawValue) > twist_radius)
{
Execute_TurnToPoint(point);
}
}
void CombatAI::FacePointOrLookOut(const Stuff::Point3D& point)
{
Stuff::Scalar yaw(YawToPoint(GetSelf().GetLocalToWorld(),point));
if (Fabs(yaw) > 0.2f)
{
ClearMoveOrder();
Execute_TurnToPoint(point);
}
else
{
if (m_TimeNotMoving > 6.5f)
{
orderMoveLookOut();
}
}
}
void CombatAI::PitchToPoint(const Stuff::Point3D& point)
{
if (GetSelf().IsDerivedFrom(Airplane::DefaultData) == false)
{
return;
}
// TODO: this is the same code as in Torso::PitchToPoint ... please replace!
LinearMatrix4D pitch_matrix(GetSelf().GetLocalToWorld());
UnitVector3D unit_forward;
pitch_matrix.GetLocalForwardInWorld(&unit_forward);
Point3D projection(unit_forward);
Stuff::Vector3D delta_to_point;
delta_to_point.Subtract((Stuff::Point3D)pitch_matrix,point);
Stuff::Scalar distance = delta_to_point.GetApproximateLength();
projection *= distance;
projection += (Stuff::Point3D)pitch_matrix;
Stuff::Scalar pitch = Tan(Fabs(point.y - projection.y) / distance);
if (pitch > 1)
{
pitch = 1;
}
Airplane* plane = Cast_Object(Airplane*,&GetSelf());
plane->pitchDemand = (plane->GetGameModel()->pitchSpeed) * pitch;
if (point.y > projection.y)
{
plane->pitchDemand *= -1.0f;
}
}
bool CombatAI::ShotShouldHit(MWObject& target)
{
Stuff::Scalar chance_to_hit = GetBaseChanceToHit();
AddTowardGunnery ();
ModifyChanceToHit(chance_to_hit,target,GetSelf().GetLocalToWorld(),ApproximateDistanceToTarget());
if (chance_to_hit <= 0)
{
#ifdef LAB_ONLY
if (MW4AI::Statistics::Enabled() == true)
{
MW4AI::Statistics::NotifyToHitRoll(GetSelf().objectID,GetBaseChanceToHit(),0,0);
}
#endif
return (false);
}
const Stuff::Scalar fraction = Stuff::Random::GetFraction();
#ifdef LAB_ONLY
if (MW4AI::Statistics::Enabled() == true)
{
MW4AI::Statistics::NotifyToHitRoll(GetSelf().objectID,GetBaseChanceToHit(),chance_to_hit,fraction);
}
#endif
return (fraction < chance_to_hit);
}
void CombatAI::Info(void (*printCallback)(char* s))
{
AutoHeap local_heap (g_CombatAIHeap);
inherited::Info(printCallback);
std::string stats;
AddStatsToString(stats);
printCallback((char*)stats.c_str());
}
void CombatAI::GetDirectPath(std::vector<Stuff::Point3D>& points)
{
AutoHeap local_heap (g_CombatAIHeap);
/*
{for (std::vector<Torso*>::const_iterator i = GetSelf().GetTorsos().begin();
i != GetSelf().GetTorsos().end();
++i)
{
Torso* torso = *i;
Check_Object(torso);
Stuff::LinearMatrix4D matrix(torso->twistJoint->GetLocalToWorld());
points.push_back((Stuff::Point3D)matrix);
Stuff::UnitVector3D unit_forward;
matrix.GetLocalForwardInWorld(&unit_forward);
Stuff::Point3D forward(unit_forward);
forward *= 10;
forward += (Stuff::Point3D)matrix;
points.push_back(forward);
}}
*/
/*
// show line from GetLineOfSight()
points.push_back(GetSelf().GetLineOfSight());
Stuff::LinearMatrix4D matrix;
GetSelf().GetLineOfSight(matrix);
Stuff::UnitVector3D forward;
matrix.GetLocalForwardInWorld(&forward);
Stuff::Point3D f(forward);
f *= 10.0f;
f += GetSelf().GetLineOfSight();
points.push_back(f);
*/
// show combat ai dest
if (m_LastMoveDest == Vector3D(0,0,0))
{
return;
}
Stuff::Point3D start(GetSelf().GetLocalToWorld());
start.y += 3.0f;
points.push_back(start);
Stuff::Point3D end(m_LastMoveDest);
end.y = start.y;
points.push_back(end);
/*
// show airplane collision height lines
UnitVector3D unit_forward;
GetSelf().GetLocalToWorld().GetLocalForwardInWorld(&unit_forward);
Point3D forward(unit_forward);
forward *= 25.0f;
Point3D point(GetSelf().GetLocalToWorld());
point += forward;
point.y += 100.0f;
Point3D point2(GetSelf().GetLocalToWorld());
forward *= 4.0f;
point2 += forward;
point2.y += 100.0f;
UnitVector3D unit_left;
GetSelf().GetLocalToWorld().GetLocalLeftInWorld(&unit_left);
Point3D left(unit_left);
left *= 30.0f;
UnitVector3D unit_right;
GetSelf().GetLocalToWorld().GetLocalRightInWorld(&unit_right);
Point3D right(unit_right);
right *= 30.0f;
Point3D point3(point2);
point2 += left;
point3 += right;
points.push_back(point);
point.y -= 500;
points.push_back(point);
points.push_back(point2);
point2.y -= 500;
points.push_back(point2);
points.push_back(point3);
point3.y -= 500;
points.push_back(point3);
*/
}
void CombatAI::FloatToPoint(const Stuff::Point3D& point)
{
AutoHeap local_heap (g_CombatAIHeap);
if (GetSelf().IsDerivedFrom(Airplane::DefaultData) == false)
{
return;
}
StopMoving();
m_LastMoveDest = point;
m_ForceDestinationRecalc = false;
m_LastMoveOrderPosition.Assimilate(new Stuff::LinearMatrix4D(GetSelf().GetLocalToWorld()));
SetRamTarget(0);
Airplane* airplane = Cast_Object(Airplane*,&GetSelf());
Check_Object(airplane);
airplane->Float(point);
}
bool CombatAI::MoveRequestPending()
{
bool rv(false);
if ((m_CurMove == 0) ||
(m_CurMove->Done() == true))
{
rv = false;
m_MoveRequestFinishedTime = 0;
return (false);
}
else
{
if ((m_CurPath != 0) &&
(m_CurPath->Calced() == true))
{
if (m_CurPath->State () == CRailPath::RAIL_MOVE_WAITING)
{
rv = true;
}
else
{
rv = false;
}
}
else
{
rv = true;
}
}
if (rv == true)
{
m_MoveRequestFinishedTime = 0;
return (true);
}
if (m_MoveRequestFinishedTime == 0)
{
m_MoveRequestFinishedTime = gos_GetElapsedTime();
}
return (m_MoveRequestFinishedTime + min_move_request_wait > gos_GetElapsedTime());
}
void CombatAI::MoveToPoint(const Stuff::Point3D& point)
{
if (GetSelf_AsVehicle() == 0)
{
return;
}
if ((m_ForceDestinationRecalc == false) &&
(MoveRequestPending() == true))
{
return;
}
m_LastMoveDest = point;
m_ForceDestinationRecalc = false;
m_LastMoveOrderPosition.Assimilate(new Stuff::LinearMatrix4D(GetSelf().GetLocalToWorld()));
SetRamTarget(0);
orderMoveToLocPoint(point,
(int)MaxVehicleSpeed(),
true,
false);
}
void CombatAI::MoveToPoint(const Stuff::Point3D& point, bool move_forward, bool dont_use_throttle, MWObject* ram_target)
{
if (CanMove() == false)
{
return;
}
if ((m_ForceDestinationRecalc == false) &&
(MoveRequestPending() == true))
{
return;
}
SetRamTarget(ram_target);
m_LastMoveDest = point;
m_ForceDestinationRecalc = false;
Stuff::Scalar speed;
if (move_forward == false)
{
speed = -MaxVehicleSpeed();
}
else
{
if (dont_use_throttle == true)
{
speed = MaxVehicleSpeed();
}
else
{
speed = RecommendedCurrentSpeed();
}
}
m_LastMoveOrderPosition.Assimilate(new Stuff::LinearMatrix4D(GetSelf().GetLocalToWorld()));
orderMoveToLocPoint(point,
(int)speed,
move_forward,
false);
}
void CombatAI::NotifyProjectileApproaching(const Stuff::Point3D& origin)
{
m_LastTimeProjectileShotAtMe = (Stuff::Scalar)gos_GetElapsedTime();
m_LastProjectileOrigin = origin;
}
void CombatAI::NotifyInTargetingReticule(MWObject& who)
{
Check_Object(&who);
if (m_LastTimeTargeted != 0)
{
m_TimeBeingTargeted += ((Stuff::Scalar)gos_GetElapsedTime() - m_LastTimeTargeted);
}
m_LastTimeTargeted = (Stuff::Scalar)gos_GetElapsedTime();
m_LastTargetedAlignment = who.GetAlignment();
}
void CombatAI::NotifyHeatShutdownImminent()
{
inherited::NotifyHeatShutdownImminent();
m_WaitingForHeatToReachMinSkill = true;
ModifyMood(UserConstants::Instance()->Get(UserConstants::shutdown));
m_ShutDownDueToHeat = true;
m_UsingCoolant = true;
GetSelf().SetCooling(1);
}
void CombatAI::NotifyFriendlyFire(Adept::Entity& who_shot_me)
{
inherited::NotifyFriendlyFire(who_shot_me);
if (m_SquadOrders.GetPointer() != 0)
{
m_SquadOrders->NotifyFriendlyFire(who_shot_me);
}
}
void CombatAI::UpdateWaitingForHeatToReachZero()
{
if (m_ShutDownDueToHeat == true)
{
if (GetSelf().vehicleShutDown == false)
{
m_ShutDownDueToHeat = false;
}
else
{
GetSelf().ShutDownRequest(false);
}
}
if (m_WaitingForHeatToReachMinSkill == true)
{
bool stop_waiting(false);
HeatManager* hm = GetSelf().m_heatManager;
if (hm != 0)
{
if (GetNormalizedCurrentHeat() <= ((Stuff::Scalar)MinHeatSkill() / 100.0f) + 0.01f)
{
stop_waiting = true;
}
}
else
{
stop_waiting = true;
}
bool turn_off_coolant(false);
if (stop_waiting == true)
{
m_WaitingForHeatToReachMinSkill = false;
turn_off_coolant = true;
}
else
{
if ((GetUnableToFireDuration(1.0f) == true) ||
(Target() == 0) ||
(GetAttackState() == NOT_ATTACKING) ||
(Target()->IsDestroyed() == true))
{
turn_off_coolant = true;
}
}
if ((turn_off_coolant == true) &&
(m_UsingCoolant == true))
{
m_UsingCoolant = false;
GetSelf().SetCooling(0);
}
}
else
{
if ((GetSelf().m_heatManager != 0) &&
(GetSelf().m_heatManager->GetHeat() > 0))
{
if (GetNormalizedCurrentHeat() >= ((Stuff::Scalar)MaxHeatSkill() / 100.0f) - 0.01f)
{
m_WaitingForHeatToReachMinSkill = true;
if ((m_UsingCoolant == false) &&
(GetSelf().m_heatManager != 0))
{
m_UsingCoolant = true;
GetSelf().SetCooling(1);
}
}
}
}
}
void CombatAI::UpdateBeingTargeted()
{
AutoHeap local_heap (g_CombatAIHeap);
if (m_LastTimeTargeted == 0)
{
m_TimeBeingTargeted = 0;
}
else
{
if (m_LastTimeTargeted + being_targeted_dissipate_time < gos_GetElapsedTime())
{
m_LastTimeTargeted = 0;
m_TimeBeingTargeted = 0;
}
}
}
void CombatAI::SetSpeed(Stuff::Scalar speed)
{
AutoHeap local_heap (g_CombatAIHeap);
if (CanMove() == false)
{
return;
}
CommandEntry* ce = CreateSetSpeedCommand(speed);
Check_Pointer(ce);
ExecuteCommand(ce);
}
void CombatAI::ThrottleOverride(Stuff::Scalar duration, bool force)
{
Verify(UserConstants::Instance() != 0);
Stuff::Scalar elite((Stuff::Scalar)EliteSkill());
if ((force == false) &&
(elite < UserConstants::Instance()->Get(UserConstants::min_throttle_override)))
{
return;
}
m_ThrottleOverrideStopTime = (Stuff::Scalar)gos_GetElapsedTime() + duration;
SetSpeed(1.0f);
}
void CombatAI::UpdateThrottleOverride()
{
if (m_ThrottleOverrideStopTime == 0)
{
return;
}
if (m_ThrottleOverrideStopTime < gos_GetElapsedTime())
{
m_ThrottleOverrideStopTime = 0;
SetSpeed(RecommendedCurrentSpeed()/MaxVehicleSpeed());
return;
}
}
Stuff::Scalar CombatAI::MaxVehicleSpeed()
{
const Vehicle__GameModel* vehicle_model = static_cast<const Vehicle__GameModel*>(GetSelf_AsVehicle()->GetGameModel());
Check_Object(vehicle_model);
return (vehicle_model->maxSpeed);
}
Stuff::Scalar CombatAI::RecommendedCurrentSpeed()
{
if ((Target() != 0) &&
(GetLengthSquared((Stuff::Point3D)GetSelf().GetLocalToWorld(),
(Stuff::Point3D)GetTarget().GetLocalToWorld()) < (auto_full_throttle_range * auto_full_throttle_range)))
{
return ((MaxVehicleSpeed() * m_AttackThrottle) / 100);
}
if ((Stuff::Scalar)EliteSkill() < UserConstants::Instance()->Get(UserConstants::min_throttle_override))
{
return ((MaxVehicleSpeed() * m_AttackThrottle) / 100);
}
return (MaxVehicleSpeed());
}
void CombatAI::NotifyShot(ObjectID who, bool fCanBeRecursive)
{
AutoHeap local_heap (g_CombatAIHeap);
inherited::NotifyShot(who,false);
if (fCanBeRecursive == true)
{
Adept::Entity* entity = NameTable::GetInstance()->FindData(who);
if ((entity != 0) &&
(entity->IsDestroyed() == false) &&
(entity != getEntity()) &&
(getEntity() != 0) &&
(entity->GetAlignment() != getEntity()->GetAlignment()))
{
Verify(Adept::Mission::GetInstance() != 0);
Verify(Adept::Mission::GetInstance()->IsDerivedFrom(MechWarrior4::MWMission::DefaultData) == true);
MechWarrior4::MWMission* mission = Cast_Object(MechWarrior4::MWMission*,Adept::Mission::GetInstance());
Check_Object(mission);
GroupContainer& gc = mission->GetGroupContainer();
{for (MWObject::GroupList::iterator i = GetSelf().GetGroups().begin();
i != GetSelf().GetGroups().end();
++i)
{
if (gc.GroupExists(*i) == true)
{
Group& group = gc.GetGroup(*i);
group.NotifyShot(who);
}
}}
}
}
}
void CombatAI::NotifyLastShotWasFriendlyFire(Adept::Entity& who_i_hit)
{
AutoHeap local_heap (g_CombatAIHeap);
Check_Object(&who_i_hit);
if (Target() == 0)
{
return;
}
if (who_i_hit.objectID != GetSelf().objectID)
{
if (GetTarget().objectID == who_i_hit.objectID)
{
if ((m_Tactic.GetPointer() == 0) ||
(m_Tactic->GetID() != MW4AI::Tactics::TACTIC_DEFEND))
{
return;
}
}
if ((m_LastFriendlyFireTime != 0) &&
(m_LastFriendlyFireTime == (Stuff::Scalar)gos_GetElapsedTime()))
{
return;
}
if (m_FriendlyFireDelay <= 0)
{
m_FriendlyFireDelay = initial_friendly_fire_delay;
}
else
{
m_FriendlyFireDelay *= friendly_fire_multiplier;
}
m_LastFriendlyFireTime = (Stuff::Scalar)gos_GetElapsedTime();
}
}
void CombatAI::UpdateFriendlyFireTiming()
{
if (m_FriendlyFireDelay != 0)
{
if ((m_FriendlyFireDelay < 0) ||
(m_FriendlyFireDelay > max_friendly_fire_delay))
{
m_FriendlyFireDelay = max_friendly_fire_delay;
}
if (gos_GetElapsedTime() > m_LastFriendlyFireTime + (m_FriendlyFireDelay * 2))
{
m_LastFriendlyFireTime = 0;
m_FriendlyFireDelay = 0;
}
}
}
void CombatAI::NotifyAlignmentChanged()
{
AutoHeap local_heap (g_CombatAIHeap);
if (Target() == 0)
{
return;
}
if ((m_Tactic.GetPointer() != 0) &&
(m_Tactic->GetID() == MW4AI::Tactics::TACTIC_DEFEND)) // TODO: clean this up -- should not have to match by ID
{
if (GetTarget().GetAlignment() != GetSelf().GetAlignment())
{
Reset();
}
}
else
{
if (GetTarget().GetAlignment() == GetSelf().GetAlignment())
{
Reset();
}
}
}
void CombatAI::EnsureNotTargeting(Adept::ObjectID object)
{
AutoHeap local_heap (g_CombatAIHeap);
if ((Target() != 0) &&
(GetTarget().objectID == object))
{
Reset();
}
}
bool CombatAI::PointIsValid(const Point3D& point, bool ignore_mission_bounds, bool ignore_squads) const
{
AutoHeap local_heap (g_CombatAIHeap);
if (IsDerivedFrom(MechWarrior4::Airplane::DefaultData) == true)
{
return (true);
}
if ((point.x <= MinX) ||
(point.x >= MaxX) ||
(point.z <= MinZ) ||
(point.z >= MaxZ))
{
return (false);
}
Verify(Adept::Mission::GetInstance() != 0);
Verify(Adept::Mission::GetInstance()->IsDerivedFrom(MechWarrior4::MWMission::DefaultData) == true);
MechWarrior4::MWMission* mission = Cast_Object(MechWarrior4::MWMission*,Adept::Mission::GetInstance());
if (ignore_mission_bounds == false)
{
if (mission->GetMissionArea(point) != MWMission::InMissionArea)
{
return (false);
}
}
if (m_CombatLeashRadius != 0)
{
if (GetLengthSquared(point,m_CombatLeash) > m_CombatLeashRadius * m_CombatLeashRadius)
{
return (false);
}
}
if (PointIsOK(point) == false)
{
return (false);
}
if ((ignore_squads == false) &&
(m_SquadOrders.GetPointer() != 0) &&
(m_SquadOrders->PointIsValid(point) == false))
{
return (false);
}
return (true);
}
void CombatAI::NotifyShotFired(const Adept::Entity::CollisionQuery& query, MWObject& shooter)
{
AutoHeap local_heap (g_CombatAIHeap);
COMBAT_LOGIC("NotifyShot");
TIME_FUNCTION(tCombatAITime_NotifyShot);
inherited::NotifyShotFired(query,shooter);
Verify(query.m_line != 0);
if (getEntity() == &shooter)
{
}
else
{
if ((GetSelf().GetGroups().size() > 0) &&
(shooter.GetGroups().size() > 0))
{
{for (MWObject::GroupList::const_iterator i = GetSelf().GetGroups().begin();
i != GetSelf().GetGroups().end();
++i)
{
MWObject::GroupList::const_iterator found = std::find(shooter.GetGroups().begin(),shooter.GetGroups().end(),*i);
if (found != shooter.GetGroups().end())
{
Group& group_we_are_both_in = Groups::GetGroup(*found);
group_we_are_both_in.NotifyShotFired(*(query.m_line),GetSelf(),shooter);
}
}}
}
}
}
void CombatAI::NotifyInternalHit()
{
AutoHeap local_heap (g_CombatAIHeap);
Verify(UserConstants::Instance() != 0);
inherited::NotifyInternalHit();
if (m_InternalArmorBreached == false)
{
ModifyMood(UserConstants::Instance()->Get(UserConstants::internal_armor_breached));
m_InternalArmorBreached = true;
}
}
void CombatAI::NotifyComponentDestroyed(int zone)
{
AutoHeap local_heap (g_CombatAIHeap);
Verify(m_NumArmsBlownOff >= 0);
Verify(m_NumArmsBlownOff <= 2);
Verify(m_NumLegsBlownOff >= 0);
Verify(m_NumLegsBlownOff <= 2);
Verify(UserConstants::Instance() != 0);
inherited::NotifyComponentDestroyed(zone);
Stuff::Scalar mood_modifier(0);
switch (zone)
{
case Adept::InternalDamageObject::LeftArmZone:
case Adept::InternalDamageObject::RightArmZone:
{
++m_NumArmsBlownOff;
Verify(m_NumArmsBlownOff <= 2);
if (m_NumArmsBlownOff == 1)
{
mood_modifier = UserConstants::Instance()->Get(UserConstants::first_arm_hit);
}
else
{
mood_modifier = UserConstants::Instance()->Get(UserConstants::second_arm_hit);
}
if (zone == Adept::InternalDamageObject::LeftArmZone)
{
m_LeftArmExists = false;
}
else
{
m_RightArmExists = false;
}
break;
}
case Adept::InternalDamageObject::LeftLegZone:
case Adept::InternalDamageObject::RightLegZone:
{
++m_NumLegsBlownOff;
Verify(m_NumLegsBlownOff <= 2);
mood_modifier = UserConstants::Instance()->Get(UserConstants::first_leg_hit);
break;
}
}
if (mood_modifier != 0)
{
ModifyMood(mood_modifier);
}
}
void CombatAI::NotifyFailedPilotingRoll()
{
Verify(UserConstants::Instance() != 0);
inherited::NotifyFailedPilotingRoll();
ModifyMood(UserConstants::Instance()->Get(UserConstants::fall_down));
}
void CombatAI::ModifyMood(Stuff::Scalar add)
{
Stuff::Scalar mood(CurrentMood() + add);
Clamp(mood,(Stuff::Scalar)MOODMIN,(Stuff::Scalar)MOODMAX);
CurrentMood(mood);
}
void CombatAI::ModifyChanceToHit(Stuff::Scalar& chance_to_hit, MWObject& target, const Stuff::LinearMatrix4D& my_matrix, Stuff::Scalar distance_from_target)
{
Verify(UserConstants::Instance() != 0);
bool fCrouched = false;
if ((target.animStateEngine != 0) &&
(target.animStateEngine->GetState() == MechAnimationStateEngine::CrouchState))
{
fCrouched = true;
}
chance_to_hit *= UserConstants::Instance()->GetMultiplier_Tonnage(target.GetTonage());
chance_to_hit *= UserConstants::Instance()->GetMultiplier_CrouchState(fCrouched);
Stuff::Scalar target_speed_kph(0);
Stuff::Scalar yaw_demand(0);
if (target.IsDerivedFrom(Vehicle::DefaultData) == true)
{
Vehicle* vehicle = Cast_Object(Vehicle*,&target);
Check_Object(vehicle);
target_speed_kph = Stuff::Fabs(vehicle->currentSpeedKPH);
yaw_demand = Stuff::Fabs(vehicle->yawDemand);
}
Stuff::Scalar my_speed_kph(0);
if (GetSelf().IsDerivedFrom(Vehicle::DefaultData) == true)
{
my_speed_kph = GetSelf_AsVehicle()->currentSpeedKPH;
}
if ((&target == &GetTarget()) &&
(m_LookState != LOOK_CENTER))
{
chance_to_hit *= UserConstants::Instance()->Get(UserConstants::if_firing_with_one_arm);
}
chance_to_hit *= UserConstants::Instance()->GetMultiplier_Speed(target_speed_kph);
chance_to_hit *= UserConstants::Instance()->GetMultiplier_ShooterSpeed(my_speed_kph);
chance_to_hit *= UserConstants::Instance()->GetMultiplier_Distance(distance_from_target);
chance_to_hit *= UserConstants::Instance()->GetMultiplier_Bearing((Stuff::Point3D)my_matrix,target.GetLocalToWorld(),target_speed_kph);
chance_to_hit *= UserConstants::Instance()->GetMultiplier_TurnRate(yaw_demand);
Stuff::Scalar fog_distance = 1000.0f;
if (GetIgnoreFog() == false)
{
fog_distance = GetFogDistance();
}
if (
(
(fog_distance <= UserConstants::Instance()->Get(UserConstants::max_fog_dist_to_use_blind_skill))
)
||
(
(Adept::Mission::GetInstance() != 0) &&
(Adept::Mission::GetInstance()->GetGameModel() != 0) &&
(Adept::Mission::GetInstance()->m_isNightMission == true)
)
)
{
chance_to_hit *= (((Stuff::Scalar)BlindFightingSkill()) * 0.01f);
}
switch (UserConstants::Instance()->FindCategory(distance_from_target,
UserConstants::distance_adjacent,
UserConstants::distance_very_far))
{
case UserConstants::distance_adjacent:
case UserConstants::distance_near:
case UserConstants::distance_medium:
{
chance_to_hit *= (((Stuff::Scalar)ShortRangeGunnerySkill()) * 0.01f);
break;
}
case UserConstants::distance_very_far:
{
chance_to_hit *= (((Stuff::Scalar)LongRangeGunnerySkill()) * 0.01f);
break;
}
}
}
void CombatAI::NotifyShutdown()
{
AutoHeap local_heap (g_CombatAIHeap);
inherited::NotifyShutdown();
}
void CombatAI::StopMoving()
{
AutoHeap local_heap (g_CombatAIHeap);
m_LastMoveDest.Zero();
m_LastMoveOrderPosition.Delete();
ClearMoveOrder();
}
void CombatAI::NotifyNoPath()
{
AutoHeap local_heap (g_CombatAIHeap);
// StopMoving();
MoverAI::NotifyNoPath ();
if (m_Tactic.GetPointer() != 0)
{
SuggestPointWasNotAGoodDestination(m_LastMoveDest);
m_ForceDestinationRecalc = true;
m_MostRecentNoPathWarnings.push_back((Stuff::Scalar)gos_GetElapsedTime());
}
else
{
if (m_SquadOrders.GetPointer() != 0)
{
m_SquadOrders->NotifyNoPath();
}
}
}
bool CombatAI::ReactToCollision(Stuff::DynamicArrayOf<CollisionData> *collisions,bool& shoulddamage)
{
AutoHeap local_heap (g_CombatAIHeap);
for (int i=0; i<collisions->GetLength(); ++i)
{
CollisionData *data = &(*collisions)[i];
if ((Target() != 0) &&
(data->m_otherEntity == Target()))
{
m_LastTimeRammedTarget = (Stuff::Scalar)gos_GetElapsedTime();
break;
}
}
return inherited::ReactToCollision(collisions,shoulddamage);
}
Stuff::Scalar CombatAI::ApproximateDistanceToTarget()
{
AutoHeap local_heap (g_CombatAIHeap);
Stuff::Point3D delta;
delta.Subtract((Stuff::Point3D)GetSelf().GetLocalToWorld(),
(Stuff::Point3D)GetTarget().GetLocalToWorld());
return (delta.GetApproximateLength());
}
bool CombatAI::GetUnableToFireDuration(Stuff::Scalar duration) const
{
if (m_LastUnableToFireTime == 0)
{
return (false);
}
return (m_LastUnableToFireTime + duration < gos_GetElapsedTime());
/*
const Stuff::Scalar time((Stuff::Scalar)gos_GetElapsedTime());
if (m_LastTimeSwitchedTargets + duration > time)
{
return (false);
}
if (m_LastFireTime + duration > time)
{
return (false);
}
(m_LastUnableToFireTime + duration > time))
{
return (false);
}
if (m_LastUnableToFireTime == 0)
{
return (false);
}
return (m_LastUnableToFireTime + duration < gos_GetElapsedTime());
return (true);
*/
}
MWObject* CombatAI::GetNearest(bool fMustBeEnemy)
{
COMBAT_LOGIC("Get Nearest");
TIME_FUNCTION(tCombatAITime_GetNearest);
if (GetSelf().GetSensor() == 0)
{
return (0);
}
const int index = (int)fMustBeEnemy;
if ((m_CachedNearest[index] != -2) &&
(m_CachedNearestTime[index] + nearest_enemy_refresh > gos_GetElapsedTime()))
{
Adept::Entity* cached = NameTable::GetInstance()->FindData(m_CachedNearest[index]);
if ((cached != 0) &&
(cached->IsDerivedFrom(MWObject::DefaultData) == true))
{
return (Cast_Object(MWObject*,cached));
}
}
std::vector<MWObject*> objects;
GetSensorList(GetSelf().GetSensor()->RefreshAndGetSensorData(),objects);
Stuff::Point3D my_pos(GetSelf().GetLocalToWorld());
Stuff::Scalar best_distance = 0;
std::vector<MWObject*>::iterator best = objects.end();
{for (std::vector<MWObject*>::iterator i = objects.begin();
i != objects.end();
++i)
{
if ((*i)->objectID != GetSelf().objectID)
{
if ((fMustBeEnemy == false) ||
((*i)->GetAlignment() != GetSelf().GetAlignment()))
{
Stuff::Scalar distance = GetLengthSquared(my_pos,(Stuff::Point3D)(*i)->GetLocalToWorld());
if ((best == 0) ||
(distance < best_distance))
{
best = i;
best_distance = distance;
}
}
}
}}
if (best != objects.end())
{
m_CachedNearest[index] = (*best)->objectID;
m_CachedNearestTime[index] = gos_GetElapsedTime();
return (Cast_Object(MWObject*,*best));
}
else
{
m_CachedNearest[index] = -2;
m_CachedNearestTime[index] = 0;
}
return (0);
}
Adept::DamageObject* CombatAI::PickComponent(MWObject& who, const component_vector& components)
{
Verify(UserConstants::Instance() != 0);
if ((IsMovingQuickly(who) == true) ||
(components.size() == 0))
{
return (GetComponent_TorsoThenTopDown(components));
}
Stuff::Scalar elite((Stuff::Scalar)EliteSkill());
if (elite < UserConstants::Instance()->Get(UserConstants::switch_to_random_component_picking))
{
return (GetComponent_LeastDamaged(components));
}
if (elite < UserConstants::Instance()->Get(UserConstants::switch_to_most_damaged_component_picking))
{
return (GetComponent_Random(components));
}
return (GetComponent_MostDamaged(who,components));
}
bool CombatAI::IsMovingQuickly(MWObject& who)
{
if (who.IsDerivedFrom(Vehicle::DefaultData) == false)
{
return (false);
}
const Vehicle* v = Cast_Object(const Vehicle*,&who);
if ((v != 0) &&
(v->currentSpeedKPH >= min_speed_kph_considered_moving_quickly))
{
Stuff::LinearMatrix4D los;
who.GetLineOfSight(los);
if (MatrixFacesPoint(los,(Stuff::Point3D)GetSelf().GetLocalToWorld(),Stuff::Pi * 0.5f) == true)
{
return (false);
}
return (true);
}
return (false);
}
void CombatAI::InitializeSecondaryTargets(CombatAI::secondary_target_list& secondary_targets)
{
if (GetSelf().GetSensor() == 0)
{
return;
}
secondary_targets.reserve(GetSelf().GetSensor()->numberOfContacts);
const MechWarrior4::Sensor::sensorDataArray& sensed_data = GetSelf().GetSensor()->RefreshAndGetSensorData();
{for (int i = 0;
i < GetSelf().GetSensor()->numberOfContacts;
++i)
{
MechWarrior4::SensorData* d = sensed_data[i];
if ((d != 0) &&
(d->object.GetCurrent() != 0))
{
MWObject* obj = d->object.GetCurrent();
if ((obj->GetAlignment() != GetSelf().GetAlignment()) &&
(obj != Target()) &&
(obj != &GetSelf()) &&
(obj->GetTargetDesirability() != MWObject::desirability_never) &&
(obj->GetTargetDesirability() != 0) &&
(obj->weaponChain.IsEmpty() == false) &&
(obj->GetAI() != 0))
{
secondary_targets.push_back(obj);
}
}
}}
}
CombatAI::secondary_target_list::iterator CombatAI::SelectSecondaryTarget(CombatAI::secondary_target_list& secondary_targets)
{
Verify(secondary_targets.size() > 0);
if (secondary_targets.size() == 1)
{
return (secondary_targets.begin());
}
Stuff::Point3D my_pos = (Stuff::Point3D)GetSelf().GetLocalToWorld();
secondary_target_list::iterator best = secondary_targets.begin();
Stuff::Scalar best_distance_squared = GetLengthSquared((Stuff::Point3D)(*best)->GetLocalToWorld(),my_pos);
secondary_target_list::iterator i = best;
++i;
{for (;
i != secondary_targets.end();
++i)
{
Check_Object(*i);
Stuff::Scalar distance_squared = GetLengthSquared((Stuff::Point3D)(*i)->GetLocalToWorld(),my_pos);
if (distance_squared < best_distance_squared)
{
best_distance_squared = distance_squared;
best = i;
}
}}
return (best);
}
void CombatAI::RefreshCanSeeResults()
{
int i = 0;
std::vector<CanSeeResult>::iterator iter = m_CanSeeResults.begin();
while (i < m_CanSeeResults.size())
{
if (m_CanSeeResults[i].m_Time + can_see_result_refresh_time < gos_GetElapsedTime())
{
m_CanSeeResults.erase(iter);
RefreshCanSeeResults();
}
else
{
++i;
++iter;
}
}
}
bool CombatAI::CanSee(MWObject& entity, Stuff::Scalar cheat_angle)
{
TIME_FUNCTION(tCombatAITime_CanSee);
Stuff::Point3D hisLOS(entity.GetLineOfSight());
Stuff::Scalar fog_distance = 1000.0f;
if (GetIgnoreFog() == false)
{
fog_distance = GetFogDistance();
}
if (m_Deactive == true)
{
if (GetApproximateLength(hisLOS,GetSelf().GetLineOfSight()) >= fog_distance)
{
return (false);
}
return (true);
}
Stuff::LinearMatrix4D my_los;
GetSelf().GetLineOfSight(my_los);
if (GetSelf().IsDerivedFrom(Dropship::DefaultData) == true)
{
return (GetApproximateLength(hisLOS,(Stuff::Point3D)GetSelf().GetLocalToWorld()) < fog_distance);
}
if (MatrixFacesPoint(my_los,hisLOS,cheat_angle) == false)
{
switch (m_LookState)
{
case LOOK_CENTER:
{
return (false);
}
case LOOK_LEFT:
{
if (MatrixFacesPoint(GetFireSourceMatrix(MW4AI::FIRE_FROM_LEFT_ARM),hisLOS,cheat_angle) == false)
{
return (false);
}
break;
}
case LOOK_RIGHT:
{
if (MatrixFacesPoint(GetFireSourceMatrix(MW4AI::FIRE_FROM_RIGHT_ARM),hisLOS,cheat_angle) == false)
{
return (false);
}
break;
}
default:
{
Verify(!"Invalid look state.");
break;
}
}
}
if (GetLongTomPitchPoint().GetLengthSquared() > 0)
{
return (true);
}
Stuff::Point3D myLOS(GetSelf().GetLineOfSight());
if (GetApproximateLength(hisLOS,myLOS) >= fog_distance)
{
return (false);
}
RefreshCanSeeResults();
{for (std::vector<CanSeeResult>::const_iterator i = m_CanSeeResults.begin();
i != m_CanSeeResults.end();
++i)
{
if (((*i).m_Angle == cheat_angle) &&
((*i).m_Entity == entity.objectID))
{
return ((*i).m_Result == MW4AI::FireData::HIT_TARGET);
}
}}
MW4AI::FireData projection(GetSelf(),myLOS,hisLOS);
projection.Project();
MW4AI::FireData::HIT_RESULT result(projection.GetHitResult(&entity,true));
CanSeeResult csr;
csr.m_Result = result;
csr.m_Time = (Stuff::Scalar)gos_GetElapsedTime();
csr.m_Entity = entity.objectID;
csr.m_Angle = cheat_angle;
m_CanSeeResults.push_back(csr);
return (result == MW4AI::FireData::HIT_TARGET);
}
bool CombatAI::GetIgnoringFriendlyFire() const
{
{for (MWObject::GroupList::const_iterator i = GetSelf().GetGroups().begin();
i != GetSelf().GetGroups().end();
++i)
{
gosREPORT((Adept::Mission::GetInstance() != 0),"The mission object does not exist");
gosREPORT((Adept::Mission::GetInstance()->IsDerivedFrom(MechWarrior4::MWMission::DefaultData) == true),"The mission object is not the correct type");
MechWarrior4::MWMission* mission = Cast_Object(MechWarrior4::MWMission*,Adept::Mission::GetInstance());
Check_Object(mission);
if (mission->GetGroupContainer().GroupExists(*i) == true)
{
const Group& g = mission->GetGroupContainer().GetGroup(*i);
if (g.IgnoresFriendlyFire() == true)
{
return (true);
}
}
}}
return (MoverAI::GetIgnoringFriendlyFire());
}
bool CombatAI::GetEscapeRegionFocusIfAvailable(Stuff::Point3D& focus) const
{
{for (MWObject::GroupList::const_iterator i = GetSelf().GetGroups().begin();
i != GetSelf().GetGroups().end();
++i)
{
gosREPORT((Adept::Mission::GetInstance() != 0),"The mission object does not exist");
gosREPORT((Adept::Mission::GetInstance()->IsDerivedFrom(MechWarrior4::MWMission::DefaultData) == true),"The mission object is not the correct type");
MechWarrior4::MWMission* mission = Cast_Object(MechWarrior4::MWMission*,Adept::Mission::GetInstance());
Check_Object(mission);
if (mission->GetGroupContainer().GroupExists(*i) == true)
{
const Group& g = mission->GetGroupContainer().GetGroup(*i);
if (g.HasAI() == true)
{
Adept::Entity* leader = g.IDToEntity(*(g.GetElements().begin()));
if ((leader != 0) &&
(leader != getEntity()))
{
focus = (Stuff::Point3D)leader->GetLocalToWorld();
return (true);
}
}
}
}}
return (false);
}
Stuff::Scalar CombatAI::GetMaxFireCheatAngle()
{
if (IsLaserTurret() == true)
{
return (UserConstants::Instance()->Get(UserConstants::max_turret_fire_cheat_angle) * Radians_Per_Degree);
}
if (GetSelf().IsDerivedFrom(Airplane::DefaultData) == true)
{
return (UserConstants::Instance()->Get(UserConstants::max_airplane_fire_cheat_angle) * Radians_Per_Degree);
}
return (UserConstants::Instance()->Get(UserConstants::max_fire_cheat_angle) * Radians_Per_Degree);
}
bool CombatAI::IsLaserTurret()
{
if ((GetSelf().IsDerivedFrom(Building::DefaultData) == true) &&
(MW4AI::HasAnyProjectileWeapons(GetSelf()) == false))
{
return (true);
}
return (false);
}
void CombatAI::CenterTorso()
{
{for (std::vector<Torso*>::const_iterator i = GetSelf().GetTorsos().begin();
i != GetSelf().GetTorsos().end();
++i)
{
Torso* torso = *i;
Check_Object(torso);
if (Small_Enough(torso->yawValue) == false)
{
Stuff::Scalar yaw_demand = torso->yawValue * -1;
yaw_demand *= 4; // arbitrary, but makes it happen faster
if (yaw_demand > 1)
{
yaw_demand = 1;
}
else
{
if (yaw_demand < -1)
{
yaw_demand = -1;
}
}
torso->yawDemand = yaw_demand;
}
if (Small_Enough(torso->pitchValue) == false)
{
Stuff::Scalar pitch_demand = torso->pitchValue * -1;
pitch_demand *= 4; // arbitrary, but makes it happen faster
if (pitch_demand > 1)
{
pitch_demand = 1;
}
else
{
if (pitch_demand < -1)
{
pitch_demand = -1;
}
}
torso->pitchDemand = pitch_demand;
}
}}
}
void CombatAI::SurrenderPose()
{
AutoHeap local_heap (g_CombatAIHeap);
CenterTorso();
Torso* torso = GetSelf().GetTorso();
if (torso != 0)
{
torso->LookSurrender();
}
}
bool CombatAI::SuicideIsOK() const
{
if (CurrentMood() < MOODMIN + 1)
{
return (true);
}
if (m_NumArmsBlownOff + m_NumLegsBlownOff > 1)
{
return (true);
}
return (false);
}
void CombatAI::SetLookState(LookState state)
{
if (m_LookState == state)
{
return;
}
m_LastTimeLookChanged = (Stuff::Scalar)gos_GetElapsedTime();
m_LookState = state;
}
void CombatAI::SetLookState(const MW4AI::FireStyles::FireStyle& style)
{
if (ShouldRun(true) == false)
{
return;
}
m_LastLookTime = (Stuff::Scalar)gos_GetElapsedTime();
if ((GetAttackState() != ATTACKING) ||
(style.ShouldFireAtPrimaryTarget() == false) ||
(GetSelf().GetTorsos().size() != 1) ||
(GetSelf().GetTorso()->twistJoint == 0) ||
(GetSelf().IsDerivedFrom(Mech::DefaultData) == false) ||
(EliteSkill() < UserConstants::Instance()->Get(UserConstants::min_look_left_or_right)) ||
(EliteSkill() > UserConstants::Instance()->Get(UserConstants::max_look_left_or_right)))
{
SetLookState(LOOK_CENTER);
return;
}
// NOTE: this implementation is a cheesy hack; I will replace it before too long. It does the hjob, though.
const Stuff::Scalar yaw(YawToPoint(GetSelf().GetTorso()->twistJoint->GetLocalToWorld(),(Stuff::Point3D)GetTarget().GetLocalToWorld()));
if (yaw < -Pi_Over_4)
{
SetLookState(LOOK_RIGHT);
return;
}
if (yaw > Pi_Over_4)
{
SetLookState(LOOK_LEFT);
return;
}
SetLookState(LOOK_CENTER);
}
void CombatAI::UpdateLookState()
{
if (GetSelf().IsDerivedFrom(Mech::DefaultData) == false)
{
return;
}
if (m_LastLookTime + look_decay_time < gos_GetElapsedTime())
{
m_LookState = LOOK_CENTER;
}
Mech* mech = Cast_Object(Mech*,&GetSelf());
Torso* torso = mech->GetTorso();
Check_Object(torso);
switch (m_LookState)
{
case LOOK_LEFT:
{
torso->LookLeft();
break;
}
case LOOK_RIGHT:
{
torso->LookRight();
break;
}
case LOOK_CENTER:
{
torso->LookForward();
break;
}
case LOOK_BACK:
{
torso->LookBack();
break;
}
}
}
void CombatAI::SuggestPointWasNotAGoodDestination(const Stuff::Point3D& dest)
{
while (m_BlockedMovePoints.size() > max_blocked_move_points_size)
{
m_BlockedMovePoints.erase(m_BlockedMovePoints.begin());
}
m_BlockedMovePoints.push_back(dest);
}
bool CombatAI::PointIsInBadNeighborhood(const Stuff::Point3D& point) const
{
{for (std::vector<Stuff::Point3D>::const_iterator i = m_BlockedMovePoints.begin();
i != m_BlockedMovePoints.end();
++i)
{
if (GetLengthSquared(point,*i) <= bad_area_radius * bad_area_radius)
{
return (true);
}
}}
return (false);
}
MW4AI::FireSource CombatAI::GetBestFireSource(MWObject& who)
{
{for (std::vector<CachedFireSource>::iterator i = m_FireSourceCache.begin();
i != m_FireSourceCache.end();
++i)
{
if ((*i).object_id == who.objectID)
{
if ((*i).last_update + max_fire_source_cache_refresh > gos_GetElapsedTime())
{
return ((*i).fire_source);
}
m_FireSourceCache.erase(i);
break;
}
}}
CachedFireSource cfs;
cfs.last_update = gos_GetElapsedTime() + Stuff::Random::GetFraction();
cfs.object_id = who.objectID;
cfs.fire_source = GetBestFireSource(who.GetLineOfSight());
m_FireSourceCache.push_back(cfs);
if (m_FireSourceCache.size() > 4)
{
m_FireSourceCache.erase(m_FireSourceCache.begin());
}
return (cfs.fire_source);
}
MW4AI::FireSource CombatAI::GetBestFireSource(const Stuff::Point3D& target_point)
{
Stuff::LinearMatrix4D my_los;
GetSelf().GetLineOfSight(my_los);
if (MatrixFacesPoint(my_los,m_LastAimPoint,GetMaxFireCheatAngle()) == true)
{
return (MW4AI::FIRE_FROM_ANYWHERE);
}
Stuff::Scalar min(UserConstants::Instance()->Get(UserConstants::min_look_time_at_0));
Stuff::Scalar max(UserConstants::Instance()->Get(UserConstants::min_look_time_at_100));
Stuff::Scalar min_arm_look_time(min + ((max - min) * ((Stuff::Scalar)EliteSkill() * 0.01f)));
if (Small_Enough(min_arm_look_time) == true)
{
min_arm_look_time = 0;
}
if (m_LookState == LOOK_LEFT)
{
if ((m_LeftArmExists == true) &&
(m_LastTimeLookChanged + min_arm_look_time < (Stuff::Scalar)gos_GetElapsedTime()) &&
(MatrixFacesPoint(GetFireSourceMatrix(MW4AI::FIRE_FROM_LEFT_ARM),m_LastAimPoint,GetMaxFireCheatAngle()) == true))
{
return (MW4AI::FIRE_FROM_LEFT_ARM);
}
}
else
{
if ((m_LookState == LOOK_RIGHT) &&
(m_RightArmExists == true) &&
(m_LastTimeLookChanged + min_arm_look_time < (Stuff::Scalar)gos_GetElapsedTime()) &&
(MatrixFacesPoint(GetFireSourceMatrix(MW4AI::FIRE_FROM_RIGHT_ARM),m_LastAimPoint,GetMaxFireCheatAngle()) == true))
{
return (MW4AI::FIRE_FROM_RIGHT_ARM);
}
}
return (MW4AI::FIRE_FROM_ANYWHERE);
}
Stuff::LinearMatrix4D CombatAI::GetFireSourceMatrix(MW4AI::FireSource fire_source)
{
if (fire_source != MW4AI::FIRE_FROM_ANYWHERE)
{
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(GetSelf().weaponChain));
MechWarrior4::Weapon* weapon = 0;
while ((weapon = i.ReadAndNext()) != 0)
{
if (fire_source == MW4AI::FIRE_FROM_LEFT_ARM)
{
if (WeaponIsArmWeapon(*weapon,true) == true)
{
return (WeaponSiteLocalToWorld(*weapon));
}
}
else
{
if (WeaponIsArmWeapon(*weapon,false) == true)
{
return (WeaponSiteLocalToWorld(*weapon));
}
}
}
}
Stuff::LinearMatrix4D my_los;
GetSelf().GetLineOfSight(my_los);
return (my_los);
}
void CombatAI::GetAttackInterval(Stuff::Scalar& min, Stuff::Scalar& max)
{
if (m_LastAttackIntervalRefresh + min_attack_interval_update > gos_GetElapsedTime())
{
min = m_MinAttackInterval;
max = m_MaxAttackInterval;
return;
}
min = GetMinWeaponDistance(GetSelf());
max = GetMaxWeaponDistance(GetSelf());
if (max <= min)
{
max = min + 100.0f;
}
Stuff::Scalar max_allowable_radius(1000.0f);
if (GetIgnoreFog() == false)
{
max_allowable_radius = GetFogDistance();
}
if (MW4AI::g_MissionGraph != 0)
{
Stuff::Scalar node_radius = 0;
{
CRailNode* my_node = MW4AI::g_MissionGraph->FindClosestNode((Stuff::Point3D)GetSelf().GetLocalToWorld(),false);
if (my_node != 0)
{
node_radius = (my_node->Radius() * 1.5f);
}
CRailNode* target_node = MW4AI::g_MissionGraph->FindClosestNode((Stuff::Point3D)GetTarget().GetLocalToWorld(),false);
if (target_node != 0)
{
if (node_radius != 0)
{
node_radius += (target_node->Radius() * 1.5f);
node_radius *= 0.5f;
}
else
{
node_radius = (target_node->Radius() * 1.5f);
}
}
}
if (node_radius < max_allowable_radius)
{
max_allowable_radius = node_radius;
}
if (max_allowable_radius < min_node_radius_used_for_attack_interval)
{
max_allowable_radius = min_node_radius_used_for_attack_interval;
}
}
if (max > max_allowable_radius)
{
max = max_allowable_radius;
}
if (min > (max * 0.3f))
{
min = max * 0.3f;
}
m_LastAttackIntervalRefresh = gos_GetElapsedTime() + Stuff::Random::GetFraction();
m_MinAttackInterval = min;
m_MaxAttackInterval = max;
}
Stuff::Scalar CombatAI::GetCombatRadiusForRange(Stuff::Scalar percentile_range)
{
Verify(percentile_range >= 0);
Verify(percentile_range <= 100);
percentile_range *= GetAttackRadiusMultiplier();
if (percentile_range > 100)
{
percentile_range = 100;
}
if (percentile_range < 0)
{
percentile_range = 0;
}
Stuff::Scalar range = percentile_range * 0.01f;
Stuff::Scalar min;
Stuff::Scalar max;
GetAttackInterval(min,max);
Stuff::Scalar rv = min + ((max - min) * range);
return (rv);
}
Stuff::Scalar CombatAI::GetMoodRadiusMultiplier() const
{
Verify(UserConstants::Instance() != 0);
UserConstants::ID id;
int int_mood = (int)CurrentMood();
switch ((MW4AI::Moods::ID)int_mood)
{
case MW4AI::Moods::DESPERATE_START:
case MW4AI::Moods::DESPERATE_END:
{
id = UserConstants::interval_multiplier_desperate;
break;
}
case MW4AI::Moods::DEFENSIVE_START:
case MW4AI::Moods::DEFENSIVE_END:
{
id = UserConstants::interval_multiplier_defensive;
break;
}
case MW4AI::Moods::NEUTRAL_START:
case MW4AI::Moods::NEUTRAL_END:
{
id = UserConstants::interval_multiplier_neutral;
break;
}
case MW4AI::Moods::AGRESSIVE_START:
case MW4AI::Moods::AGRESSIVE_END:
{
id = UserConstants::interval_multiplier_aggressive;
break;
}
case MW4AI::Moods::BRUTAL_START:
case MW4AI::Moods::BRUTAL_END:
{
id = UserConstants::interval_multiplier_brutal;
break;
}
default:
{
Verify("Mood not found!" == 0);
return (1);
}
}
return (UserConstants::Instance()->Get(id));
}
void CombatAI::SetAttackRadiusMultiplier(Stuff::Scalar multiplier)
{
Verify(multiplier <= 1.0f);
m_AttackRadiusMultiplier = multiplier;
}
bool CombatAI::ShouldSpendAmmoAgainst(MWObject& who, MW4AI::FireStyles::FireStyle& style)
{
if ((style.ShouldFireAtPrimaryTarget() == false) ||
(&who == Target()))
{
if ((EliteSkill() >= UserConstants::Instance()->Get(UserConstants::min_dont_use_limited_ammo_vs_non_mechs)) &&
(EliteSkill() <= UserConstants::Instance()->Get(UserConstants::max_dont_use_limited_ammo_vs_non_mechs)) &&
(who.IsDerivedFrom(Mech::DefaultData) == false) &&
(HasAnyUnlimitedAmmoWeapons(GetSelf()) == true))
{
return (false);
}
else
{
return (true);
}
}
return (false);
}
void CombatAI::ThisIsAMethodToSetABreakpointInIfCurrentVehicle()
{
#if defined(LAB_ONLY)
AutoHeap local_heap (g_CombatAIHeap);
if (Adept::Player::GetInstance() != 0)
{
MechWarrior4::MWPlayer* p = Cast_Object(MechWarrior4::MWPlayer*,Adept::Player::GetInstance());
if ((p != 0) &&
(p->GetInterface() != 0) &&
(p->GetInterface()->vehicle != 0) &&
(p->GetInterface()->vehicle == &GetSelf()))
{
if ((DebugHelper::GetInstance() != 0) &&
(DebugHelper::GetInstance()->ShouldBreakInCurrentVehicle() == true))
{
PAUSE(("This is the current vehicle."));
}
}
}
#endif
}
Stuff::Scalar CombatAI::GetNormalizedCurrentHeat() const
{
HeatManager* hm = GetSelf().m_heatManager;
if (hm == 0)
{
return (0);
}
Stuff::Scalar max_heat(hm->GetMaxHeat());
if (Small_Enough(max_heat) == true)
{
return (0);
}
Stuff::Scalar cur_heat(hm->EstimateMaxCurrentHeat());
if (cur_heat > max_heat)
{
cur_heat = max_heat;
}
if (cur_heat < 0)
{
cur_heat = 0;
}
return (cur_heat / max_heat);
}
bool CombatAI::LeftmostOrRightmostWeaponHitsSomethingElse(const MW4AI::FireParamPackage& params)
{
std::vector<Weapon*> weapons;
GetLeftmostAndRightmostWeapons(GetSelf(),weapons);
if (weapons.size() < 2)
{
return (false);
}
{for (std::vector<Weapon*>::const_iterator i = weapons.begin();
i != weapons.end();
++i)
{
Check_Object(*i);
std::pair<FireData,Stuff::Time> result = CalculateFireData(params,**i);
Stuff::Point3D end = result.first.GetDest();
FireData new_fire_data(params.fire_data.GetSource(),
**i,
end);
new_fire_data.Project();
switch (new_fire_data.GetHitResult((Adept::Entity*)params.who_to_hit,true))
{
case FireData::HIT_NOTHING:
{
if ((params.who_to_hit == false) &&
(params.vehicle_shooting_at != 0))
{
return (true);
}
break;
}
case FireData::HIT_SOMETHING_ELSE:
{
return (true);
}
case FireData::HIT_TARGET:
{
break;
}
}
}}
return (false);
}
Stuff::Scalar CombatAI::GetComponentScore(int armor_component, int internal_component, Stuff::Scalar max_score, bool is_left)
{
const Stuff::Scalar LEFT(1);
const Stuff::Scalar RIGHT(-1);
if (MW4AI::Damage::GetInternalDamage(GetSelf(),internal_component) == Adept::InternalDamageObject::Destroyed)
{
if (is_left == true)
{
return (max_score * RIGHT);
}
return (max_score * LEFT);
}
const Stuff::Scalar armor_level = MW4AI::Damage::GetHighResArmorLevel(GetSelf(),armor_component);
if (armor_level != 1)
{
const Stuff::Scalar rv(0.66f * max_score * (1 - armor_level));
if (is_left == true)
{
return (rv * LEFT);
}
return (rv * RIGHT);
}
return (0);
}
Stuff::Scalar FindLongTomAngleForDistance(LongTomWeaponSubsystem& long_tom,
Stuff::Scalar target_distance)
{
Stuff::Scalar d = target_distance;
Stuff::Scalar v = long_tom.GetGameModel()->initialLinearVelocity.y;
Stuff::Scalar g = g_Gravity;
Stuff::Scalar n = (v * v * v * v) - (g * g * d * d);
if (n < 0)
{
n = (v * v * v * v) + (g * g * d * d);
}
Stuff::Scalar t = ((2 * v * v) - (2 * Stuff::Sqrt(n))) / (g * g);
t = Stuff::Sqrt(Stuff::Fabs(t));
Stuff::Scalar x = d / (v * t);
Clamp(x,0,1);
Stuff::Scalar angle = Stuff::Arccos(x);
return (angle);
}
Stuff::Point3D CombatAI::GetLongTomPitchPoint()
{
if ((Target() == 0) ||
(GetEyeSitePointer() == 0))
{
return (Stuff::Point3D(0,0,0));
}
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(GetSelf().weaponChain));
MechWarrior4::Weapon* weapon = 0;
while ((weapon = i.ReadAndNext()) != 0)
{
if ((weapon->IsDestroyed() == false) &&
(DECRYPT(weapon->encryptedAmmoCount) > 0) &&
(weapon->IsDerivedFrom(LongTomWeaponSubsystem::DefaultData) == true))
{
LongTomWeaponSubsystem* long_tom = Cast_Object(LongTomWeaponSubsystem*,weapon);
Check_Object(long_tom);
Stuff::Point3D my_pos(GetSelf().GetLocalToWorld());
Stuff::Point3D target_pos(GetTarget().GetLocalToWorld());
Stuff::Point3D delta;
delta.Subtract(target_pos,my_pos);
Stuff::Scalar distance = delta.GetLength();
distance += (target_pos.y - my_pos.y);
Stuff::Scalar angle(FindLongTomAngleForDistance(*long_tom,delta.GetLength()));
Stuff::UnitVector3D forward;
GetEyeSitePointer()->GetLocalToWorld().GetLocalForwardInWorld(&forward);
Stuff::Point3D point_forward(forward);
point_forward.y = 0;
point_forward.Normalize(point_forward);
Stuff::Point3D my_torso_pos(GetEyeSitePointer()->GetLocalToWorld());
point_forward *= 100;
point_forward += my_torso_pos;
point_forward.y = my_torso_pos.y + (100 * Stuff::Tan(angle));
return (point_forward);
}
}
return (Stuff::Point3D(0,0,0));
}
bool CombatAI::CanFireNARC()
{
if (MWMission::GetInstance() == 0)
{
return (false);
}
MWMission* mission = Cast_Object(MWMission*,MWMission::GetInstance());
Narc* my_teams_narc = mission->GetNarc(GetSelf().GetAlignment());
if ((my_teams_narc == 0) ||
(my_teams_narc->IsDestroyed() == true))
{
return (true);
}
if ((my_teams_narc->attachedEntity.GetCurrent() == NULL) ||
(my_teams_narc->attachedEntity.GetCurrent()->IsDestroyed()))
{
return (true);
}
if ((Target() != 0) &&
(Target()->GetAlignment() != my_teams_narc->attachedEntity.GetCurrent()->GetAlignment()))
{
return (true);
}
return (false);
}
bool CombatAI::CanFireHeatGeneratingWeapons() const
{
if (m_WaitingForHeatToReachMinSkill == true)
{
return (false);
}
if ((GetSelf().m_heatManager != 0) &&
(GetSelf().m_heatManager->IsCooling() != 0))
{
return (false);
}
return (true);
}
void CombatAI::CheapShot(MWObject& who, bool should_hit, bool should_spend_ammo)
{
const Stuff::Scalar distance_to_enemy(GetApproximateLength((Stuff::Point3D)GetSelf().GetLocalToWorld(),
(Stuff::Point3D)who.GetLocalToWorld()));
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(GetSelf().weaponChain));
MechWarrior4::Weapon* weapon = 0;
Adept::Entity* target = 0;
if (should_hit == true)
{
component_vector components;
DamageObjectChainToVector(who.damageObjects,components);
if (components.size() > 0)
{
Adept::DamageObject* damage_object = GetComponent_Random(components);
if (damage_object != 0)
{
target = damage_object->parentEntity;
}
}
}
while ((weapon = i.ReadAndNext()) != 0)
{
if (MW4AI::WeaponCanFire(*weapon,distance_to_enemy,m_MinFiringDelay,m_MaxFiringDelay))
{
if ((DECRYPT(weapon->encryptedAmmoCount) == -1) ||
(should_spend_ammo == true))
{
weapon->QuickFireWeapon(target);
}
}
}
}
bool CombatAI::MoveDone() const
{
if ((m_CurMove == 0) ||
(m_CurMove->Done() == true) ||
(m_CurMove->MoveType() == MOVE_LOOKOUT))
{
return (true);
}
if (m_CurMove->MoveType() == MOVE_FOLLOW)
{
if ((m_OrderSpeed == 0) ||
(m_TimeNotMoving > 2.0f))
{
return (true);
}
}
return (false);
}
void CombatAI::Crouch()
{
if (GetSelf_AsVehicle() == 0)
{
return;
}
if (GetSelf_AsVehicle()->IsDerivedFrom(Mech::DefaultData) == true)
{
Mech* mech = Cast_Object(Mech*,&GetSelf());
mech->CrouchRequest();
}
}
bool CombatAI::Crouching()
{
if (GetSelf_AsVehicle() == 0)
{
return (false);
}
if (GetSelf_AsVehicle()->IsDerivedFrom(Mech::DefaultData) == true)
{
Mech* mech = Cast_Object(Mech*,&GetSelf());
if (mech->animStateEngine == 0)
{
return (false);
}
return (mech->animStateEngine->GetState() == MechAnimationStateEngine::CrouchState);
}
return (false);
}
bool CombatAI::MovedWithin(Stuff::Scalar duration) const
{
if ((m_CurMove != 0) &&
(m_CurMove->Done() == false))
{
return (true);
}
return (m_LastTimeMoved + duration > (Stuff::Scalar)gos_GetElapsedTime());
}
void CombatAI::ContinuousFlyBy(const Stuff::Point3D& loc)
{
Verify(!"Should never get here -- not a plane!");
}
void CombatAI::Save (MemoryStream *stream)
{
if (m_Tactic.GetPointer() != 0)
{
*stream << true;
*stream << (int)m_Tactic->GetID();
}
else
{
*stream << false;
}
*stream << m_LastPos;
*stream << m_LastMoveDest;
*stream << m_LastAimPoint;
*stream << (int)m_HitResult;
*stream << m_DistanceFromTargetSquared;
*stream << m_OverridingMovement;
*stream << m_AttackThrottle;
*stream << m_LastFireTime;
*stream << m_MinFiringDelay;
*stream << m_MaxFiringDelay;
*stream << m_LastTimeProjectileShotAtMe;
*stream << m_LastProjectileOrigin;
*stream << m_ThrottleOverrideStopTime;
*stream << m_HoldingFire;
*stream << m_InternalArmorBreached;
*stream << m_LeftArmExists;
*stream << m_RightArmExists;
*stream << m_NumArmsBlownOff;
*stream << m_NumLegsBlownOff;
*stream << m_LastUnableToFireTime;
*stream << m_ForceDestinationRecalc;
*stream << m_LastFrameTime;
*stream << m_ApproximateTimeBetweenFrames;
*stream << m_LastFriendlyFireTime;
*stream << m_FriendlyFireDelay;
*stream << m_LastTimeTargeted;
*stream << m_TimeBeingTargeted;
*stream << m_LastTargetedAlignment;
*stream << m_LastTimeRammedTarget;
*stream << m_LastTimeJumped;
*stream << m_JumpDuration;
*stream << m_PerformedFirstUpdate;
*stream << m_SquadTargetingRadius;
*stream << m_ExplicitFirePoint;
*stream << m_ExplicitFireStretch;
if (m_LastAttackOrderPosition.GetPointer() != 0)
{
*stream << true;
*stream << *m_LastAttackOrderPosition;
}
else
{
*stream << false;
}
if (m_LastMoveOrderPosition.GetPointer() != 0)
{
*stream << true;
*stream << *m_LastMoveOrderPosition;
}
else
{
*stream << false;
}
*stream << m_WaitingForHeatToReachMinSkill;
*stream << m_MostRecentTactics.size();
{for (std::vector<MW4AI::Tactics::TacticID>::iterator i = m_MostRecentTactics.begin();
i != m_MostRecentTactics.end();
++i)
{
*stream << (int)(*i);
}}
*stream << (int)m_SearchLightController.GetState();
*stream << (int)m_LookState;
*stream << m_LastLookTime;
*stream << m_BlockedMovePoints.size();
{for (std::vector<Stuff::Point3D>::iterator i = m_BlockedMovePoints.begin();
i != m_BlockedMovePoints.end();
++i)
{
*stream << *i;
}}
*stream << m_AttackRadiusMultiplier;
*stream << m_LastOffsetToMiss;
*stream << m_MissOffsetStartTime;
*stream << m_Last_Till_Value;
// TODO: save squad orders ...
*stream << m_CheapShotTarget;
*stream << m_CheapShotShouldHit;
*stream << m_CheapShotShouldSpendAmmo;
*stream << m_LastTimeMoved;
*stream << m_PerWeaponRayCasting;
*stream << m_AutoTargetingEnabled;
MoverAI::Save(stream);
}
void CombatAI::Load (MemoryStream *stream)
{
AutoHeap local_heap (g_CombatAIHeap);
bool tactic_exists;
*stream >> tactic_exists;
if (tactic_exists == true)
{
int tactic_id;
*stream >> tactic_id;
m_Tactic = Tactics::CreateTactic(m_Interface,(MW4AI::Tactics::TacticID)tactic_id);
}
else
{
m_Tactic.Delete();
}
*stream >> m_LastPos;
*stream >> m_LastMoveDest;
*stream >> m_LastAimPoint;
int hit_result;
*stream >> hit_result;
m_HitResult = (MW4AI::FireData::HIT_RESULT)hit_result;
*stream >> m_DistanceFromTargetSquared;
*stream >> m_OverridingMovement;
*stream >> m_AttackThrottle;
*stream >> m_LastFireTime;
*stream >> m_MinFiringDelay;
*stream >> m_MaxFiringDelay;
*stream >> m_LastTimeProjectileShotAtMe;
*stream >> m_LastProjectileOrigin;
*stream >> m_ThrottleOverrideStopTime;
*stream >> m_HoldingFire;
*stream >> m_InternalArmorBreached;
*stream >> m_LeftArmExists;
*stream >> m_RightArmExists;
*stream >> m_NumArmsBlownOff;
*stream >> m_NumLegsBlownOff;
*stream >> m_LastUnableToFireTime;
*stream >> m_ForceDestinationRecalc;
*stream >> m_LastFrameTime;
*stream >> m_ApproximateTimeBetweenFrames;
*stream >> m_LastFriendlyFireTime;
*stream >> m_FriendlyFireDelay;
*stream >> m_LastTimeTargeted;
*stream >> m_TimeBeingTargeted;
*stream >> m_LastTargetedAlignment;
*stream >> m_LastTimeRammedTarget;
*stream >> m_LastTimeJumped;
*stream >> m_JumpDuration;
*stream >> m_PerformedFirstUpdate;
*stream >> m_SquadTargetingRadius;
*stream >> m_ExplicitFirePoint;
*stream >> m_ExplicitFireStretch;
bool last_attack_order_pos;
*stream >> last_attack_order_pos;
if (last_attack_order_pos == true)
{
Stuff::LinearMatrix4D temp;
*stream >> temp;
m_LastAttackOrderPosition.Assimilate(&temp);
}
bool last_move_order_pos;
*stream >> last_move_order_pos;
if (last_move_order_pos == true)
{
Stuff::LinearMatrix4D temp;
*stream >> temp;
m_LastMoveOrderPosition.Assimilate(&temp);
}
*stream >> m_WaitingForHeatToReachMinSkill;
std::vector<MW4AI::Tactics::TacticID>::size_type most_recent_tactics_size;
*stream >> most_recent_tactics_size;
m_MostRecentTactics.clear();
{for (std::vector<MW4AI::Tactics::TacticID>::size_type i = 0;
i < most_recent_tactics_size;
++i)
{
int tactic_id;
*stream >> tactic_id;
m_MostRecentTactics.push_back((MW4AI::Tactics::TacticID)tactic_id);
}}
int search_light_state;
*stream >> search_light_state;
m_SearchLightController.SetState((MW4AI::SearchLightController::State)search_light_state);
int look_state;
*stream >> look_state;
m_LookState = (LookState)look_state;
*stream >> m_LastLookTime;
std::vector<Stuff::Point3D>::size_type blocked_move_points_size;
*stream >> blocked_move_points_size;
m_BlockedMovePoints.clear();
{for (std::vector<Stuff::Point3D>::size_type i = 0;
i < blocked_move_points_size;
++i)
{
Stuff::Point3D temp;
*stream >> temp;
m_BlockedMovePoints.push_back(temp);
}}
*stream >> m_AttackRadiusMultiplier;
*stream >> m_LastOffsetToMiss;
*stream >> m_MissOffsetStartTime;
*stream >> m_Last_Till_Value;
// Stuff::Auto_Ptr<MW4AI::SquadOrders> m_SquadOrders;
*stream >> m_CheapShotTarget;
*stream >> m_CheapShotShouldHit;
*stream >> m_CheapShotShouldSpendAmmo;
*stream >> m_LastTimeMoved;
*stream >> m_PerWeaponRayCasting;
*stream >> m_AutoTargetingEnabled;
MoverAI::Load (stream);
}
void CombatAI::NotifyRespawned()
{
Reset();
m_IgnoreFog = false;
m_HitResult = FireData::HIT_NOTHING;
m_OverridingMovement = false;
m_AttackThrottle = 100.0f;
m_MinFiringDelay = 0;
m_MaxFiringDelay = 0;
m_LastTimeProjectileShotAtMe = 0;
m_ThrottleOverrideStopTime = 0;
m_HoldingFire = false;
m_NumArmsBlownOff = 0;
m_NumLegsBlownOff = 0;
m_InternalArmorBreached = false;
m_LastUnableToFireTime = 0;
m_LastFireTime = 0;
m_ForceDestinationRecalc = false;
m_ApproximateTimeBetweenFrames = 0;
m_LastFrameTime = 0;
m_LastFriendlyFireTime = 0;
m_FriendlyFireDelay = 0;
m_LastTimeTargeted = 0;
m_TimeBeingTargeted = 0;
m_LastTimeRammedTarget = 0;
m_LastTimeJumped = 0;
m_SquadTargetingRadius = 0;
m_ExplicitFireStretch = 0;
m_WaitingForHeatToReachMinSkill = false;
m_LookState = LOOK_CENTER;
m_LastLookTime = (Stuff::Scalar)gos_GetElapsedTime();
m_LeftArmExists = true;
m_RightArmExists = true;
m_AttackRadiusMultiplier = 0;
m_LastOffsetToMiss = Stuff::Point3D(0,0,0);
m_MissOffsetStartTime = 0;
m_JumpDuration = 0;
m_Last_Till_Value = 0;
m_CheapShotTarget = -2;
m_CheapShotShouldHit = false;
m_CheapShotShouldSpendAmmo = true;
m_LastTimeMoved = (Stuff::Scalar)gos_GetElapsedTime();
m_PerWeaponRayCasting = false;
m_LastTargetedAlignment = GetSelf().GetAlignment();
m_LastPos = (Point3D)GetSelf().GetLocalToWorld();
m_LastMoveDest.Zero();
m_LastProjectileOrigin.Zero();
m_ExplicitFirePoint.Zero();
m_LastAimPoint.Zero();
m_AutoTargetingEnabled = true;
m_LastTimeSwitchedTargets = (Stuff::Scalar)gos_GetElapsedTime();
m_LastAttackIntervalRefresh = 0;
m_CurrentVulnerableComponent = 0;
m_CachedNearest[0] = -2;
m_CachedNearestTime[0] = 0;
m_CachedNearest[1] = -2;
m_CachedNearestTime[1] = 0;
m_ShutDownDueToHeat = false;
m_MoveRequestFinishedTime = 0;
MoverAI::NotifyRespawned();
}
MWObject* CombatAI::GetNearestEnemyToLine(Stuff::Line3D& line, const secondary_target_list& targets)
{
Stuff::Scalar least_distance = GetLengthSquared(line.m_origin,(Stuff::Point3D)GetTarget().GetLineOfSight());
MWObject* nearest = 0;
{for (secondary_target_list::const_iterator i = targets.begin();
i != targets.end();
++i)
{
if ((*i)->GetTargetDesirability() != MWObject::desirability_never)
{
Stuff::Point3D closest_point;
line.GetClosestPointTo((Stuff::Point3D)((*i)->GetLineOfSight()),&closest_point);
const Stuff::Scalar distance(GetLengthSquared(line.m_origin,closest_point));
if (distance < least_distance)
{
nearest = *i;
least_distance = distance;
}
}
}}
if (nearest == 0)
{
return (&GetTarget());
}
return (nearest);
}
MWObject* CombatAI::FindBestAutoTarget()
{
const Stuff::Point3D my_pos(GetSelf().GetLocalToWorld());
Stuff::Scalar target_distance(1);
Stuff::Scalar min_desirability(-1);
if (Target() != 0)
{
target_distance = GetApproximateLength(my_pos,(Stuff::Point3D)GetTarget().GetLocalToWorld());
min_desirability = GetTarget().GetTargetDesirability();
}
Adept::Entity* shot_by = ShotBy(gos_GetElapsedTime() - 5.0f);
std::vector<MWObject*> objects;
GetSensorList(GetSelf().GetSensor()->RefreshAndGetSensorData(),objects);
std::vector<Stuff::Scalar> scores;
std::vector<MWObject*>::iterator i = objects.begin();
while (i != objects.end())
{
if ((Target() != 0) &&
((*i) == Target()))
{
Stuff::Scalar score(1.0f);
if (Target() == shot_by)
{
score *= target_switch_shot_by_multiplier;
if ((shot_by != 0) &&
(shot_by->IsPlayerVehicle() == true))
{
score *= 20.0f;
}
}
scores.push_back(score);
++i;
}
else
{
if (((*i)->GetAlignment() != GetSelf().GetAlignment()) &&
((*i)->GetAlignment() != Entity::DefaultAlignment) &&
((*i)->GetTargetDesirability() >= min_desirability) &&
((*i)->GetTargetDesirability() != MWObject::desirability_never))
{
Stuff::Scalar distance(GetApproximateLength(my_pos,(Stuff::Point3D)(*i)->GetLocalToWorld()));
distance += 0.1f;
Stuff::Scalar score(target_distance / distance);
score *= target_switch_score_multiplier;
if ((*i)->objectID == m_ShotBy)
{
score *= target_switch_shot_by_multiplier;
if ((*i)->IsPlayerVehicle() == true)
{
score *= (Stuff::Scalar)(CurrentMood()) * 0.5f;
}
}
scores.push_back(score);
++i;
}
else
{
objects.erase(i);
}
}
}
std::vector<Stuff::Scalar>::iterator greatest = std::max_element(scores.begin(),scores.end());
if (greatest == scores.end())
{
return (0);
}
int index = index_of(scores.begin(),scores.end(),*greatest);
if ((Target() != 0) &&
(objects[index] == Target()))
{
return (0);
}
return (objects[index]);
}
void CombatAI::UpdateAutoTargeting()
{
if ((m_AutoTargetingEnabled == false) ||
(m_LastTimeSwitchedTargets + min_target_switch_time > (Stuff::Scalar)gos_GetElapsedTime()) ||
(Target() == 0) ||
(Target()->IsDestroyed() == true) ||
(m_Tactic.GetPointer() == 0) ||
(m_Tactic->GetID() == MW4AI::Tactics::TACTIC_DEFEND) ||
(m_Tactic->GetID() == MW4AI::Tactics::TACTIC_RETREAT))
{
return;
}
m_LastTimeSwitchedTargets = (Stuff::Scalar)gos_GetElapsedTime();
MWObject* target = 0;
if (m_SquadOrders.GetPointer() != 0)
{
target = m_SquadOrders->GetAutoTarget();
}
else
{
target = FindBestAutoTarget();
}
if ((target != 0) &&
(target != Target()))
{
Target(target);
}
}
bool CombatAI::GetExtendedSensorData(std::vector<MWObject*>& sensor_data_list)
{
if (m_SquadOrders.GetPointer() != 0)
{
return (m_SquadOrders->GetExtendedSensorData(sensor_data_list));
}
return (false);
}
Stuff::Scalar CombatAI::GetLeastSquaredSensorDistance(const Stuff::Point3D& point) const
{
if (m_SquadOrders.GetPointer() != 0)
{
return (m_SquadOrders->GetLeastSquaredSensorDistance(point));
}
return (inherited::GetLeastSquaredSensorDistance(point));
}
bool CombatAI::LineMightHitFriendlyUnits(const Stuff::Line3D& line)
{
if ((Target() != 0) &&
(Target()->IsDerivedFrom(MWObject::DefaultData) == true))
{
return (MW4AI::MightHitFriendlies(line,line.m_length,GetSelf(),Cast_Object(MWObject*,Target())));
}
return (MW4AI::MightHitFriendlies(line,line.m_length,GetSelf(),0));
}
Stuff::Scalar CombatAI::GetMinSpeed() const
{
if (Attacking() == false)
{
return (0);
}
return (m_MinSpeed);
}
void CombatAI::SetMinSpeed(Stuff::Scalar min_speed)
{
m_MinSpeed = min_speed;
}
void CombatAI::SetGoalPitch(Stuff::Scalar pitch)
{
if (GetSelf().GetTorso() == 0)
{
return;
}
m_GoalPitch = pitch;
}
void CombatAI::SetGoalYaw(Stuff::Scalar yaw)
{
if (GetSelf().GetTorso() == 0)
{
return;
}
m_GoalYaw = yaw;
}
void CombatAI::UpdateGoalPitchAndYaw()
{
if (m_GoalYaw != 0)
{
Torso* torso = GetSelf().GetTorso();
if (Fabs(torso->yawValue - m_GoalYaw) < 0.2f)
{
m_GoalYaw = 0;
}
else
{
torso->yawDemand = m_GoalYaw - torso->yawValue;
Clamp(torso->yawDemand,-0.7f,0.7f);
}
}
if (m_GoalPitch != 0)
{
Torso* torso = GetSelf().GetTorso();
if (Fabs(torso->pitchValue - m_GoalPitch) < 0.2f)
{
m_GoalPitch = 0;
}
else
{
torso->pitchDemand = m_GoalPitch - torso->pitchValue;
Clamp(torso->pitchDemand,-0.7f,0.7f);
}
}
}
void CombatAI::UpdateCurrentVulnerableComponent()
{
if ((EliteSkill() < UserConstants::Instance()->Get(UserConstants::min_vulnerable_leg_hiding)) ||
(EliteSkill() > UserConstants::Instance()->Get(UserConstants::max_vulnerable_leg_hiding)) ||
(CurrentMood() < UserConstants::Instance()->Get(UserConstants::min_mood_vulnerable_leg_hiding)) ||
(CurrentMood() > UserConstants::Instance()->Get(UserConstants::max_mood_vulnerable_leg_hiding)) ||
(GetSelf().IsDerivedFrom(Mech::DefaultData) == false))
{
m_CurrentVulnerableComponent = 0;
return;
}
Stuff::Scalar hide_side(0);
hide_side += GetComponentScore(Adept::DamageObject::LeftLegArmorZone,Adept::InternalDamageObject::LeftLegZone,0.75f,true);
hide_side += GetComponentScore(Adept::DamageObject::RightLegArmorZone,Adept::InternalDamageObject::RightLegZone,0.75f,false);
if ((hide_side < 0.25f) &&
(hide_side > -0.25f))
{
m_CurrentVulnerableComponent = 0;
return;
}
if (hide_side < -1)
{
m_CurrentVulnerableComponent = -1;
return;
}
if (hide_side > 1)
{
m_CurrentVulnerableComponent = 1;
return;
}
m_CurrentVulnerableComponent = hide_side;
}
bool CombatAI::CanMove() const
{
if ((m_OverridingMovement == false) ||
(GetSelf_AsVehicle() == 0))
{
return (false);
}
return (true);
}