/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// AI.cpp
// The Artificial Intelligence system
// Author: Michael S. Booth, November 2000
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRCDebug.h"
#include "Common/GameState.h"
#include "Common/PerfTimer.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "Common/XferCRC.h"
#include "GameLogic/AI.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/SidesList.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Weapon.h"
extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color);
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE CLASS ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
void TAiData::addSideInfo(AISideInfo *infoToAdd)
{
infoToAdd->m_next = m_sideInfo;
m_sideInfo = infoToAdd;
}
void TAiData::addFactionBuildList(AISideBuildList *buildList)
{
AISideBuildList *info = m_sideBuildLists;
while (info) {
if (buildList->m_side == info->m_side) {
if (info->m_buildList)
info->m_buildList->deleteInstance();
info->m_buildList = buildList->m_buildList;
buildList->m_buildList = NULL;
buildList->m_next = NULL;
buildList->deleteInstance();
return;
}
info = info->m_next;
}
buildList->m_next = m_sideBuildLists;
m_sideBuildLists = buildList;
}
TAiData::~TAiData()
{
AISideInfo *info = m_sideInfo;
m_sideInfo = NULL;
while (info) {
AISideInfo *cur = info;
info = info->m_next;
cur->deleteInstance();
}
AISideBuildList *build = m_sideBuildLists;
m_sideBuildLists = NULL;
while (build) {
AISideBuildList *cur = build;
build = build->m_next;
cur->deleteInstance();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE CLASS ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
AISideBuildList::AISideBuildList( AsciiString side ) :
m_side(side),
m_buildList(NULL),
m_next(NULL)
{
}
AISideBuildList::~AISideBuildList()
{
if (m_buildList) {
m_buildList->deleteInstance(); // note - deletes all in the list.
}
m_buildList = NULL;
}
void AISideBuildList::addInfo(BuildListInfo *info)
{
// Add to the end of the list.
if (m_buildList == NULL) {
m_buildList = info;
} else {
BuildListInfo *cur = m_buildList;
while (cur && cur->getNext()) {
cur = cur->getNext();
}
DEBUG_ASSERTCRASH(cur && cur->getNext()==NULL, ("Logic error."));
cur->setNextBuildList(info);
}
info->setNextBuildList(NULL); // should be at the end of the list.
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
static const FieldParse TheAIFieldParseTable[] =
{
{ "StructureSeconds", INI::parseReal,NULL, offsetof( TAiData, m_structureSeconds ) },
{ "TeamSeconds", INI::parseReal,NULL, offsetof( TAiData, m_teamSeconds ) },
{ "Wealthy", INI::parseInt,NULL, offsetof( TAiData, m_resourcesWealthy ) },
{ "Poor", INI::parseInt,NULL, offsetof( TAiData, m_resourcesPoor ) },
{ "ForceIdleMSEC", INI::parseDurationUnsignedInt,NULL,offsetof( TAiData, m_forceIdleFramesCount ) },
{ "StructuresWealthyRate", INI::parseReal,NULL, offsetof( TAiData, m_structuresWealthyMod ) },
{ "TeamsWealthyRate", INI::parseReal,NULL, offsetof( TAiData, m_teamWealthyMod ) },
{ "StructuresPoorRate", INI::parseReal,NULL, offsetof( TAiData, m_structuresPoorMod ) },
{ "TeamsPoorRate", INI::parseReal,NULL, offsetof( TAiData, m_teamPoorMod ) },
{ "TeamResourcesToStart", INI::parseReal,NULL, offsetof( TAiData, m_teamResourcesToBuild ) },
{ "GuardInnerModifierAI", INI::parseReal,NULL, offsetof( TAiData, m_guardInnerModifierAI ) },
{ "GuardOuterModifierAI", INI::parseReal,NULL, offsetof( TAiData, m_guardOuterModifierAI ) },
{ "GuardInnerModifierHuman",INI::parseReal,NULL, offsetof( TAiData, m_guardInnerModifierHuman ) },
{ "GuardOuterModifierHuman",INI::parseReal,NULL, offsetof( TAiData, m_guardOuterModifierHuman ) },
{ "GuardChaseUnitsDuration", INI::parseDurationUnsignedInt,NULL, offsetof( TAiData, m_guardChaseUnitFrames ) },
{ "GuardEnemyScanRate", INI::parseDurationUnsignedInt,NULL, offsetof( TAiData, m_guardEnemyScanRate ) },
{ "GuardEnemyReturnScanRate", INI::parseDurationUnsignedInt,NULL, offsetof( TAiData, m_guardEnemyReturnScanRate ) },
{ "SkirmishGroupFudgeDistance", INI::parseReal,NULL, offsetof( TAiData, m_skirmishGroupFudgeValue ) },
{ "RepulsedDistance", INI::parseReal,NULL, offsetof( TAiData, m_repulsedDistance ) },
{ "EnableRepulsors", INI::parseBool,NULL, offsetof( TAiData, m_enableRepulsors ) },
{ "AlertRangeModifier", INI::parseReal,NULL, offsetof( TAiData, m_alertRangeModifier) },
{ "AggressiveRangeModifier",INI::parseReal,NULL, offsetof( TAiData, m_aggressiveRangeModifier) },
{ "ForceSkirmishAI", INI::parseBool,NULL, offsetof( TAiData, m_forceSkirmishAI ) },
{ "RotateSkirmishBases", INI::parseBool,NULL, offsetof( TAiData, m_rotateSkirmishBases ) },
{ "AttackUsesLineOfSight", INI::parseBool,NULL, offsetof( TAiData, m_attackUsesLineOfSight ) },
{ "AttackIgnoreInsignificantBuildings", INI::parseBool,NULL, offsetof( TAiData, m_attackIgnoreInsignificantBuildings ) },
{ "AttackPriorityDistanceModifier", INI::parseReal,NULL, offsetof( TAiData, m_attackPriorityDistanceModifier) },
{ "MaxRecruitRadius", INI::parseReal,NULL, offsetof( TAiData, m_maxRecruitDistance ) },
{ "WallHeight", INI::parseReal,NULL, offsetof( TAiData, m_wallHeight ) },
{ "SideInfo", AI::parseSideInfo, NULL, NULL },
{ "SkirmishBuildList", AI::parseSkirmishBuildList, NULL, NULL },
{ "MinInfantryForGroup", INI::parseInt,NULL, offsetof( TAiData, m_minInfantryForGroup ) },
{ "MinVehiclesForGroup", INI::parseInt,NULL, offsetof( TAiData, m_minVehiclesForGroup ) },
{ "MinDistanceForGroup", INI::parseReal,NULL, offsetof( TAiData, m_minDistanceForGroup ) },
{ "DistanceRequiresGroup", INI::parseReal,NULL, offsetof( TAiData, m_distanceRequiresGroup ) },
{ "MinClumpDensity", INI::parseReal,NULL, offsetof( TAiData, m_minClumpDensity ) },
{ "InfantryPathfindDiameter", INI::parseInt,NULL, offsetof( TAiData, m_infantryPathfindDiameter ) },
{ "VehiclePathfindDiameter", INI::parseInt,NULL, offsetof( TAiData, m_vehiclePathfindDiameter ) },
{ "RebuildDelayTimeSeconds", INI::parseInt,NULL, offsetof( TAiData, m_rebuildDelaySeconds ) },
{ "SupplyCenterSafeRadius", INI::parseReal,NULL, offsetof( TAiData, m_supplyCenterSafeRadius ) },
{ "AIDozerBoredRadiusModifier", INI::parseReal,NULL, offsetof( TAiData, m_aiDozerBoredRadiusModifier ) },
{ "AICrushesInfantry", INI::parseBool,NULL, offsetof( TAiData, m_aiCrushesInfantry ) },
{ NULL, NULL, NULL, 0 } // keep this last
};
void AI::parseSideInfo(INI *ini, void *instance, void* /*store*/, const void* /*userData*/)
{
const char* c = ini->getNextToken();
AsciiString side(c);
static const FieldParse myFieldParse[] =
{
{ "ResourceGatherersEasy", INI::parseInt, NULL, offsetof( AISideInfo, m_easy ) },
{ "ResourceGatherersNormal", INI::parseInt, NULL, offsetof( AISideInfo, m_normal ) },
{ "ResourceGatherersHard", INI::parseInt, NULL, offsetof( AISideInfo, m_hard ) },
{ "BaseDefenseStructure1", INI::parseAsciiString, NULL, offsetof( AISideInfo, m_baseDefenseStructure1 ) },
{ "SkillSet1", AI::parseSkillSet, NULL, offsetof( AISideInfo, m_skillSet1 ) },
{ "SkillSet2", AI::parseSkillSet, NULL, offsetof( AISideInfo, m_skillSet2 ) },
{ "SkillSet3", AI::parseSkillSet, NULL, offsetof( AISideInfo, m_skillSet3 ) },
{ "SkillSet4", AI::parseSkillSet, NULL, offsetof( AISideInfo, m_skillSet4 ) },
{ "SkillSet5", AI::parseSkillSet, NULL, offsetof( AISideInfo, m_skillSet5 ) },
{ NULL, NULL, NULL, 0 } // keep this last
};
AISideInfo *resourceInfo = ((TAiData*)instance)->m_sideInfo;
while (resourceInfo) {
if (side == resourceInfo->m_side) {
break;
}
resourceInfo = resourceInfo->m_next;
}
if (resourceInfo==NULL)
{
resourceInfo = newInstance(AISideInfo);
((TAiData*)instance)->addSideInfo(resourceInfo);
}
resourceInfo->m_side = side;
ini->initFromINI(resourceInfo, myFieldParse);
}
void AI::parseSkillSet(INI *ini, void *instance, void* store, const void* /*userData*/)
{
static const FieldParse myFieldParse[] =
{
{ "Science", AI::parseScience, NULL, NULL },
{ NULL, NULL, NULL, 0 } // keep this last
};
TSkillSet *skillset = ((TSkillSet*)store);
skillset->m_numSkills = 0;
ini->initFromINI(store, myFieldParse);
}
void AI::parseScience(INI *ini, void *instance, void* /*store*/, const void* /*userData*/)
{
TSkillSet *skillset = ((TSkillSet*)instance);
if (skillset->m_numSkills>=MAX_AI_UPGRADES) {
#ifdef DEBUG_CRASHING
const char* c = ini->getNextToken();
DEBUG_CRASH(("Too many SCIENCE skills in skillset. Skill = %s, max is %d", c, MAX_AI_UPGRADES));
#endif
return;
}
skillset->m_skills[skillset->m_numSkills] = SCIENCE_INVALID;
INI::parseScience(ini, instance, skillset->m_skills+skillset->m_numSkills, NULL);
ScienceType science = skillset->m_skills[skillset->m_numSkills];
if (science != SCIENCE_INVALID) {
if (TheScienceStore->getSciencePurchaseCost(science)==0) {
DEBUG_CRASH(("Science %s is not purchaseable, can't be bought.",
TheScienceStore->getInternalNameForScience(science).str()));
return;
}
skillset->m_numSkills++;
}
}
void AI::parseSkirmishBuildList(INI *ini, void *instance, void* /*store*/, const void* /*userData*/)
{
const char* c = ini->getNextToken();
AsciiString faction(c);
static const FieldParse myFieldParse[] =
{
{ "Structure", BuildListInfo::parseStructure, NULL, NULL },
{ NULL, NULL, NULL, 0 } // keep this last
};
AISideBuildList *build = newInstance(AISideBuildList)(faction);
ini->initFromINI(build, myFieldParse);
((TAiData*)instance)->addFactionBuildList(build);
}
//--------------------------------------------------------------------------------------------------------
/// The AI system singleton
AI *TheAI = NULL;
/**
* Constructor for the AI system
*/
AI::AI( void )
{
m_aiData = NEW TAiData;
m_pathfinder = NEW Pathfinder;
m_nextFormationID = NO_FORMATION_ID;
}
/**
* Initialize the AI system
*/
void AI::init( void )
{
m_nextGroupID = 0;
}
/**
* Reset the AI system in preparation for a new map
*/
void AI::reset( void )
{
m_pathfinder->reset();
while (m_aiData && m_aiData->m_next) {
TAiData *cur = m_aiData;
m_aiData = m_aiData->m_next;
delete cur;
}
while (m_groupList.size())
{
AIGroup *groupToRemove = m_groupList.front();
if (groupToRemove)
{
destroyGroup(groupToRemove);
}
else
{
m_groupList.pop_front(); // NULL group, just kill from list. Shouldn't really happen, but just in case.
}
}
m_nextGroupID = 0;
m_nextFormationID = NO_FORMATION_ID;
getNextFormationID(); // increment once past NO_FORMATION_ID. jba.
}
/**
* Update the AI system
*/
void AI::update( void )
{
// Do pathfinding.
m_pathfinder->processPathfindQueue();
// run player updates
{
ThePlayerList->UPDATE();
}
}
/**
* Destroy the AI system
*/
AI::~AI()
{
if (m_pathfinder) {
delete m_pathfinder;
}
m_pathfinder = NULL;
while (m_aiData)
{
TAiData *cur = m_aiData;
m_aiData = m_aiData->m_next;
delete cur;
}
}
void AI::newOverride(void)
{
TAiData *cur = m_aiData;
m_aiData = NEW TAiData;
*m_aiData = *cur;
m_aiData->m_sideInfo = NULL;
AISideInfo *info = cur->m_sideInfo;
while (info) {
AISideInfo *newInfo = newInstance(AISideInfo);
*newInfo = *info;
newInfo->m_next = NULL;
addSideInfo(newInfo);
info = info->m_next;
}
m_aiData->m_sideBuildLists = NULL;
AISideBuildList *build = cur->m_sideBuildLists;
while (build) {
AISideBuildList *newbuild = newInstance(AISideBuildList)(build->m_side);
newbuild->m_next = NULL;
newbuild->m_buildList = build->m_buildList->duplicate();
m_aiData->addFactionBuildList(newbuild);
build = build->m_next;
}
m_aiData->m_next = cur;
}
void AI::addSideInfo(AISideInfo *infoToAdd)
{
m_aiData->addSideInfo(infoToAdd);
}
//-------------------------------------------------------------------------------------------------
/** Parse GameData entry */
//-------------------------------------------------------------------------------------------------
void AI::parseAiDataDefinition( INI* ini )
{
if( TheAI )
{
//
// if the type of loading we're doing creates override data, we need to
// be loading into a new override item
//
if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES )
TheAI->newOverride();
} // end if
// parse the ini weapon definition
ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable );
}
//--------------------------------------------------------------------------------------------------------
/**
* Create a new AI Group
*/
AIGroup *AI::createGroup( void )
{
// create a new instance
AIGroup *group = newInstance(AIGroup);
// add it to the list
// DEBUG_LOG(("***AIGROUP %x is being added to m_groupList.\n", group ));
m_groupList.push_back( group );
return group;
}
/**
* Destroy the given AI Group
*/
void AI::destroyGroup( AIGroup *group )
{
std::list::iterator i = std::find( m_groupList.begin(), m_groupList.end(), group );
// make sure group is actually in the list
if (i == m_groupList.end())
return;
DEBUG_ASSERTCRASH(group != NULL, ("A NULL group made its way into the AIGroup list.. jkmcd"));
// remove it
// DEBUG_LOG(("***AIGROUP %x is being removed from m_groupList.\n", group ));
m_groupList.erase( i );
// destroy group
group->deleteInstance();
}
/**
* Given an ID, return the associated AIGroup
*/
AIGroup *AI::findGroup( UnsignedInt id )
{
/// @todo Optimize this (MSB)
std::list::iterator i;
for( i=m_groupList.begin(); i!=m_groupList.end(); ++i )
if ((*i)->getID() == id)
return (*i);
return NULL;
}
//--------------------------------------------------------------------------------------------------------
/**
* Get the next formation id.
*/
FormationID AI::getNextFormationID(void )
{
FormationID nextVal = m_nextFormationID;
m_nextFormationID = (FormationID) (nextVal+1);
return nextVal;
}
//-----------------------------------------------------------------------------
class PartitionFilterLiveMapEnemies : public PartitionFilter
{
private:
const Object *m_obj;
public:
PartitionFilterLiveMapEnemies(const Object *obj) : m_obj(obj) { }
virtual Bool allow(Object *objOther)
{
// this is way fast (bit test) so do it first.
if (objOther->isEffectivelyDead())
return false;
// this is also way fast (bit test) so do it next.
if (objOther->isOffMap() != m_obj->isOffMap())
return false;
Relationship r = m_obj->getRelationship(objOther);
if (r != ENEMIES)
return false;
return true;
}
#if defined(_DEBUG) || defined(_INTERNAL)
virtual const char* debugGetName() { return "PartitionFilterLiveMapEnemies"; }
#endif
};
//-----------------------------------------------------------------------------
class PartitionFilterWithinAttackRange : public PartitionFilter
{
private:
const Object* m_obj;
public:
PartitionFilterWithinAttackRange(const Object* obj) : m_obj(obj) { }
virtual Bool allow(Object* objOther)
{
for (Int i = 0; i < WEAPONSLOT_COUNT; i++ )
{
// ignore empty slots.
const Weapon* w = m_obj->getWeaponInWeaponSlot((WeaponSlotType)i);
if (w == NULL)
continue;
if (w->isWithinAttackRange(m_obj, objOther))
{
return true;
}
}
return false;
}
#if defined(_DEBUG) || defined(_INTERNAL)
virtual const char* debugGetName() { return "PartitionFilterWithinAttackRange"; }
#endif
};
typedef struct
{
Int priority;
const AttackPriorityInfo *info;
} TPriorityInfo;
static void priorityFunc(Object *obj, void *userData)
{
TPriorityInfo *dp;
dp = (TPriorityInfo*)userData;
Int curPriority = dp->info->getPriority(obj->getTemplate());
if (curPriority>dp->priority) {
dp->priority = curPriority;
}
}
//-----------------------------------------------------------------------------
/**
* Return the closest enemy, according to the qualifiers.
*/
Object *AI::findClosestEnemy( const Object *me, Real range, UnsignedInt qualifiers,
const AttackPriorityInfo *info, PartitionFilter *optionalFilter)
{
if ((qualifiers & CAN_ATTACK) && !me->isAbleToAttack())
{
/*
PartitionFilterPossibleToAttack would filter out everything anyway,
so just punt here.
*/
return NULL;
}
// only consider live, on-map enemies.
// since this gets called a ton, I made a special custom filter to
// combine several canned ones, in the name of speed (srj)
PartitionFilterLiveMapEnemies filterObvious(me);
PartitionFilterWithinAttackRange filterWithinAttackRange(me);
// never target buildings (unless they can attack)
PartitionFilterRejectBuildings filterBldgs(me);
// and only stuff that isn't stealthed (and not detected)
// (note that stealthed allies aren't hidden from us, but we're only looking for enemies here)
// *** This doesn't cut it anymore. Bombtrucks can be disguised as an enemy member meaning we
// still want to acquire it -- however this old filter fails because the unit is stealthed.
//PartitionFilterRejectByObjectStatus filterStealth(OBJECT_STATUS_STEALTHED, OBJECT_STATUS_DETECTED);
// *** Use this new filter
PartitionFilterStealthedAndUndetected filterStealth( me, false );
// (optional) only stuff we can see.
PartitionFilterLineOfSight filterLOS(me);
// (optional) only stuff we can attack
PartitionFilterPossibleToAttack filterAttack(ATTACK_NEW_TARGET, me, CMD_FROM_AI);
// (optional) only stuff that is significant
PartitionFilterInsignificantBuildings filterInsignificant(true, false);
// (optional) only stuff clear of fog
PartitionFilterFreeOfFog filterFogged(me->getControllingPlayer()->getPlayerIndex());
PartitionFilter *filters[16];
Int numFilters = 0;
// Important note: the filters are called in order, and once one rejects an object,
// the remaining ones are not called, so you should endeavor to
// arrange them such that the most-coarse (ie, the ones that will usually REJECT
// the most objects) should come first.
//
// srj sez: I actually did profiling on USA04 (enabling FILTER_PROFILING in partition mgr)
// to determine the order of these. some observations:
//
// -- filterTeam is BY FAR the best to put first (since most things near you tend to be nonenemies).
// -- filterLOS tends to reject a very large number, but is computationally expensive
// -- filterStealth is BY FAR the least common to be useful, so it goes last.
// GS Fog check used to be inside can attack, so it feels right to be right after it
filters[numFilters++] = &filterObvious;
if( !(qualifiers & ATTACK_BUILDINGS) )
filters[numFilters++] = &filterBldgs;
if (qualifiers & WITHIN_ATTACK_RANGE)
filters[numFilters++] = &filterWithinAttackRange;
if (qualifiers & CAN_SEE)
filters[numFilters++] = &filterLOS;
if (qualifiers & CAN_ATTACK)
filters[numFilters++] = &filterAttack;
if (qualifiers & UNFOGGED)
filters[numFilters++] = &filterFogged;
if (qualifiers & IGNORE_INSIGNIFICANT_BUILDINGS)
filters[numFilters++] = &filterInsignificant;
filters[numFilters++] = &filterStealth;
if (optionalFilter)
{
filters[numFilters++] = optionalFilter;
}
filters[numFilters] = NULL;
if (info == NULL || info == TheScriptEngine->getDefaultAttackInfo())
{
// No additional attack info, so just return the closest one.
Object* o = ThePartitionManager->getClosestObject( me, range, FROM_BOUNDINGSPHERE_2D, filters );
return o;
}
Object *bestEnemy = NULL;
Int effectivePriority=0;
Int actualPriority=0;
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange(me, range, FROM_BOUNDINGSPHERE_2D, filters, ITER_SORTED_NEAR_TO_FAR);
MemoryPoolObjectHolder holder(iter);
for (Object *theEnemy = iter->first(); theEnemy; theEnemy = iter->next())
{
Int curPriority = info->getPriority(theEnemy->getTemplate());
if (curPriority == 0)
continue; // don't attack 0 priority targets.
/* check for garrisoned buildings/vehicles & see if a higher priority unit is inside. */
ContainModuleInterface* contain = theEnemy->getContain();
if (contain) {
TPriorityInfo priorityInfo;
priorityInfo.priority = curPriority;
priorityInfo.info = info;
contain->iterateContained( priorityFunc, &priorityInfo, false ) ;
if (priorityInfo.priority > curPriority) {
curPriority = priorityInfo.priority;
}
}
Real distSqr = ThePartitionManager->getDistanceSquared(me, theEnemy, FROM_BOUNDINGSPHERE_2D);
Real dist = sqrt(distSqr);
Int modifier = dist/TheAI->getAiData()->m_attackPriorityDistanceModifier;
Int modPriority = curPriority-modifier;
if (modPriority < 1)
modPriority = 1;
if (modPriority > effectivePriority)
{
effectivePriority = modPriority;
actualPriority = curPriority;
bestEnemy = theEnemy;
}
if (modPriority == effectivePriority && curPriority > actualPriority)
{
effectivePriority = modPriority;
actualPriority = curPriority;
bestEnemy = theEnemy;
}
}
if (bestEnemy) {
//DEBUG_LOG(("Find closest found %s, hunter %s, info %s\n", bestEnemy->getTemplate()->getName().str(),
// me->getTemplate()->getName().str(), info->getName().str()));
}
return bestEnemy;
}
/////////////////////////////
/**
* Return the closest ally, according to the qualifiers.
*/
Object *AI::findClosestAlly( const Object *me, Real range, UnsignedInt qualifiers)
{
// never target buildings (unless they can attack)
PartitionFilterRejectBuildings filterBldgs(me);
// only consider allies.
PartitionFilterRelationship filterTeam(me, PartitionFilterRelationship::ALLOW_ALLIES);
// and only stuff that is not dead
PartitionFilterAlive filterAlive;
// and on map (or not)
PartitionFilterSameMapStatus filterMapStatus(me);
// (optional) only stuff we can see.
PartitionFilterLineOfSight filterLOS(me);
PartitionFilter *filters[16];
Int numFilters = 0;
filters[numFilters++] = &filterBldgs;
filters[numFilters++] = &filterTeam;
filters[numFilters++] = &filterAlive;
filters[numFilters++] = &filterMapStatus;
if (qualifiers & CAN_SEE)
filters[numFilters++] = &filterLOS;
filters[numFilters] = NULL;
return ThePartitionManager->getClosestObject( me, range, FROM_BOUNDINGSPHERE_2D, filters );
}
/////////////////////////////
/////////////////////////////
/**
* Return the closest repulsor.
*/
Object *AI::findClosestRepulsor( const Object *me, Real range)
{
if (!getAiData()->m_enableRepulsors) {
return NULL;
}
// never target buildings (unless they can attack)
PartitionFilterRepulsor filter(me);
// and only stuff that isn't stealthed (and not detected)
// (note that stealthed allies aren't hidden from us, but that's ok. jba.)
PartitionFilterRejectByObjectStatus filterStealth(OBJECT_STATUS_STEALTHED, OBJECT_STATUS_DETECTED);
PartitionFilter *filters[16];
Int numFilters = 0;
filters[numFilters++] = &filter;
filters[numFilters++] = &filterStealth;
filters[numFilters] = NULL;
return ThePartitionManager->getClosestObject( me, range, FROM_BOUNDINGSPHERE_2D, filters );
}
/////////////////////////////
Real AI::getAdjustedVisionRangeForObject(const Object *object, Int factorsToConsider)
{
Real originalRange = object->getVisionRange();
const AIUpdateInterface *ai = object->getAI();
const TAiData *aiData = TheAI->getAiData();
if (!ai)
{
DEBUG_CRASH(("Unit without AI ('%s') calling AI::getAdjustedVisionRangeForObject. Notify jkmcd.", object->getTemplate()->getName().str()));
return 0.0f;
}
UnsignedInt moodMatrixVal = ai->getMoodMatrixValue();
if (factorsToConsider & AI_VISIONFACTOR_OWNERTYPE)
{
Bool playerIsHuman = (moodMatrixVal & MM_Controller_Player) != 0;
if (playerIsHuman)
{
if (factorsToConsider & AI_VISIONFACTOR_GUARDINNER)
originalRange *= aiData->m_guardInnerModifierHuman;
else
originalRange *= aiData->m_guardOuterModifierHuman;
}
else
{
if (factorsToConsider & AI_VISIONFACTOR_GUARDINNER)
originalRange *= aiData->m_guardInnerModifierAI;
else
originalRange *= aiData->m_guardOuterModifierAI;
}
}
if (object->getContainedBy() != NULL)
{
originalRange = object->getLargestWeaponRange();
}
else
{
if ((factorsToConsider & AI_VISIONFACTOR_MOOD) && ((moodMatrixVal & MM_Controller_Player) == 0) )
{
switch(moodMatrixVal & MM_Mood_Bitmask)
{
case MM_Mood_Sleep:
return 0.0f;
case MM_Mood_Passive:
case MM_Mood_Normal:
break;
case MM_Mood_Alert:
originalRange *= TheAI->getAiData()->m_alertRangeModifier;
break;
case MM_Mood_Aggressive:
originalRange *= TheAI->getAiData()->m_aggressiveRangeModifier;
break;
}
}
}
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_debugVisibility)
{
// ICK. This really nasty statement is used so that we only initialize this color once.
// It should be exactly double the intensity of its targettable brother.
static RGBColor theAdjustedVisionColor = {
(TheGlobalData->m_debugVisibilityTargettableColor.red * 2 <= 1.0f ?
TheGlobalData->m_debugVisibilityTargettableColor.red * 2 :
1.0f),
(TheGlobalData->m_debugVisibilityTargettableColor.green * 2 <= 1.0f ?
TheGlobalData->m_debugVisibilityTargettableColor.green * 2 :
1.0f),
(TheGlobalData->m_debugVisibilityTargettableColor.blue * 2 <= 1.0f ?
TheGlobalData->m_debugVisibilityTargettableColor.blue * 2 :
1.0f)
};
Vector3 pos(originalRange, 0, 0);
for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i)
{
pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI);
Coord3D coord = { pos.X + object->getPosition()->x, pos.Y + object->getPosition()->y, pos.Z + object->getPosition()->z };
addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth,
TheGlobalData->m_debugVisibilityTileDuration,
theAdjustedVisionColor);
}
}
#endif
return originalRange;
}
//-------------------------------------------------------------------------------------------------
TAiData::TAiData() :
m_next(NULL),
m_sideInfo(NULL),
m_attackIgnoreInsignificantBuildings(false),
m_skirmishGroupFudgeValue(0.0f),
m_structureSeconds(0),
m_teamSeconds(0),
m_resourcesWealthy(0),
m_resourcesPoor(0),
m_forceIdleFramesCount(1),
m_structuresWealthyMod(0),
m_teamPoorMod(0),
m_teamResourcesToBuild(0),
m_guardInnerModifierAI(0),
m_guardOuterModifierAI(0),
m_guardInnerModifierHuman(0),
m_guardOuterModifierHuman(0),
m_guardChaseUnitFrames(0),
m_guardEnemyScanRate(LOGICFRAMES_PER_SECOND/2),
m_guardEnemyReturnScanRate(LOGICFRAMES_PER_SECOND),
m_wallHeight(0),
m_alertRangeModifier(0),
m_aggressiveRangeModifier(0),
m_attackPriorityDistanceModifier(0),
m_maxRecruitDistance(0),
m_repulsedDistance(0),
m_enableRepulsors(false),
m_forceSkirmishAI(false),
m_rotateSkirmishBases(false),
m_attackUsesLineOfSight(true),
m_minInfantryForGroup(3),
m_minVehiclesForGroup(4),
m_minDistanceForGroup(100),
m_minClumpDensity(0.5f),
m_infantryPathfindDiameter(6),
m_vehiclePathfindDiameter(6),
m_supplyCenterSafeRadius(250),
m_rebuildDelaySeconds(10),
//Added By Sadullah Nader
//Initialization(s) inserted
m_distanceRequiresGroup(0.0f),
m_sideBuildLists(NULL),
m_structuresPoorMod(0.0f),
m_teamWealthyMod(0.0f),
m_aiDozerBoredRadiusModifier(2.0),
m_aiCrushesInfantry(true)
//
{
}
//-------------------------------------------------------------------------------------------------
void TAiData::crc( Xfer *xfer )
{
xfer->xferReal( &m_structureSeconds );
xfer->xferReal( &m_teamSeconds );
xfer->xferInt( &m_resourcesWealthy );
xfer->xferInt( &m_resourcesPoor );
xfer->xferUnsignedInt( &m_forceIdleFramesCount );
xfer->xferReal( &m_structuresWealthyMod );
xfer->xferReal( &m_teamWealthyMod );
xfer->xferReal( &m_structuresPoorMod );
xfer->xferReal( &m_teamPoorMod );
xfer->xferReal( &m_teamResourcesToBuild );
xfer->xferReal( &m_guardInnerModifierAI );
xfer->xferReal( &m_guardOuterModifierAI );
xfer->xferReal( &m_guardInnerModifierHuman );
xfer->xferReal( &m_guardOuterModifierHuman );
xfer->xferUnsignedInt( &m_guardChaseUnitFrames );
xfer->xferUnsignedInt( &m_guardEnemyScanRate );
xfer->xferUnsignedInt( &m_guardEnemyReturnScanRate );
xfer->xferReal( &m_alertRangeModifier );
xfer->xferReal( &m_aggressiveRangeModifier );
xfer->xferReal( &m_attackPriorityDistanceModifier );
xfer->xferReal( &m_maxRecruitDistance );
xfer->xferReal( &m_repulsedDistance );
xfer->xferBool( &m_enableRepulsors );
CRCGEN_LOG(("CRC after AI TAiData for frame %d is 0x%8.8X\n", TheGameLogic->getFrame(), ((XferCRC *)xfer)->getCRC()));
} // end crc
//-----------------------------------------------------------------------------
void TAiData::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
//-----------------------------------------------------------------------------
void TAiData::loadPostProcess( void )
{
} // end loadPostProcess
//-----------------------------------------------------------------------------
void AI::crc( Xfer *xfer )
{
xfer->xferSnapshot( m_pathfinder );
CRCGEN_LOG(("CRC after AI pathfinder for frame %d is 0x%8.8X\n", TheGameLogic->getFrame(), ((XferCRC *)xfer)->getCRC()));
AsciiString marker;
TAiData *aiData = m_aiData;
while (aiData)
{
marker = "MARKER:TAiData";
xfer->xferAsciiString(&marker);
xfer->xferSnapshot( aiData );
aiData = aiData->m_next;
}
for (std::list::iterator groupIt = m_groupList.begin(); groupIt != m_groupList.end(); ++groupIt)
{
if (*groupIt)
{
marker = "MARKER:AIGroup";
xfer->xferAsciiString(&marker);
xfer->xferSnapshot( (*groupIt) );
}
}
} // end crc
//-----------------------------------------------------------------------------
void AI::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
//-----------------------------------------------------------------------------
void AI::loadPostProcess( void )
{
} // end loadPostProcess