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.
610 lines
14 KiB
C++
610 lines
14 KiB
C++
#include "MW4Headers.hpp"
|
|
|
|
#include <Adept\DamageObject.hpp>
|
|
#include "AI_FindObject.hpp"
|
|
#include "Sensor.hpp"
|
|
#include "MWDamageObject.hpp"
|
|
#include "rail_move.hpp"
|
|
#include "CombatAI.hpp"
|
|
#include "MWPlayer.hpp"
|
|
#include "MWMission.hpp"
|
|
#include "VehicleInterface.hpp"
|
|
#include "MFB.hpp"
|
|
|
|
|
|
const Stuff::Scalar min_distance = 10.0f;
|
|
const Stuff::Scalar max_distance = 1000.0f;
|
|
const Stuff::Scalar min_tonnage = 40.0f;
|
|
const Stuff::Scalar max_tonnage = 105.0f;
|
|
const Stuff::Scalar find_object_shot_by_time = 2.0f;
|
|
const Stuff::Scalar building_data_refresh_rate = 4.0f;
|
|
const Stuff::Scalar find_object_cache_delay = 3.0f;
|
|
|
|
extern __int64 tCombatAITime_FindObject;
|
|
|
|
|
|
using namespace MW4AI;
|
|
using namespace MW4AI::FindObject;
|
|
using namespace MechWarrior4;
|
|
typedef SensorData sensor_data;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Scoring functions
|
|
//
|
|
|
|
inline Stuff::Scalar ScoreDistance(Adept::Entity& src, Adept::Entity& dest)
|
|
{
|
|
Vector3D delta;
|
|
delta.Subtract((Point3D)src.GetLocalToWorld(),(Point3D)dest.GetLocalToWorld());
|
|
delta.y = 0;
|
|
|
|
return (NormalizedValue(delta.GetLengthSquared(),
|
|
min_distance * min_distance,
|
|
max_distance * max_distance,
|
|
true));
|
|
}
|
|
|
|
inline Stuff::Scalar ScoreTonnage(Adept::Entity& entity)
|
|
{
|
|
if (entity.IsDerivedFrom(MWObject::DefaultData) == false)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
MWObject* object = Cast_Object(MWObject*,&entity);
|
|
return (NormalizedValue(object->GetTonage(),min_tonnage,max_tonnage));
|
|
}
|
|
|
|
inline Stuff::Scalar ScoreDamage(Adept::Entity& object)
|
|
{
|
|
if (object.IsDerivedFrom(MWObject::DefaultData) == false)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
MWObject* v = Cast_Object(MWObject*,&object);
|
|
|
|
Stuff::SortedChainIteratorOf<MWInternalDamageObject*,int> i(&(v->internalDamageObjects));
|
|
|
|
MWInternalDamageObject* o;
|
|
Stuff::Scalar total_damage = 0;
|
|
Stuff::Scalar max_damage = 0;
|
|
|
|
while ((o = i.ReadAndNext()) != 0)
|
|
{
|
|
total_damage += o->currentInternalDamage;
|
|
max_damage += o->baseInternalDamage;
|
|
}
|
|
|
|
if (Small_Enough(max_damage))
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
return (NormalizedValue(total_damage,max_damage * 0.66f,max_damage,true)); // we crank up the minimum so 33% damage is considered 100% score
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// ScoreByCriteria()
|
|
// Scores the given object (with a normalized score) using the given find criteria
|
|
|
|
Stuff::Scalar ScoreByCriteria(MWObject& src,
|
|
MWObject& target,
|
|
int criteria)
|
|
{
|
|
Verify((MW4AI::FindObject::Criteria)criteria >= MW4AI::FindObject::FC_FIRST);
|
|
Verify((MW4AI::FindObject::Criteria)criteria <= MW4AI::FindObject::FC_LAST);
|
|
|
|
switch ((MW4AI::FindObject::Criteria)criteria)
|
|
{
|
|
case MW4AI::FindObject::FC_BEST_TARGET:
|
|
{
|
|
return (0.45f * ScoreDistance(src,target) +
|
|
0.35f * ScoreDamage(target) +
|
|
0.2f * ScoreTonnage(target));
|
|
}
|
|
case MW4AI::FindObject::FC_GREATEST_THREAT:
|
|
{
|
|
return (0.6f * ScoreDistance(src,target) +
|
|
0.4f * ScoreTonnage(target));
|
|
}
|
|
case MW4AI::FindObject::FC_LEAST_THREAT:
|
|
{
|
|
return (1.0f - (0.6f * ScoreDistance(src,target) +
|
|
0.4f * ScoreTonnage(target)));
|
|
}
|
|
case MW4AI::FindObject::FC_MOST_DAMAGED:
|
|
{
|
|
return (ScoreDamage(target));
|
|
}
|
|
case MW4AI::FindObject::FC_NEAREST:
|
|
{
|
|
return (ScoreDistance(src,target));
|
|
}
|
|
}
|
|
|
|
Verify(!"Should never get here.");
|
|
return (ScoreDistance(src,target));
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// QualifiesByAlignment
|
|
// Indicates whether a given sensor data object qualifies by testing its alignment
|
|
// against the given alignment-specification value.
|
|
|
|
bool QualifiesByAlignment(MWObject& src,
|
|
MWObject& target,
|
|
int fa_alignment)
|
|
{
|
|
Verify(fa_alignment != 0); // value of 0 makes no sense
|
|
Verify(fa_alignment <= MW4AI::FindObject::FA_ANY);
|
|
|
|
if (&src == &target)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
switch (src.GetRelativeAlignment(target.GetAlignment()))
|
|
{
|
|
case Adept::Entity::Enemy:
|
|
{
|
|
return ((fa_alignment & MW4AI::FindObject::FA_ENEMY) != 0);
|
|
}
|
|
|
|
case Adept::Entity::Player:
|
|
{
|
|
return ((fa_alignment & MW4AI::FindObject::FA_FRIENDLY) != 0);
|
|
}
|
|
|
|
case Adept::Entity::DefaultAlignment:
|
|
{
|
|
return ((fa_alignment & MW4AI::FindObject::FA_NEUTRAL) != 0);
|
|
}
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// QualifiesByType
|
|
// Indicates whether a given sensor data object qualifies by testing its
|
|
// object type against the given alignment-specification value.
|
|
|
|
bool QualifiesByType(MWObject& src,
|
|
MWObject& target,
|
|
int ft_type)
|
|
{
|
|
Verify(ft_type != 0); // 0 is invalid: you must specify a type
|
|
|
|
if (target.GetTargetDesirability() == MWObject::desirability_never)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
if (ft_type & FT_BUILDING)
|
|
{
|
|
if (target.IsDerivedFrom(Building::DefaultData) == true)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if (ft_type & FT_MFB)
|
|
{
|
|
if (target.IsDerivedFrom(MFB::DefaultData) == true)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if ((target.GetTargetDesirability() == MWObject::desirability_invalid) &&
|
|
(target.IsPlayerVehicle() == false))
|
|
{
|
|
if (target.GetAI() == 0)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
if ((target.weaponChain.IsEmpty() == true) &&
|
|
(target.IsDerivedFrom(Mech::DefaultData) == false) &&
|
|
(!(ft_type & FT_UNARMED)))
|
|
{
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
if (ft_type & FT_MECH)
|
|
{
|
|
if (target.IsDerivedFrom(Mech::DefaultData) == true)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if (ft_type & FT_AIRPLANE)
|
|
{
|
|
if (target.IsDerivedFrom(Airplane::DefaultData) == true)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if (ft_type & FT_VEHICLE)
|
|
{
|
|
if ((target.IsDerivedFrom(Vehicle::DefaultData) == true) &&
|
|
(target.IsDerivedFrom(MFB::DefaultData) == false) &&
|
|
(target.IsDerivedFrom(Mech::DefaultData) == false) &&
|
|
(target.IsDerivedFrom(Airplane::DefaultData) == false))
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if (ft_type & FT_BOAT)
|
|
{
|
|
if (target.IsDerivedFrom(Boat::DefaultData) == true)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
if (ft_type & FT_TURRET)
|
|
{
|
|
if (target.IsDerivedFrom(Building::DefaultData) == true)
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
|
|
bool Qualifies(MWObject& src,
|
|
MWObject& target,
|
|
int fa_alignment,
|
|
int ft_type)
|
|
{
|
|
if (QualifiesByAlignment(src,target,fa_alignment) == false)
|
|
{
|
|
return (false);
|
|
}
|
|
|
|
return (QualifiesByType(src,target,ft_type));
|
|
}
|
|
|
|
|
|
void FillNameTableData(MWObject& src,
|
|
std::vector<MWObject*>& objects_to_search)
|
|
{
|
|
Adept::NameTable* table = Adept::NameTable::GetInstance();
|
|
|
|
{
|
|
int size(table->nameTableArray[NameTable::VehicleArray].GetLength());
|
|
|
|
{for (int i = 0;
|
|
i < size;
|
|
++i)
|
|
{
|
|
NameTableEntry *data = &table->nameTableArray [NameTable::VehicleArray][i];
|
|
Adept::Entity* ent = data->dataPointer->GetCurrent();
|
|
|
|
if ((ent != 0) &&
|
|
(ent->IsDestroyed() == false) &&
|
|
(ent != &src) &&
|
|
(ent->IsDerivedFrom(MWObject::DefaultData) == true))
|
|
{
|
|
objects_to_search.push_back(Cast_Object(MWObject*,ent));
|
|
}
|
|
}}
|
|
}
|
|
|
|
{
|
|
int size(table->nameTableArray[NameTable::PlayerArray].GetLength());
|
|
|
|
{for (int i = 0;
|
|
i < size;
|
|
++i)
|
|
{
|
|
NameTableEntry *data = &table->nameTableArray [NameTable::PlayerArray][i];
|
|
Adept::Entity* ent = data->dataPointer->GetCurrent ();
|
|
|
|
if ((ent != 0) &&
|
|
(ent->IsDerivedFrom(Adept::Player::DefaultData) == true))
|
|
{
|
|
Adept::Player* player = Cast_Object(Adept::Player*,ent);
|
|
|
|
if ((player->vehicle != 0) &&
|
|
(player->vehicle->IsDestroyed() == false) &&
|
|
(player->vehicle != &src) &&
|
|
(player->vehicle->IsDerivedFrom(MWObject::DefaultData) == true))
|
|
{
|
|
objects_to_search.push_back(Cast_Object(MWObject*,player->vehicle));
|
|
}
|
|
}
|
|
}}
|
|
}
|
|
}
|
|
|
|
void FillSensorData(MWObject& src,
|
|
const Sensor::sensorDataArray& sensed_data,
|
|
int number_of_contacts,
|
|
std::vector<MWObject*>& sensor_data_list)
|
|
{
|
|
if ((src.GetAI() != 0) &&
|
|
(src.GetAI()->GetExtendedSensorData(sensor_data_list) == true))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (number_of_contacts == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sensor_data_list.reserve(number_of_contacts);
|
|
|
|
{for (int i = 0;
|
|
i < number_of_contacts;
|
|
++i)
|
|
{
|
|
if ((sensed_data[i] != 0) &&
|
|
(sensed_data[i]->object.GetCurrent() != 0) &&
|
|
(sensed_data[i]->object.GetCurrent() != &src))
|
|
{
|
|
sensor_data_list.push_back(sensed_data[i]->object.GetCurrent());
|
|
}
|
|
}}
|
|
}
|
|
|
|
|
|
// FindObject(): finds an object given the given sensor (sensor) and qualifier (object_type)
|
|
|
|
long MW4AI::FindObject::Find(MWObject& src,
|
|
int fa_alignment,
|
|
int ft_type,
|
|
int fc_criteria,
|
|
int ff_flags,
|
|
Stuff::Scalar distance_limit,
|
|
const Adept::ObjectID* except_object)
|
|
{
|
|
AI_LOGIC("Find Object");
|
|
TIME_FUNCTION(tCombatAITime_FindObject);
|
|
|
|
Verify(ff_flags <= FF_WHO_SHOT + FF_SEEPLAYER + FF_LOOK_EVERYWHERE + FF_TARGETLOS);
|
|
|
|
if (distance_limit == 0)
|
|
{
|
|
distance_limit = MW4AI::FindObject::DISTANCE_LIMIT;
|
|
}
|
|
|
|
if (src.m_FindObjectList == 0)
|
|
{
|
|
src.m_FindObjectList = new MW4AI::FindObjectList;
|
|
}
|
|
|
|
std::vector<MWObject*>& objects_to_search = src.m_FindObjectList->objects;
|
|
objects_to_search.clear();
|
|
|
|
if ((ff_flags & MW4AI::FindObject::FF_SEEPLAYER) &&
|
|
(MWMission::GetInstance()->GetGameModel() != 0) &&
|
|
(Adept::Player::GetInstance() != 0))
|
|
{
|
|
MWPlayer* p = Cast_Object(MWPlayer*,Adept::Player::GetInstance());
|
|
if ((p != 0) &&
|
|
(p->GetInterface() != 0) &&
|
|
(p->GetInterface()->vehicle != 0))
|
|
{
|
|
Vehicle* vehicle = p->GetInterface()->vehicle;
|
|
|
|
if ((QualifiesByAlignment(src,*vehicle,fa_alignment) == true) &&
|
|
(vehicle->IsDestroyed() == false) &&
|
|
(vehicle->objectID != src.objectID))
|
|
{
|
|
const Adept::Mission::GameModel* game_model = MWMission::GetInstance()->GetGameModel();
|
|
Stuff::Scalar fog_distance = game_model->m_generalFogEnd;
|
|
|
|
if (fog_distance > 1200)
|
|
{
|
|
fog_distance = 1200;
|
|
}
|
|
|
|
if (fog_distance > distance_limit)
|
|
{
|
|
fog_distance = distance_limit;
|
|
}
|
|
|
|
Stuff::Point3D player_vehicle_pos(vehicle->GetLineOfSight());
|
|
Stuff::Point3D my_pos(src.GetLineOfSight());
|
|
|
|
Stuff::LinearMatrix4D src_los;
|
|
src.GetLineOfSight(src_los);
|
|
|
|
if ((GetLengthSquared(player_vehicle_pos,my_pos) < fog_distance * fog_distance) &&
|
|
(MatrixFacesPoint(src_los,player_vehicle_pos,Stuff::Pi_Over_4) == true))
|
|
{
|
|
if ((src.m_FindObjectCache != 0) &&
|
|
(src.m_FindObjectCache->m_RayCastTime + find_object_cache_delay < gos_GetElapsedTime()))
|
|
{
|
|
delete src.m_FindObjectCache;
|
|
src.m_FindObjectCache = 0;
|
|
}
|
|
|
|
MW4AI::FireData::HIT_RESULT hit_result;
|
|
|
|
if (src.m_FindObjectCache != 0)
|
|
{
|
|
hit_result = src.m_FindObjectCache->m_HitResult;
|
|
}
|
|
else
|
|
{
|
|
FireData fire_data(src,my_pos,player_vehicle_pos);
|
|
fire_data.Project();
|
|
hit_result = fire_data.GetHitResult(vehicle);
|
|
|
|
src.m_FindObjectCache = new MW4AI::FindObjectCache((Stuff::Scalar)gos_GetElapsedTime(),hit_result);
|
|
}
|
|
|
|
if (hit_result == FireData::HIT_TARGET)
|
|
{
|
|
if (src.GetSensor() == 0)
|
|
{
|
|
return (vehicle->objectID);
|
|
}
|
|
else
|
|
{
|
|
objects_to_search.push_back(vehicle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((src.GetSensor() == 0) &&
|
|
(objects_to_search.size() == 0))
|
|
{
|
|
return (-2);
|
|
}
|
|
|
|
if ((ff_flags & MW4AI::FindObject::FF_WHO_SHOT) &&
|
|
(src.GetAI() != 0))
|
|
{
|
|
Adept::Entity* e = src.GetAI()->ShotBy(gos_GetElapsedTime() - find_object_shot_by_time);
|
|
|
|
if ((e != 0) &&
|
|
(e != &src) &&
|
|
(e->IsDerivedFrom(MWObject::DefaultData) == true) &&
|
|
(e->IsDestroyed() == false))
|
|
{
|
|
MWObject* v = Cast_Object(MWObject*,e);
|
|
Check_Object(v);
|
|
|
|
if (QualifiesByAlignment(src,*v,fa_alignment) == true)
|
|
{
|
|
return (v->objectID);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ff_flags & MW4AI::FindObject::FF_LOOK_EVERYWHERE)
|
|
{
|
|
FillNameTableData(src,objects_to_search);
|
|
}
|
|
else
|
|
{
|
|
FillSensorData(src,
|
|
src.GetSensor()->RefreshAndGetSensorData(),
|
|
src.GetSensor()->numberOfContacts,
|
|
objects_to_search);
|
|
|
|
if (ft_type & FT_BUILDING)
|
|
{
|
|
if (src.GetSensor()->GetLastTimeUpdatedBuildingData() < gos_GetElapsedTime() + building_data_refresh_rate + Stuff::Random::GetFraction())
|
|
{
|
|
src.GetSensor()->UpdateBuildingData();
|
|
}
|
|
|
|
FillSensorData(src,
|
|
src.GetSensor()->GetBuildingData(),
|
|
src.GetSensor()->numberOfBuildingContacts,
|
|
objects_to_search);
|
|
}
|
|
}
|
|
|
|
if (objects_to_search.size() == 0)
|
|
{
|
|
return (-2);
|
|
}
|
|
|
|
const Stuff::Scalar distance_limit_squared = distance_limit * distance_limit;
|
|
|
|
Stuff::Scalar best_score = 0;
|
|
std::vector<MWObject*>::const_iterator best_found = objects_to_search.end();
|
|
|
|
Stuff::Point3D my_pos(src.GetLocalToWorld());
|
|
|
|
{for (std::vector<MWObject*>::const_iterator i = objects_to_search.begin();
|
|
i != objects_to_search.end();
|
|
++i)
|
|
{
|
|
if ((except_object != 0) &&
|
|
((*i)->objectID == *except_object))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ff_flags & MW4AI::FindObject::FF_TARGETLOS)
|
|
{
|
|
|
|
// Test line of sight
|
|
Stuff::Point3D target_pos((*i)->GetLineOfSight());
|
|
Stuff::Point3D my_pos(src.GetLineOfSight());
|
|
FireData fire_data(src,my_pos,target_pos);
|
|
fire_data.Project();
|
|
if (fire_data.GetHitResult((*i)) != FireData::HIT_TARGET)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Qualifies(src,**i,fa_alignment,ft_type) == true)
|
|
{
|
|
Point3D pos((*i)->GetLocalToWorld());
|
|
|
|
Scalar distance(0);
|
|
|
|
if (src.GetAI() != 0)
|
|
{
|
|
distance = src.GetAI()->GetLeastSquaredSensorDistance(pos);
|
|
}
|
|
|
|
if (distance == 0)
|
|
{
|
|
Vector3D delta;
|
|
delta.Subtract(my_pos,pos);
|
|
delta.y = 0;
|
|
|
|
distance = delta.GetLengthSquared();
|
|
}
|
|
|
|
if (distance < distance_limit_squared)
|
|
{
|
|
Stuff::Scalar s;
|
|
|
|
if ((*i)->GetTargetDesirability() == MWObject::desirability_invalid)
|
|
{
|
|
s = ScoreByCriteria(src,**i,fc_criteria);
|
|
}
|
|
else
|
|
{
|
|
Verify((*i)->GetTargetDesirability() != MWObject::desirability_never);
|
|
s = (*i)->GetTargetDesirability();
|
|
}
|
|
|
|
Verify(s >= 0);
|
|
Verify(s <= 1);
|
|
|
|
if (s >= best_score)
|
|
{
|
|
if (s == 1)
|
|
{
|
|
return ((*i)->objectID);
|
|
}
|
|
|
|
best_score = s;
|
|
best_found = i;
|
|
}
|
|
}
|
|
}
|
|
}}
|
|
|
|
if (best_found == objects_to_search.end())
|
|
{
|
|
return (-2); // -2 = ABL constant for nothing found
|
|
}
|
|
|
|
return ((*best_found)->objectID);
|
|
}
|