//===========================================================================// // 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 #include "gameinfo.hpp" #include "mw4.hpp" #include "combatai.hpp" #include "VehicleInterface.hpp" #include #include "MWPlayer.hpp" #include "MWDamageObject.hpp" #include "weapon.hpp" #include "AI_Moods.hpp" #include "Airplane.hpp" #include "Mech.hpp" #include #pragma warning (push) #include #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 #include "MWDebugHelper.hpp" #include "AI_Damage.hpp" #include "LongTomWeapon.hpp" #include "AI_Statistics.hpp" #include "Narc.hpp" #include "SubsystemClassData.hpp" #include // 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& 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 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 *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::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 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& list_out) { OBB& obb = collision_volume.m_worldSpaceBounds; CombatAI::AimPoint temp((Stuff::Point3D)obb.localToParent,&who); list_out.push_back(temp); Stuff::ChainIteratorOf 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& 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::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& 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& 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 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 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::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::iterator found = m_CachedTorsoRayCastFailures.end(); {for (std::list::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::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::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& points) { AutoHeap local_heap (g_CombatAIHeap); /* {for (std::vector::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(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 *collisions,bool& shoulddamage) { AutoHeap local_heap (g_CombatAIHeap); for (int i=0; iGetLength(); ++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 objects; GetSensorList(GetSelf().GetSensor()->RefreshAndGetSensorData(),objects); Stuff::Point3D my_pos(GetSelf().GetLocalToWorld()); Stuff::Scalar best_distance = 0; std::vector::iterator best = objects.end(); {for (std::vector::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::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::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::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::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::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 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 weapons; GetLeftmostAndRightmostWeapons(GetSelf(),weapons); if (weapons.size() < 2) { return (false); } {for (std::vector::const_iterator i = weapons.begin(); i != weapons.end(); ++i) { Check_Object(*i); std::pair 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 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 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::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::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::size_type most_recent_tactics_size; *stream >> most_recent_tactics_size; m_MostRecentTactics.clear(); {for (std::vector::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::size_type blocked_move_points_size; *stream >> blocked_move_points_size; m_BlockedMovePoints.clear(); {for (std::vector::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 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 objects; GetSensorList(GetSelf().GetSensor()->RefreshAndGetSensorData(),objects); std::vector scores; std::vector::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::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& 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); }