#include "MW4Headers.hpp" #include "AI_Tactics.hpp" #include "Helicopter.hpp" #include "Mech.hpp" #include "AI_TacticInterface.hpp" #include "LRMWeaponSubsystem.hpp" #include "MRMWeaponSubsystem.hpp" #include "AI_Condition.hpp" #include "Building.hpp" #include "SubsystemClassData.hpp" using namespace Stuff; using namespace MW4AI; using namespace MW4AI::Tactics; using namespace MechWarrior4; #pragma warning(disable:4239) const Stuff::Time satisfaction_delta_per_second = 0.08f; const Stuff::Scalar min_satisfaction_threshold = 0.1f; const Stuff::Time duration_to_ignore_tactic_satisfaction = 8.0f; // we initially ignore whether or not the tactic is working for this many seconds const Stuff::Scalar min_tactic_duration = 30.0f; // minimum duration of any tactic, in seconds const Stuff::Scalar max_tactic_duration = 120.0f; // maximum duration of any tactic, in seconds //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tactics::Registrar* Registrar::instance = 0; void Tactics::Registrar::IncrementRefCount(TacticInterface& i) { if (instance == 0) { gos_PushCurrentHeap(MechWarrior4::Heap); instance = new Registrar(i); gos_PopCurrentHeap(); } else { Verify(instance->m_RefCount > 0); } ++(instance->m_RefCount); } void Tactics::Registrar::DecrementRefCount() { Verify(instance != 0); Verify(instance->m_RefCount > 0); --(instance->m_RefCount); if (instance->m_RefCount == 0) { delete instance; instance = 0; } } Tactics::Registrar& Registrar::GetInstance() { Verify(instance != 0); Verify(instance->m_RefCount > 0); return (*instance); } Registrar::Registrar(TacticInterface& iface) : m_RefCount(0) { #define ADD(tactic) \ { Auto_Ptr t(new tactic); \ m_Tactics.push_back(t); } ADD(Circle); ADD(Strafe); ADD(CircleHover); ADD(StandGround); ADD(StopAndFire); ADD(Ram); ADD(Joust); ADD(Rush); ADD(HitAndRun); ADD(Front); ADD(Rear); ADD(Retreat); ADD(BackUpAndFire); ADD(JumpAndShoot); ADD(Snipe); ADD(ShootOnly); ADD(Defend); ADD(LocalPatrol); ADD(Surrender); ADD(Ambush); ADD(DeathFromAbove); ADD(HeliPopup); ADD(DiveBomb); ADD(FastCircle); ADD(Stare); #undef ADD } Tactics::TacticID Registrar::SelectTactic(TacticInterface& iface, const std::vector& most_recent_tactics) const { // MSL 5.05 if (iface.GetSelf().IsDerivedFrom(Building::DefaultData) == true) { return (TACTIC_SHOOT_ONLY); } if ((iface.GetSelf().IsDerivedFrom(Vehicle::DefaultData) == false) || (iface.IsShootOnlyAI() == true)) { return (TACTIC_SHOOT_ONLY); } if (iface.GetSelf().IsDerivedFrom(Helicopter::DefaultData) == true) { return (TACTIC_CIRCLE_HOVER); } if (iface.GetSelf().IsDerivedFrom(Airplane::DefaultData) == true) { MechWarrior4::Airplane* plane = Cast_Object(MechWarrior4::Airplane*,&(iface.GetSelf())); const MechWarrior4::Airplane::GameModel* game_model = plane->GetGameModel(); switch (game_model->m_attackType) { default: { Verify(!"Error: no airplane attack type specified."); return (TACTIC_DIVE_BOMB); } case MechWarrior4::Vehicle::NightShadeAttackType: { return (TACTIC_DIVE_BOMB); } case MechWarrior4::Vehicle::ShiloneAttackType: { return (TACTIC_STRAFE); } } } if (iface.GetSelf().IsDerivedFrom(Mech::DefaultData) == false) { if (iface.GetMood() <= 2) { return (TACTIC_RETREAT); } return (TACTIC_SNIPE); } std::vector tactics; {for (tactic_container::const_iterator i = m_Tactics.begin(); i != m_Tactics.end(); ++i) { if (((*i)->CanBeSelectedAutomatically(iface) == true) && (std::find(most_recent_tactics.begin(),most_recent_tactics.end(),(*i)->GetID()) == most_recent_tactics.end())) { tactics.push_back(*i); } }} if (tactics.size() == 0) { {for (tactic_container::const_iterator i = m_Tactics.begin(); i != m_Tactics.end(); ++i) { if ((*i)->CanBeSelectedAutomatically(iface) == true) { if (((*i)->GetID() != TACTIC_RETREAT) || (iface.CanRetreat() == true)) { tactics.push_back(*i); } } }} if (tactics.size() == 0) { return (TACTIC_CIRCLE_OF_DEATH); } } if (tactics.size() == 1) { return ((*(tactics.begin()))->GetID()); } while (tactics.size() > 2) { std::vector::iterator worst = tactics.end(); Stuff::Scalar worst_score = 100; {for (std::vector::iterator i = tactics.begin(); i != tactics.end(); ++i) { const Stuff::Scalar score((*i)->Evaluate(iface)); if (score <= worst_score) { worst_score = score; worst = i; } }} tactics.erase(worst); } return (tactics[Stuff::Random::GetInt() & 0x01]->GetID()); } Tactic& Registrar::GetTactic(TacticID id) const { {for (tactic_container::const_iterator i = m_Tactics.begin(); i != m_Tactics.end(); ++i) { if ((*i)->GetID() == id) { return (**i); } }} Verify(!"Should never get here!"); return (**(m_Tactics.begin())); } Auto_Ptr MW4AI::Tactics::CreateTactic(TacticInterface& i, TacticID id) { Tactic* t = 0; switch (id) { case TACTIC_STOP_AND_FIRE: { t = new StopAndFire(); break; } case TACTIC_RAM: { t = new Ram(); break; } case TACTIC_JOUST: { t = new Joust(); break; } case TACTIC_RUSH: { t = new Rush(); break; } case TACTIC_HIT_AND_RUN: { t = new HitAndRun(); break; } case TACTIC_FRONT: { t = new Front(); break; } case TACTIC_REAR: { t = new Rear(); break; } case TACTIC_CIRCLE_OF_DEATH: { t = new Circle(); break; } case TACTIC_RETREAT: { t = new Retreat(); break; } case TACTIC_BACK_UP_AND_FIRE: { t = new BackUpAndFire(); break; } case TACTIC_CIRCLE_HOVER: { t = new CircleHover(); break; } case TACTIC_STRAFE: { t = new Strafe(); break; } case TACTIC_STAND_GROUND: { t = new StandGround(); break; } case TACTIC_JUMP_AND_SHOOT: { t = new JumpAndShoot(); break; } case TACTIC_SNIPE: { t = new Snipe(); break; } case TACTIC_SHOOT_ONLY: { t = new ShootOnly(); break; } case TACTIC_DEFEND: { t = new Defend(); break; } case TACTIC_LOCAL_PATROL: { t = new LocalPatrol(); break; } case TACTIC_SURRENDER: { t = new Surrender(); break; } case TACTIC_AMBUSH: { t = new Ambush(); break; } case TACTIC_DEATH_FROM_ABOVE: { t = new DeathFromAbove(); break; } case TACTIC_HELI_POPUP: { t = new HeliPopup(); break; } case TACTIC_DIVE_BOMB: { t = new DiveBomb(); break; } case TACTIC_FAST_CIRCLE: { t = new FastCircle(); break; } case TACTIC_STARE: { t = new Stare(); break; } default: { Verify("Tactic not found!" == 0); } } if (t == 0) { return (0); } return (t); } Registrar::~Registrar() { } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // General tactics functions // void VerifyTactic(TacticID id) { Verify(id >= TACTIC_FIRST); Verify(id <= TACTIC_LAST); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tactic // void Tactic::SetExplicit(bool is_explicit) { m_Explicit = is_explicit; } bool Tactic::GetExplicit() const { return (m_Explicit); } bool Tactic::CanBeSelectedAutomatically(TacticInterface& iface) const { return (false); } Tactic::Tactic() : m_CreationTime((Stuff::Scalar)gos_GetElapsedTime()) , m_CreationMood(-1) , m_Satisfaction(1.0f) , m_LastSatisfactionUpdate(0) , m_TacticToIgnore((TacticID)0) { m_AutoEndTime = m_CreationTime + min_tactic_duration + ((max_tactic_duration - min_tactic_duration) * Stuff::Random::GetFraction()); m_CreationTime += Stuff::Random::GetFraction(); } void Tactic::SetIgnoreExplicitTactic(TacticID tactic) { m_TacticToIgnore = tactic; } TacticID Tactic::GetIgnoreExplicitTactic() const { return (m_TacticToIgnore); } Tactic::~Tactic() { } Stuff::Scalar Tactic::GetDuration() const { return ((Stuff::Scalar)gos_GetElapsedTime() - m_CreationTime); } void Tactic::Update(TacticInterface& i) { if (m_CreationMood == -1) { m_CreationMood = (Stuff::Scalar)i.GetMood(); } } bool Tactic::ShouldAbandon(TacticInterface& i) const { if (ShouldAbandonImmediately() == true) { m_Explicit = false; return (true); } if ((m_Explicit == true) || (GetDuration() < UserConstants::Instance()->Get(UserConstants::min_tactic_time)) || (m_CreationMood == -1)) { return (false); } if ((i.GetSelf().IsDerivedFrom(Building::DefaultData) == true) && (i.GetSelf().IsDerivedFrom(Airplane::DefaultData) == true)) { return (false); } Stuff::Scalar mood_delta = Stuff::Fabs(m_CreationMood - i.GetMood()); if (mood_delta > 1) { return (true); } if (gos_GetElapsedTime() > m_AutoEndTime) { return (true); } return (IsIneffective()); } bool Tactic::IsIneffective() const { if (m_Satisfaction <= min_satisfaction_threshold) { return (true); } return (false); } Stuff::Scalar Tactic::Evaluate(TacticInterface& iface) const { return (0); } void Tactic::UpdateSatisfaction() { if ((m_LastSatisfactionUpdate != 0) && (m_CreationTime + duration_to_ignore_tactic_satisfaction < gos_GetElapsedTime())) { const Stuff::Time time_delta = gos_GetElapsedTime() - m_LastSatisfactionUpdate; if (IsHappy() == true) { m_Satisfaction += (Stuff::Scalar)(satisfaction_delta_per_second * time_delta); if (m_Satisfaction > 1.0f) { m_Satisfaction = 1.0f; } } else { m_Satisfaction -= (Stuff::Scalar)(satisfaction_delta_per_second * time_delta); if (m_Satisfaction < 0.0f) { m_Satisfaction = 0.0f; } } } m_LastSatisfactionUpdate = gos_GetElapsedTime(); } bool Tactic::Finished() const { if (ShouldAbandonImmediately() == true) { return (true); } if ((m_TacticToIgnore != (TacticID)0) && (GetID() != m_TacticToIgnore)) { return (true); } return (false); } bool Tactic::ShouldIgnoreLOS() const { return (false); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tactic // inline bool Qualifies(TacticInterface& iface, UserConstants::ID min_elite, UserConstants::ID max_elite, UserConstants::ID min_mood, UserConstants::ID max_mood) { if ((iface.GetEliteLevel() < UserConstants::Instance()->Get(min_elite)) || (iface.GetEliteLevel() > UserConstants::Instance()->Get(max_elite))) { return (false); } if ((iface.GetMood() < UserConstants::Instance()->Get(min_mood)) || (iface.GetMood() > UserConstants::Instance()->Get(max_mood))) { return (false); } return (true); } Stuff::Scalar Score_FasterThanTarget(TacticInterface& iface) { Vehicle* v1 = iface.GetSelf_AsVehicle(); Vehicle* v2 = iface.GetTarget_AsVehicle(); Stuff::Scalar my_speed(0); if (v1 != 0) { my_speed = v1->GetGameModel()->maxSpeed; } Stuff::Scalar target_speed(0); if (v2 != 0) { target_speed = v2->GetGameModel()->maxSpeed; } if (my_speed > target_speed) { return (1); } return (0); } Stuff::Scalar Score_HaveMissiles(TacticInterface& iface) { Stuff::ChainIteratorOf i(&(iface.GetSelf().weaponChain)); MechWarrior4::Weapon* weapon = 0; while ((weapon = i.ReadAndNext()) != 0) { if (weapon->GetAmmoCount() > 0) // TODO: comment this back in when ammo # works correctly ... { if ((weapon->IsDerivedFrom(LRMWeaponSubsystem::DefaultData) == true) || (weapon->IsDerivedFrom(MRMWeaponSubsystem::DefaultData) == true)) { return (1); } } } return (0); } Stuff::Scalar Score_TargetFacingAway(TacticInterface& iface) { if (Conditions::InFrontOfTarget(iface,true) < 0.5f) { return (1); } return (0); } Stuff::Scalar Score_FarFromTarget(TacticInterface& iface) { if (GetApproximateLength((Stuff::Point3D)iface.GetSelf().GetLocalToWorld(), (Stuff::Point3D)iface.GetTarget().GetLocalToWorld()) > 250) { return (1); } return (0); } Stuff::Scalar Score_Gimped(TacticInterface& iface) { if (iface.NumLegsDestroyed() == 1) { return (1); } return (0); } Stuff::Scalar Score_WantsToUseVCH(TacticInterface& iface) { if (iface.GetCurrentVulnerableComponent() != 0) { return (1); } return (0); } bool Joust::CanBeSelectedAutomatically(TacticInterface& iface) const { return (Qualifies(iface, UserConstants::min_elite_joust, UserConstants::max_elite_joust, UserConstants::min_mood_joust, UserConstants::max_mood_joust)); } Stuff::Scalar Joust::Evaluate(TacticInterface& iface) const { return ( ( 0.5f * Score_FasterThanTarget(iface)) + (-1.0f * Score_FarFromTarget(iface)) + (-1.0f * Score_Gimped(iface)) + (-1.0f * Score_WantsToUseVCH(iface))); } bool Rush::CanBeSelectedAutomatically(TacticInterface& iface) const { return (Qualifies(iface, UserConstants::min_elite_rush, UserConstants::max_elite_rush, UserConstants::min_mood_rush, UserConstants::max_mood_rush)); } Stuff::Scalar Rush::Evaluate(TacticInterface& iface) const { return ( (-0.5f * Score_FasterThanTarget(iface)) + (-1.0f * Score_HaveMissiles(iface)) + ( 1.0f * Score_TargetFacingAway(iface)) + (-0.5f * Score_FarFromTarget(iface)) + ( 1.0f * Score_Gimped(iface)) + (-0.5f * Score_WantsToUseVCH(iface))); } bool HitAndRun::CanBeSelectedAutomatically(TacticInterface& iface) const { return (Qualifies(iface, UserConstants::min_elite_hitandrun, UserConstants::max_elite_hitandrun, UserConstants::min_mood_hitandrun, UserConstants::max_mood_hitandrun)); } Stuff::Scalar HitAndRun::Evaluate(TacticInterface& iface) const { return ( ( 1.0f * Score_FasterThanTarget(iface)) + (-1.0f * Score_Gimped(iface)) + (-1.0f * Score_WantsToUseVCH(iface))); } bool Rear::CanBeSelectedAutomatically(TacticInterface& iface) const { return (Qualifies(iface, UserConstants::min_elite_rear, UserConstants::max_elite_rear, UserConstants::min_mood_rear, UserConstants::max_mood_rear)); } Stuff::Scalar Rear::Evaluate(TacticInterface& iface) const { return ( ( 1.0f * Score_TargetFacingAway(iface)) + ( 1.0f * Score_Gimped(iface)) + ( 0.5f * Score_WantsToUseVCH(iface))); } bool Circle::CanBeSelectedAutomatically(TacticInterface& iface) const { return (Qualifies(iface, UserConstants::min_elite_circle, UserConstants::max_elite_circle, UserConstants::min_mood_circle, UserConstants::max_mood_circle)); } Stuff::Scalar Circle::Evaluate(TacticInterface& iface) const { return ( ( 0.5f * Score_FasterThanTarget(iface)) + ( 1.0f * Score_HaveMissiles(iface)) + ( 0.5f * Score_FarFromTarget(iface)) + (-0.5f * Score_Gimped(iface)) + ( 1.0f * Score_WantsToUseVCH(iface))); } bool Retreat::CanBeSelectedAutomatically(TacticInterface& iface) const { return (Qualifies(iface, UserConstants::min_elite_retreat, UserConstants::max_elite_retreat, UserConstants::min_mood_retreat, UserConstants::max_mood_retreat)); } Stuff::Scalar Retreat::Evaluate(TacticInterface& iface) const { return ( ( 1.0f * Score_FasterThanTarget(iface)) + (-1.0f * Score_TargetFacingAway(iface)) + (-1.0f * Score_FarFromTarget(iface)) + (-1.0f * Score_Gimped(iface)) + (-0.5f * Score_WantsToUseVCH(iface))); } bool DeathFromAbove::CanBeSelectedAutomatically(TacticInterface& iface) const { if ((iface.CanJump() == false) || (iface.GetTarget().IsDerivedFrom(Mech::DefaultData) == false)) { return (false); } return (Qualifies(iface, UserConstants::min_elite_dfa, UserConstants::max_elite_dfa, UserConstants::min_mood_dfa, UserConstants::max_mood_dfa)); } Stuff::Scalar DeathFromAbove::Evaluate(TacticInterface& iface) const { return ( ( 1.0f * Score_FasterThanTarget(iface)) + (-1.0f * Score_FarFromTarget(iface)) + (-2.0f * Score_Gimped(iface)) + (-0.5f * Score_WantsToUseVCH(iface))); }