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.
5063 lines
119 KiB
C++
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);
|
|
}
|
|
|