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.
490 lines
12 KiB
C++
490 lines
12 KiB
C++
#include "MW4Headers.hpp"
|
|
|
|
#include "AI_FireStyle.hpp"
|
|
#include "Weapon.hpp"
|
|
#include "AI_Weapons.hpp"
|
|
#include "aiutils.hpp"
|
|
#include "MWObject.hpp"
|
|
#include "AI_FireData.hpp"
|
|
#include "ProjectileWeapon.hpp"
|
|
#include "HighExplosiveWeapon.hpp"
|
|
#include "LongTomWeapon.hpp"
|
|
#include "ArtilleryWeapon.hpp"
|
|
#include "NetWeapon.hpp"
|
|
// MSL 5.03 RTX
|
|
//#include "rtxweaponsub.hpp"
|
|
#include <Adept\Map.hpp>
|
|
#include <Adept\Site.hpp>
|
|
#include "NarcWeaponSubsystem.hpp"
|
|
#include "SubsystemClassData.hpp"
|
|
#include "MWDamageObject.hpp"
|
|
#include "ai.hpp"
|
|
|
|
#pragma warning(push)
|
|
#include <stlport\algorithm>
|
|
#pragma warning(pop)
|
|
|
|
|
|
|
|
const double linked_fire_shot_delay = 0.3f;
|
|
const Stuff::Scalar min_high_explosive_distance = 80.0f;
|
|
|
|
extern __int64 tCombatAITime_WeaponFiring;
|
|
extern __int64 tWeaponFiring;
|
|
|
|
using namespace MW4AI;
|
|
|
|
bool FireStyles::FireStyle::gHeatManagementEnabled = false; // TODO: remove this eventually ...
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// General fire styles functions
|
|
//
|
|
|
|
Auto_Ptr<FireStyles::FireStyle> FireStyles::CreateFireStyle(FireStyleID id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case FireStyles::FIRE_MAXIMUM: { return (new FireStyles::MaximumFire()); }
|
|
case FireStyles::FIRE_OPPORTUNITY: { return (new FireStyles::OpportunityFire()); }
|
|
}
|
|
|
|
Verify(!"Should never get here.");
|
|
return (new FireStyles::MaximumFire());
|
|
}
|
|
|
|
|
|
void FireStyles::VerifyFireStyle(FireStyleID id)
|
|
{
|
|
Verify(id >= FireStyles::FIRE_FIRST);
|
|
Verify(id <= FireStyles::FIRE_LAST);
|
|
}
|
|
|
|
std::string FireStyles::FireStyleToString(FireStyleID id)
|
|
{
|
|
VerifyFireStyle(id);
|
|
|
|
switch (id)
|
|
{
|
|
case FireStyles::FIRE_MAXIMUM: { return ("Maximum"); }
|
|
case FireStyles::FIRE_OPPORTUNITY: { return ("Opportunity"); }
|
|
default:
|
|
{
|
|
Verify("Should not get here" == 0);
|
|
}
|
|
}
|
|
|
|
return ("");
|
|
}
|
|
|
|
MechWarrior4::MWObject* SelfOrParentVehicle(Adept::Entity* entity)
|
|
{
|
|
if ((entity == 0) ||
|
|
(entity->IsDerivedFrom(MechWarrior4::MWObject::DefaultData) == false))
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
MechWarrior4::MWObject* mwobject = Cast_Object(MechWarrior4::MWObject*,entity);
|
|
Check_Object(mwobject);
|
|
|
|
if (mwobject->GetParentVehicle() == 0)
|
|
{
|
|
return (mwobject);
|
|
}
|
|
|
|
return (mwobject->GetParentVehicle());
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Functors: these plug in to TrueForAllWeapons(), TrueForAnyWeapon(), etc. to perform the appropriate task or query
|
|
//
|
|
|
|
#define FUNCTOR_HEADER(classname) \
|
|
public: \
|
|
classname(const FireParamPackage& params) \
|
|
: m_Params(params) \
|
|
{ \
|
|
} \
|
|
private: \
|
|
const FireParamPackage& m_Params;
|
|
|
|
#define REMOVE true
|
|
#define NO_REMOVE false
|
|
|
|
class Fire_Functor
|
|
{
|
|
public:
|
|
virtual bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update) = 0;
|
|
};
|
|
|
|
class Functor_CanFire
|
|
: public Fire_Functor
|
|
{
|
|
FUNCTOR_HEADER(Functor_CanFire);
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Check_Object(&weapon);
|
|
|
|
if ((m_Params.can_spend_ammo == false) &&
|
|
(weapon.GetAmmoCount() != -1))
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
if (WeaponCanFire(weapon,m_Params.fire_data.GetLine().m_length,m_Params.min_delay,m_Params.max_delay) == false)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
class Functor_CorrectFireSource
|
|
: public Fire_Functor
|
|
{
|
|
FUNCTOR_HEADER(Functor_CorrectFireSource);
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Check_Object(&weapon);
|
|
|
|
switch (m_Params.where_to_fire_from)
|
|
{
|
|
case FIRE_FROM_LEFT_ARM:
|
|
{
|
|
if (WeaponIsArmWeapon(weapon,true) == false)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case FIRE_FROM_RIGHT_ARM:
|
|
{
|
|
if (WeaponIsArmWeapon(weapon,false) == false)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
class Functor_DoesDamage
|
|
: public Fire_Functor
|
|
{
|
|
FUNCTOR_HEADER(Functor_DoesDamage);
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Check_Object(&weapon);
|
|
|
|
if (weapon.IsDerivedFrom(MechWarrior4::ProjectileWeapon::DefaultData) == true)
|
|
{
|
|
MechWarrior4::ProjectileWeapon* projectile_weapon = Cast_Object(MechWarrior4::ProjectileWeapon*,&weapon);
|
|
const ProjectileWeapon__GameModel* game_model = projectile_weapon->GetGameModel();
|
|
|
|
if (game_model->damageAmount <= 0)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
if (weapon.IsDerivedFrom(MechWarrior4::HighExplosiveWeapon::DefaultData) == true)
|
|
{
|
|
if (m_Params.can_commit_suicide == false)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
if (m_Params.fire_data.GetLine().m_length > min_high_explosive_distance)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
|
|
if (weapon.IsDerivedFrom(MechWarrior4::ArtilleryWeapon::DefaultData) == true)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
if ((m_Params.can_fire_narc == false) &&
|
|
(weapon.IsDerivedFrom(MechWarrior4::NarcWeapon::DefaultData) == true))
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
}
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
class Functor_Fire
|
|
: public Fire_Functor
|
|
{
|
|
FUNCTOR_HEADER(Functor_Fire);
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Check_Object(&weapon);
|
|
Verify(WeaponCanFire(weapon,0,m_Params.min_delay,m_Params.max_delay) == true);
|
|
|
|
std::pair<FireData,Stuff::Time> result = CalculateFireData(m_Params,weapon);
|
|
|
|
if ((m_Params.per_weapon_raycasting == true) &&
|
|
(weapon.IsDerivedFrom(MechWarrior4::MissileWeapon::DefaultData) == false))
|
|
{
|
|
FireData ray_cast_data(m_Params.fire_data.GetSource(),
|
|
weapon,
|
|
result.first.GetDest());
|
|
ray_cast_data.Project();
|
|
|
|
Adept::Entity* non_const_who_to_hit = const_cast<Adept::Entity*>(m_Params.who_to_hit);
|
|
|
|
switch (ray_cast_data.GetHitResult(non_const_who_to_hit))
|
|
{
|
|
case FireData::HIT_NOTHING:
|
|
case FireData::HIT_SOMETHING_ELSE:
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
COMBAT_LOGIC("Update Attacking::Firing::With Current Query::Weapon");
|
|
TIME_FUNCTION(tWeaponFiring);
|
|
TIME_FUNCTION(tCombatAITime_WeaponFiring);
|
|
|
|
result.first.GetQuery().m_raySource = m_Params.component;
|
|
|
|
weapon.FireWeapon(&(result.first.GetQuery()),result.second, Stuff::Point3D::Identity);
|
|
}
|
|
|
|
// begin: fill in all the weapon_update members
|
|
weapon_update.targetOffset = Point3D::Identity;
|
|
|
|
if ((weapon_update.target.GetCurrent() != 0) &&
|
|
(weapon_update.target.GetCurrent() != Adept::Map::GetInstance()))
|
|
{
|
|
weapon_update.targetOffset.MultiplyByInverse(m_Params.fire_data.GetDest(),weapon_update.target.GetCurrent()->GetLocalToWorld());
|
|
}
|
|
else
|
|
{
|
|
weapon_update.targetOffset = m_Params.fire_data.GetDest();
|
|
}
|
|
|
|
weapon_update.lockTime = (Stuff::Scalar)result.second;
|
|
|
|
weapon_update.weaponFired |= (0x1 << weapon.GetWeaponID());
|
|
|
|
if (weapon.IsDerivedFrom(MechWarrior4::MissileWeapon::DefaultData) == true)
|
|
{
|
|
MechWarrior4::MissileWeapon* missile_weapon = Cast_Object(MechWarrior4::MissileWeapon*,&weapon);
|
|
|
|
weapon_update.AntiWeaponCount[weapon.GetWeaponID()] = missile_weapon->GetAMSNumber();
|
|
}
|
|
|
|
// end
|
|
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
class Functor_HeatLevelOK
|
|
: public Fire_Functor
|
|
{
|
|
public:
|
|
Functor_HeatLevelOK(const FireParamPackage& params)
|
|
: m_Params(params)
|
|
, m_HeatGeneratedSoFar(0)
|
|
{
|
|
}
|
|
|
|
private:
|
|
const FireParamPackage& m_Params;
|
|
Stuff::Scalar m_HeatGeneratedSoFar;
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Check_Object(&weapon);
|
|
|
|
Stuff::Scalar heat = weapon.GetTotalHeatGenerated();
|
|
|
|
if ((m_Params.can_generate_heat == false) &&
|
|
(heat > 0))
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
if (FireStyles::FireStyle::gHeatManagementEnabled == false)
|
|
{
|
|
return (NO_REMOVE);
|
|
}
|
|
|
|
if (weapon.IsDerivedFrom(MechWarrior4::HighExplosiveWeapon::DefaultData) == true)
|
|
{
|
|
return (NO_REMOVE);
|
|
}
|
|
|
|
if (m_Params.current_heat + m_HeatGeneratedSoFar + heat >= (m_Params.max_heat * 0.9f))
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
m_HeatGeneratedSoFar += heat;
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
class Functor_HitsEntity
|
|
: public Fire_Functor
|
|
{
|
|
public:
|
|
Functor_HitsEntity(const FireParamPackage& params, Adept::Entity* entity)
|
|
: m_Params(params)
|
|
, m_Entity(entity)
|
|
{
|
|
}
|
|
|
|
private:
|
|
const FireParamPackage& m_Params;
|
|
Adept::Entity* m_Entity;
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Verify(WeaponCanFire(weapon,0,m_Params.min_delay,m_Params.max_delay) == true);
|
|
|
|
if (weapon.IsDerivedFrom(MechWarrior4::HighExplosiveWeapon::DefaultData) == true)
|
|
{
|
|
return (NO_REMOVE);
|
|
}
|
|
|
|
if ((weapon.GetGameModel() != 0) &&
|
|
(weapon.GetGameModel()->splashRadius > m_Params.fire_data.GetLine().m_length))
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
|
|
class Functor_WontFriendlyFire
|
|
: public Fire_Functor
|
|
{
|
|
public:
|
|
Functor_WontFriendlyFire(const FireParamPackage& params)
|
|
: m_Params(params)
|
|
{
|
|
}
|
|
|
|
private:
|
|
const FireParamPackage& m_Params;
|
|
|
|
public:
|
|
bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
if (m_Params.fire_data.GetSource().IsDerivedFrom(MWObject::DefaultData) == true)
|
|
{
|
|
MWObject* mwobject = Cast_Object(MWObject*,&m_Params.fire_data.GetSource());
|
|
if (MightHitFriendlies(m_Params.fire_data.GetLine(),GetMaxWeaponDistance(weapon),*mwobject,m_Params.intended_target) == true)
|
|
{
|
|
return (REMOVE);
|
|
}
|
|
}
|
|
|
|
return (NO_REMOVE);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
#undef FUNCTOR_EXECUTE_FUNCTION
|
|
|
|
typedef std::vector<MechWarrior4::Weapon*> weapon_list;
|
|
typedef std::vector<Fire_Functor*> functor_list;
|
|
|
|
void GetQualifiedWeapons(Stuff::ChainOf<MechWarrior4::Weapon*>& weapons,
|
|
functor_list& qualifiers,
|
|
weapon_list& weapons_out,
|
|
MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&weapons);
|
|
MechWarrior4::Weapon* weapon = 0;
|
|
while ((weapon = i.ReadAndNext()) != 0)
|
|
{
|
|
Check_Object(weapon);
|
|
|
|
if (weapon->IsDestroyed() == false)
|
|
{
|
|
bool qualified = true;
|
|
|
|
{for (functor_list::iterator i = qualifiers.begin();
|
|
i != qualifiers.end();
|
|
++i)
|
|
{
|
|
if ((*i)->Execute(*weapon,weapon_update) == REMOVE)
|
|
{
|
|
qualified = false;
|
|
break;
|
|
}
|
|
}}
|
|
|
|
if (qualified == true)
|
|
{
|
|
weapons_out.push_back(weapon);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Maximum fire : fire as much as you can, as often as you can
|
|
//
|
|
|
|
void FireStyles::MaximumFire::Execute(const FireParamPackage& params,
|
|
Stuff::ChainOf<MechWarrior4::Weapon *>& weapons,
|
|
Adept::Entity* target,
|
|
MechWarrior4::WeaponUpdate& weapon_update)
|
|
{
|
|
Functor_CanFire can_fire(params); // which weapons can hit the target point?
|
|
Functor_CorrectFireSource correct_fire_source(params); // which weapons fire from the correct source (left arm only/right arm only/any & all)?
|
|
Functor_DoesDamage does_damage(params); // which weapons actually do some kind of damage?
|
|
Functor_HeatLevelOK heat_level_ok(params); // which weapons can fire without overheating?
|
|
Functor_HitsEntity hits_entity(params,target); // which weapons hit the intended entity?
|
|
Functor_WontFriendlyFire no_friendly_fire(params); // which weapons have no chance of causing friendly fire
|
|
Functor_Fire fire(params); // OK, fire!
|
|
|
|
functor_list qualifiers;
|
|
qualifiers.push_back(&can_fire);
|
|
qualifiers.push_back(&correct_fire_source);
|
|
qualifiers.push_back(&does_damage);
|
|
qualifiers.push_back(&heat_level_ok);
|
|
qualifiers.push_back(&hits_entity);
|
|
qualifiers.push_back(&no_friendly_fire);
|
|
qualifiers.push_back(&fire);
|
|
|
|
weapon_list w;
|
|
GetQualifiedWeapons(weapons,qualifiers,w,weapon_update);
|
|
}
|