Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
690 lines
16 KiB
C++
690 lines
16 KiB
C++
#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<Tactic> 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<TacticID>& 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<Tactic*> 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<Tactic*>::iterator worst = tactics.end();
|
|
Stuff::Scalar worst_score = 100;
|
|
|
|
{for (std::vector<Tactic*>::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::Tactic> 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<MechWarrior4::Weapon*> 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)));
|
|
}
|