/*
** Command & Conquer Generals Zero Hour(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. //
// //
////////////////////////////////////////////////////////////////////////////////
// AISkirmishPlayer.cpp
// Computerized opponent
// Author: Michael S. Booth, January 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameMemory.h"
#include "Common/GlobalData.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Team.h"
#include "Common/ThingFactory.h"
#include "Common/BuildAssistant.h"
#include "Common/SpecialPower.h"
#include "Common/ThingTemplate.h"
#include "Common/WellKnownKeys.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/AISkirmishPlayer.h"
#include "GameLogic/SidesList.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/TerrainLogic.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/RebuildHoleBehavior.h"
#include "GameLogic/Module/UpdateModule.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/ProductionUpdate.h"
#include "GameClient/TerrainVisual.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
#define USE_DOZER 1
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
AISkirmishPlayer::AISkirmishPlayer( Player *p ) : AIPlayer(p),
m_curFlankBaseDefense(0),
m_curFrontBaseDefense(0),
m_curFrontLeftDefenseAngle(0),
m_curFrontRightDefenseAngle(0),
m_curLeftFlankLeftDefenseAngle(0),
m_curLeftFlankRightDefenseAngle(0),
m_curRightFlankLeftDefenseAngle(0),
m_curRightFlankRightDefenseAngle(0),
m_frameToCheckEnemy(0),
m_currentEnemy(NULL)
{
m_frameLastBuildingBuilt = TheGameLogic->getFrame();
p->setCanBuildUnits(true); // turn on ai production by default.
}
AISkirmishPlayer::~AISkirmishPlayer()
{
clearTeamsInQueue();
}
/**
* Build our base.
*/
void AISkirmishPlayer::processBaseBuilding( void )
{
//
// Refresh base buildings. Scan through list, if a building is missing,
// rebuild it, unless it's rebuild count is zero.
//
if (m_readyToBuildStructure)
{
const ThingTemplate *bldgPlan=NULL;
BuildListInfo *bldgInfo = NULL;
Bool isPriority = false;
Object *bldg = NULL;
const ThingTemplate *powerPlan=NULL;
BuildListInfo *powerInfo = NULL;
Bool isUnderPowered = !m_player->getEnergy()->hasSufficientPower();
Bool powerUnderConstruction = false;
for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
{
AsciiString name = info->getTemplateName();
if (name.isEmpty()) continue;
const ThingTemplate *curPlan = TheThingFactory->findTemplate( name );
if (!curPlan) {
DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
continue;
}
bldg = TheGameLogic->findObjectByID( info->getObjectID() );
// check for hole.
if (info->getObjectID() != INVALID_ID) {
// used to have a building.
Object *bldg = TheGameLogic->findObjectByID( info->getObjectID() );
if (bldg==NULL) {
// got destroyed.
ObjectID priorID;
priorID = info->getObjectID();
info->setObjectID(INVALID_ID);
info->setObjectTimestamp(TheGameLogic->getFrame()+1);
// Scan for a GLA hole. KINDOF_REBUILD_HOLE
Object *obj;
for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) {
if (!obj->isKindOf(KINDOF_REBUILD_HOLE)) continue;
RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( obj );
if( rhbi ) {
ObjectID spawnerID = rhbi->getSpawnerID();
if (priorID == spawnerID) {
DEBUG_LOG(("AI Found hole to rebuild %s\n", curPlan->getName().str()));
info->setObjectID(obj->getID());
}
}
}
} else {
if (bldg->getControllingPlayer() == m_player) {
// Check for built or dozer missing.
if( bldg->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
if (bldg->isKindOf(KINDOF_FS_POWER) && !bldg->isKindOf(KINDOF_CASH_GENERATOR))
{
powerUnderConstruction = true;
}
// make sure dozer is working on him.
ObjectID builder = bldg->getBuilderID();
Object* myDozer = TheGameLogic->findObjectByID(builder);
if (myDozer && ( myDozer->getControllingPlayer() != m_player || myDozer->isDisabledByType( DISABLED_UNMANNED ) ) )
{//I don't expect this dozer to work well with me.
myDozer = NULL;
bldg->setBuilder( NULL );
}
if (myDozer==NULL) {
DEBUG_LOG(("AI's Dozer got killed (or captured). Find another dozer.\n"));
queueDozer();
myDozer = findDozer(bldg->getPosition());
if (myDozer==NULL || myDozer->getAI()==NULL) {
continue;
}
myDozer->getAI()->aiResumeConstruction(bldg, CMD_FROM_AI);
} else {
// make sure he is building.
myDozer->getAI()->aiResumeConstruction(bldg, CMD_FROM_AI);
}
}
} else {
// oops, got captured.
info->setObjectID(INVALID_ID);
info->setObjectTimestamp(TheGameLogic->getFrame()+1);
}
}
}
if (info->getObjectID()==INVALID_ID && info->getObjectTimestamp()>0) {
// this object was built at some time, and got destroyed at or near objectTimestamp.
// Wait a few seconds before initiating a rebuild.
if (info->getObjectTimestamp()+TheAI->getAiData()->m_rebuildDelaySeconds*LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) {
continue;
} else {
DEBUG_LOG(("Enabling rebuild for %s\n", info->getTemplateName().str()));
info->setObjectTimestamp(0); // ready to build.
}
}
if (bldg) {
continue; // already built.
}
// Make sure it is safe to build here.
if (!isLocationSafe(info->getLocation(), curPlan)) {
continue;
}
if (info->isPriorityBuild()) {
// Always take priority build, unless we already have priority build.
if (!isPriority) {
bldgPlan = curPlan;
bldgInfo = info;
isPriority = true;
}
}
if (curPlan->isKindOf(KINDOF_FS_POWER)) {
if (powerPlan==NULL && !curPlan->isKindOf(KINDOF_CASH_GENERATOR)) {
if (isUnderPowered || info->isAutomaticBuild()) {
powerPlan = curPlan;
powerInfo = info;
}
}
}
if (!info->isAutomaticBuild()) {
continue; // marked to not build automatically.
}
Object *dozer = findDozer(info->getLocation());
if (dozer==NULL) {
if (isUnderPowered) {
queueDozer();
}
continue;
}
if (TheBuildAssistant->canMakeUnit(dozer, bldgPlan)!=CANMAKE_OK) {
if (info->isBuildable()) {
AsciiString bldgName = info->getTemplateName();
bldgName.concat(" - Dozer unable to build - money or technology missing.");
TheScriptEngine->AppendDebugMessage(bldgName, false);
}
continue;
}
// check if this building has any "rebuilds" left
if (info->isBuildable())
{
if (bldgPlan == NULL) {
bldgPlan = curPlan;
bldgInfo = info;
}
}
}
if (powerPlan && powerInfo && !powerPlan->isEquivalentTo(bldgPlan)) {
if (!powerUnderConstruction) {
bldgPlan = powerPlan;
bldgInfo = powerInfo;
DEBUG_LOG(("Forcing build of power plant.\n"));
}
}
if (bldgPlan && bldgInfo) {
#ifdef USE_DOZER
// dozer-construct the building
bldg = buildStructureWithDozer(bldgPlan, bldgInfo);
// store the object with the build order
if (bldg)
{
bldgInfo->setObjectID( bldg->getID() );
bldgInfo->decrementNumRebuilds();
m_readyToBuildStructure = false;
m_structureTimer = TheAI->getAiData()->m_structureSeconds*LOGICFRAMES_PER_SECOND;
if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresPoorMod;
} else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresWealthyMod;
}
m_frameLastBuildingBuilt = TheGameLogic->getFrame();
// only build one building per delay loop
} // bldg built
#else
// force delay between rebuilds
if (TheGameLogic->getFrame() - m_frameLastBuildingBuilt < framesToBuild)
{
m_buildDelay = framesToBuild - (TheGameLogic->getFrame() - m_frameLastBuildingBuilt);
return;
} else {
// building is missing, (re)build it
// deduct money to build, if we have it
Int cost = bldgPlan->calcCostToBuild( m_player );
if (m_player->getMoney()->countMoney() >= cost)
{
// we have the money, deduct it
m_player->getMoney()->withdraw( cost );
// inst-construct the building
bldg = buildStructureNow(bldgPlan, info, NULL);
// store the object with the build order
if (bldg)
{
info->setObjectID( bldg->getID() );
info->decrementNumRebuilds();
m_readyToBuildStructure = false;
m_structureTimer = TheAI->getAiData()->m_structureSeconds*LOGICFRAMES_PER_SECOND;
if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresPoorMod;
} else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresWealthyMod;
}
m_frameLastBuildingBuilt = TheGameLogic->getFrame();
// only build one building per delay loop
break;
} // bldg built
} // have money
} // rebuild delay ok
#endif
}
}
}
/**
* Invoked when a unit I am training comes into existence
*/
void AISkirmishPlayer::onUnitProduced( Object *factory, Object *unit )
{
AIPlayer::onUnitProduced(factory, unit);
}
/**
* Search the computer player's buildings for one that can build the given request
* and start training the unit.
* If busyOK is true, it will queue a unit even if one is building. This lets
* script invoked teams "push" to the front of the queue.
*/
Bool AISkirmishPlayer::startTraining( WorkOrder *order, Bool busyOK, AsciiString teamName)
{
Object *factory = findFactory(order->m_thing, busyOK);
if( factory )
{
ProductionUpdateInterface *pu = factory->getProductionUpdateInterface();
if (pu && pu->queueCreateUnit( order->m_thing, pu->requestUniqueUnitID() )) {
order->m_factoryID = factory->getID();
if (TheGlobalData->m_debugAI) {
AsciiString teamStr = "Queuing ";
teamStr.concat(order->m_thing->getName());
teamStr.concat(" for ");
teamStr.concat(teamName);
TheScriptEngine->AppendDebugMessage(teamStr, false);
}
return true;
}
} // end if
return FALSE;
}
/**
* Check if this team is buildable, doesn't exceed maximum limits, meets conditions, and isn't under construction.
*/
Bool AISkirmishPlayer::isAGoodIdeaToBuildTeam( TeamPrototype *proto )
{
// Check condition.
if (!proto->evaluateProductionCondition()) {
return false;
}
// check build limit
if (proto->countTeamInstances() >= proto->getTemplateInfo()->m_maxInstances){
if (TheGlobalData->m_debugAI) {
AsciiString str;
str.format("Team %s not chosen - %d already exist.", proto->getName().str(), proto->countTeamInstances());
TheScriptEngine->AppendDebugMessage(str, false);
}
return false; // Max already built.
}
for ( DLINK_ITERATOR iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
{
TeamInQueue *team = iter.cur();
if (team->m_team->getPrototype() == proto) {
return false; // currently building one of these.
}
}
Bool needMoney;
if (!isPossibleToBuildTeam( proto, true, needMoney)) {
if (TheGlobalData->m_debugAI) {
AsciiString str;
if (needMoney) {
str.format("Team %s not chosen - Not enough money.", proto->getName().str());
} else {
str.format("Team %s not chosen - Factory/tech missing or busy.", proto->getName().str());
}
TheScriptEngine->AppendDebugMessage(str, false);
}
return false;
}
return true;
}
/**
* See if any existing teams need reinforcements, and have higher priority.
*/
Bool AISkirmishPlayer::selectTeamToReinforce( Int minPriority )
{
return AIPlayer::selectTeamToReinforce(minPriority);
}
/**
* Determine the next team to build. Return true if one was selected.
*/
Bool AISkirmishPlayer::selectTeamToBuild( void )
{
return AIPlayer::selectTeamToBuild();
}
/**
Build a specific building.
*/
void AISkirmishPlayer::buildSpecificAIBuilding(const AsciiString &thingName)
{
//
Bool found = false;
Bool foundUnbuilt = false;
for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
{
if (info->getTemplateName()==thingName)
{
AsciiString name = info->getTemplateName();
if (name.isEmpty()) continue;
const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
if (!bldgPlan) {
DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
continue;
}
Object *bldg = TheGameLogic->findObjectByID( info->getObjectID() );
found = true;
if (bldg) {
continue; // already built.
}
if (info->isPriorityBuild()) {
continue; // already marked for priority build.
}
foundUnbuilt = true;
info->markPriorityBuild();
break;
}
}
if (foundUnbuilt) {
m_buildDelay = 0;
AsciiString buildingStr = "Queueing building '";
buildingStr.concat(thingName);
buildingStr.concat("' for construction.");
TheScriptEngine->AppendDebugMessage(buildingStr, false);
} else if (found) {
AsciiString buildingStr = "Warning - all instances of building '";
buildingStr.concat(thingName);
buildingStr.concat("' are already built or queued for build, not queueing.");
TheScriptEngine->AppendDebugMessage(buildingStr, false);
} else {
AsciiString buildingStr = "Error - could not find building '";
buildingStr.concat(thingName);
buildingStr.concat("' in the building template list.");
TheScriptEngine->AppendDebugMessage(buildingStr, false);
}
}
/**
Gets the player index of my enemy.
*/
Int AISkirmishPlayer::getMyEnemyPlayerIndex(void) {
Int playerNdx;
if (m_currentEnemy) {
return m_currentEnemy->getPlayerIndex();
}
// For now, return first human player, as there should only be one. jba
for (playerNdx=0; playerNdxgetPlayerCount(); playerNdx++) {
if (ThePlayerList->getNthPlayer(playerNdx)->getPlayerType() == PLAYER_HUMAN) {
break;
}
}
return playerNdx;
}
/**
Get the AI's enemy. Recalc if it has been a while (5 seconds.)
*/
void AISkirmishPlayer::acquireEnemy(void)
{
Player *bestEnemy = NULL;
Real bestDistanceSqr = HUGE_DIST*HUGE_DIST;
if (m_currentEnemy) {
Bool inBadShape = !m_currentEnemy->hasAnyUnits() || !m_currentEnemy->hasAnyBuildFacility();
if (!inBadShape) return;
}
// look for the closest enemy.
Int i;
for (i=0; igetPlayerCount(); i++) {
Player *curPlayer = ThePlayerList->getNthPlayer(i);
if (m_player->getRelationship(curPlayer->getDefaultTeam()) == ENEMIES) {
if (curPlayer->hasAnyObjects()==false) continue; // not much of an enemy.
// ok, we got an enemy;
// If a player is out of units, or out of build facilities, we can lower his priority.
Bool inBadShape = !curPlayer->hasAnyUnits() || !curPlayer->hasAnyBuildFacility();
Coord3D enemyPos = m_baseCenter;
Region2D bounds;
getPlayerStructureBounds(&bounds, i);
enemyPos.x = bounds.lo.x + bounds.width()/2;
enemyPos.y = bounds.lo.y + bounds.height()/2;
Real curDistSqr = sqr(enemyPos.x-m_baseCenter.x) + sqr(enemyPos.y-m_baseCenter.y);
//Fudge for in bad shape. If an enemy is crippled, concentrate on the other ones.
if (inBadShape) {
curDistSqr = HUGE_DIST*HUGE_DIST*0.5f;
}
// See if other ai's are attacking this target.
// We don't want the ai's to gang up on one enemy.
Int k;
for (k=0; kgetPlayerCount(); k++) {
if (k==i) continue; // don't count self.
Player *somePlayer = ThePlayerList->getNthPlayer(k);
if (somePlayer->isSkirmishAIPlayer() && (somePlayer->getCurrentEnemy()==curPlayer)) {
// Some ai is already targeting this guy. Add a distance penalty.
curDistSqr += (500*500);
}
if (somePlayer->isSkirmishAIPlayer() && (somePlayer->getCurrentEnemy()==m_player)) {
// he is attacking me. So I will (gently) prefer to attack him.
curDistSqr -= (25*25);
if (curDistSqr<0) curDistSqr = 0;
}
}
// Ai enemy - will take if we don't get a better offer.
if (curDistSqrkeyToName(m_player->getPlayerNameKey());
msg.concat(" acquiring target enemy player: ");
msg.concat(TheNameKeyGenerator->keyToName(m_currentEnemy->getPlayerNameKey()));
TheScriptEngine->AppendDebugMessage( msg, false);
}
}
/**
Get the AI's enemy. Recalc if it has been a while (20 seconds.)
*/
Player *AISkirmishPlayer::getAiEnemy(void)
{
if (TheGameLogic->getFrame()>=m_frameToCheckEnemy) {
m_frameToCheckEnemy = TheGameLogic->getFrame() + 5*LOGICFRAMES_PER_SECOND;
acquireEnemy();
}
return m_currentEnemy;
}
/**
Build base defense structures on the front or flank of the base.
*/
void AISkirmishPlayer::buildAIBaseDefense(Bool flank)
{
const AISideInfo *resInfo = TheAI->getAiData()->m_sideInfo;
AsciiString defenseTemplateName;
while (resInfo) {
if (resInfo->m_side == m_player->getSide()) {
defenseTemplateName = resInfo->m_baseDefenseStructure1;
break;
}
resInfo = resInfo->m_next;
}
if (resInfo) {
buildAIBaseDefenseStructure(resInfo->m_baseDefenseStructure1, flank);
}
}
/**
Build base defense structures on the front or flank of the base.
Base defenses are placed as follows:
m_baseCenter and m_baseRadius are calculated on map load.
Defenses are placed along the this circle.
Front defenses (!flank) are placed starting at the "Center" approach path.
The first front defense is placed towards th Center path. Number 2 is placed
to the left of #1, #3 is placed to the right of #1, #4 is placed to the left of
#2 and so on. So it looks like:
#1
#2 #3
#6 #4 #5 #7
#8 #9
The flank base defenses cover the "Flank" approach, and the "Backdoor" approach.
They alternate between these two, so the first flank defense covers flank, and the second
covers backdoor, and continue to alternate. They cover the approach using the same
pattern as front above.
John A.
*/
void AISkirmishPlayer::buildAIBaseDefenseStructure(const AsciiString &thingName, Bool flank)
{
const ThingTemplate *tTemplate = TheThingFactory->findTemplate(thingName);
if (tTemplate==NULL) {
DEBUG_CRASH(("Couldn't find base defense structure '%s' for side %s", thingName.str(), m_player->getSide().str()));
return;
}
do {
AsciiString pathLabel;
if (flank) {
if (m_curFlankBaseDefense&1) {
pathLabel.format("%s%d", SKIRMISH_FLANK, m_player->getMpStartIndex()+1);
} else {
pathLabel.format("%s%d", SKIRMISH_BACKDOOR, m_player->getMpStartIndex()+1);
}
} else {
pathLabel.format("%s%d", SKIRMISH_CENTER, m_player->getMpStartIndex()+1);
}
Coord3D goalPos = m_baseCenter;
Waypoint *way = TheTerrainLogic->getClosestWaypointOnPath( &goalPos, pathLabel );
if (way) {
goalPos = *way->getLocation();
} else {
if (flank) return;
Region2D bounds;
getPlayerStructureBounds(&bounds, getMyEnemyPlayerIndex());
goalPos.x = bounds.lo.x + bounds.width()/2;
goalPos.y = bounds.lo.y + bounds.height()/2;
}
Coord2D offset;
offset.x = goalPos.x-m_baseCenter.x;
offset.y = goalPos.y-m_baseCenter.y;
offset.normalize();
Real defenseDistance = m_baseRadius;
defenseDistance += TheAI->getAiData()->m_skirmishBaseDefenseExtraDistance;
offset.x *= defenseDistance;
offset.y *= defenseDistance;
Real structureRadius = tTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real baseCircumference = 2*PI*defenseDistance;
Real angleOffset = 2*PI*(structureRadius*4/baseCircumference);
Int selector;
Real angle;
if (flank) {
selector = m_curFlankBaseDefense>>1;
if (m_curFlankBaseDefense&1) {
if (selector&1) {
m_curLeftFlankRightDefenseAngle -= angleOffset;
angle = m_curLeftFlankRightDefenseAngle;
} else {
angle = m_curLeftFlankLeftDefenseAngle;
m_curLeftFlankLeftDefenseAngle += angleOffset;
}
} else {
if (selector&1) {
m_curRightFlankRightDefenseAngle -= angleOffset;
angle = m_curRightFlankRightDefenseAngle;
} else {
angle = m_curRightFlankLeftDefenseAngle;
m_curRightFlankLeftDefenseAngle += angleOffset;
}
}
} else {
selector = m_curFrontBaseDefense;
if (selector&1) {
m_curFrontRightDefenseAngle -= angleOffset;
angle = m_curFrontRightDefenseAngle;
} else {
angle = m_curFrontLeftDefenseAngle;
m_curFrontLeftDefenseAngle += angleOffset;
}
}
if (angle > PI/3) break;
Real s = sin(angle);
Real c = cos(angle);
DEBUG_LOG(("buildAIBaseDefenseStructure -- Angle is %f sin %f, cos %f \n", 180*angle/PI, s, c));
DEBUG_LOG(("buildAIBaseDefenseStructure -- Offset is %f %f, Final Position is %f, %f \n",
offset.x, offset.y,
offset.x*c - offset.y*s,
offset.y*c + offset.x*s
));
Coord3D buildPos = m_baseCenter;
buildPos.x += offset.x*c - offset.y*s;
buildPos.y += offset.y*c + offset.x*s;
/* See if we can build there. */
Bool canBuild;
Real placeAngle = tTemplate->getPlacementViewAngle();
canBuild = LBC_OK == TheBuildAssistant->isLocationLegalToBuild(&buildPos, tTemplate, placeAngle,
BuildAssistant::TERRAIN_RESTRICTIONS|BuildAssistant::NO_OBJECT_OVERLAP, NULL, m_player);
TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
if (flank) {
m_curFlankBaseDefense++;
} else {
m_curFrontBaseDefense++;
}
if (canBuild) {
m_player->addToPriorityBuildList(thingName, &buildPos, placeAngle);
break;
}
} while (true);
}
/**
Checks bridges along a waypoint path. If any are destroyed, sends a dozer to fix, and returns true.
If there is no bridge problem, returns false.
*/
Bool AISkirmishPlayer::checkBridges(Object *unit, Waypoint *way)
{
Coord3D unitPos = *unit->getPosition();
AIUpdateInterface *ai = unit->getAI();
if (!ai) return false; // no ai
const LocomotorSet& locoSet = ai->getLocomotorSet();
Waypoint *curWay;
for (curWay = way; curWay; curWay = curWay->getNext()) {
if (TheAI->pathfinder()->clientSafeQuickDoesPathExist(locoSet, &unitPos, curWay->getLocation())) {
continue;
}
ObjectID brokenBridge = INVALID_ID;
if (TheAI->pathfinder()->findBrokenBridge(locoSet, &unitPos, curWay->getLocation(), &brokenBridge)) {
repairStructure(brokenBridge);
return true;
}
}
return false;
}
/**
Build a specific team. If priorityBuild, put at front of queue with priority set.
*/
void AISkirmishPlayer::buildSpecificAITeam( TeamPrototype *teamProto, Bool priorityBuild)
{
AIPlayer::buildSpecificAITeam(teamProto, priorityBuild);
}
/**
Recruit a specific team, within the specific radius of the home position.
*/
void AISkirmishPlayer::recruitSpecificAITeam(TeamPrototype *teamProto, Real recruitRadius)
{
if (recruitRadius < 1) recruitRadius = 99999.0f;
//
// Create "Team in queue" based on team population
//
if (teamProto)
{
if (teamProto->getIsSingleton()) {
Team *singletonTeam = TheTeamFactory->findTeam( teamProto->getName() );
if (singletonTeam && singletonTeam->hasAnyObjects()) {
AsciiString teamStr = "Unable to recruit singleton team '";
teamStr.concat("' because team already exists.");
TheScriptEngine->AppendDebugMessage(teamStr, false);
return;
}
}
if (!teamProto->getTemplateInfo()->m_hasHomeLocation)
{
AsciiString teamStr = "Error : team '";
teamStr.concat(teamProto->getName());
teamStr.concat("' has no Home Position (or Origin).");
TheScriptEngine->AppendDebugMessage(teamStr, false);
}
// create inactive team to place members into as they are built
// when team is complete, the team is activated
Team *theTeam = TheTeamFactory->createInactiveTeam( teamProto->getName() );
AsciiString teamName = teamProto->getName();
teamName.concat(" - Recruiting.");
TheScriptEngine->AppendDebugMessage(teamName, false);
const TCreateUnitsInfo *unitInfo = &teamProto->getTemplateInfo()->m_unitsInfo[0];
// WorkOrder *orders = NULL;
Int i;
Int unitsRecruited = 0;
// Recruit.
for( i=0; igetTemplateInfo()->m_numUnitsInfo; i++ )
{
const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
if (thing)
{
int count = unitInfo[i].maxUnits;
while (count>0) {
Object *unit = theTeam->tryToRecruit(thing, &teamProto->getTemplateInfo()->m_homeLocation, recruitRadius);
if (unit)
{
unitsRecruited++;
AsciiString teamStr = "Team '";
teamStr.concat(theTeam->getPrototype()->getName());
teamStr.concat("' recruits ");
teamStr.concat(thing->getName());
teamStr.concat(" from team '");
teamStr.concat(unit->getTeam()->getPrototype()->getName());
teamStr.concat("'");
TheScriptEngine->AppendDebugMessage(teamStr, false);
unit->setTeam(theTeam);
AIUpdateInterface *ai = unit->getAIUpdateInterface();
if (ai)
{
#if defined(_DEBUG) || defined(_INTERNAL)
Coord3D pos = *unit->getPosition();
Coord3D to = teamProto->getTemplateInfo()->m_homeLocation;
DEBUG_LOG(("Moving unit from %f,%f to %f,%f\n", pos.x, pos.y , to.x, to.y ));
#endif
ai->aiMoveToPosition( &teamProto->getTemplateInfo()->m_homeLocation, CMD_FROM_AI);
}
} else {
break;
}
count--;
}
}
}
if (unitsRecruited>0)
{
/* We have something to build. */
TeamInQueue *team = newInstance(TeamInQueue);
// Put in front of queue.
prependTo_TeamReadyQueue(team);
team->m_priorityBuild = false;
team->m_workOrders = NULL;
team->m_frameStarted = TheGameLogic->getFrame();
team->m_team = theTeam;
AsciiString teamName = teamProto->getName();
teamName.concat(" - Finished recruiting.");
TheScriptEngine->AppendDebugMessage(teamName, false);
} else {
//disband.
if (!theTeam->getPrototype()->getIsSingleton()) {
theTeam->deleteInstance();
theTeam = NULL;
}
AsciiString teamName = teamProto->getName();
teamName.concat(" - Recruited 0 units, disbanding.");
TheScriptEngine->AppendDebugMessage(teamName, false);
}
}
}
/**
* Train our teams.
*/
void AISkirmishPlayer::processTeamBuilding( void )
{
// select a new team
if (selectTeamToBuild()) {
queueUnits();
}
}
//----------------------------------------------------------------------------------------------------------
/**
* See if it's time to build another base building.
*/
void AISkirmishPlayer::doBaseBuilding( void )
{
if (m_player->getCanBuildBase()) {
// See if we are ready to start trying a structure.
if (!m_readyToBuildStructure) {
m_structureTimer--;
if (m_structureTimer<=0) {
m_readyToBuildStructure = true;
m_buildDelay = 0;
}
if (m_structureTimer > 3*LOGICFRAMES_PER_SECOND) {
m_structureTimer = 3*LOGICFRAMES_PER_SECOND;
}
}
// This timer is to keep from banging on the logic each frame. If something interesting
// happens, like a building is added or a unit finished, the timers are shortcut.
m_buildDelay--;
if (m_buildDelay<1) {
if (m_readyToBuildStructure) {
processBaseBuilding();
}
if (m_buildDelay<1) { // processBaseBuilding may reset m_buildDelay.
m_buildDelay = 2*LOGICFRAMES_PER_SECOND; // check again in 2 seconds.
}
// Note that this timer gets shortcut when a building is completed.
}
}
}
//----------------------------------------------------------------------------------------------------------
/**
* See if any ready teams have finished moving to the rally point.
*/
void AISkirmishPlayer::checkReadyTeams( void )
{
AIPlayer::checkReadyTeams();
}
//----------------------------------------------------------------------------------------------------------
/**
* See if any queued teams have finished building, or have run out of time.
*/
void AISkirmishPlayer::checkQueuedTeams( void )
{
AIPlayer::checkQueuedTeams();
}
//----------------------------------------------------------------------------------------------------------
/**
* See if it is time to start another ai team building.
*/
void AISkirmishPlayer::doTeamBuilding( void )
{
// See if any teams are expired.
if (m_player->getCanBuildUnits()) {
// See if we are ready to start trying a team.
if (!m_readyToBuildTeam) {
m_teamTimer--;
if (m_teamTimer<=0) {
m_readyToBuildTeam = true;
m_teamDelay = 0;
}
if (m_teamTimer > 3*LOGICFRAMES_PER_SECOND) {
m_teamTimer = 3*LOGICFRAMES_PER_SECOND;
}
}
// This timer is to keep from banging on the logic each frame. If something interesting
// happens, like a building is added or a unit finished, the timers are shortcut.
m_teamDelay--;
if (m_teamDelay<1) {
queueUnits(); // update the queues.
if (m_readyToBuildTeam) {
processTeamBuilding();
}
m_teamDelay = 2*LOGICFRAMES_PER_SECOND; // check again in 5 seconds.
// Note that this timer gets shortcut when a unit or building is completed.
}
}
}
//----------------------------------------------------------------------------------------------------------
/**
* Perform computer-controlled player AI
*/
void AISkirmishPlayer::update( void )
{
AIPlayer::update();
}
//----------------------------------------------------------------------------------------------------------
/**
* Adjusts the build list to match the starting position.
*/
void AISkirmishPlayer::adjustBuildList(BuildListInfo *list)
{
Bool foundStart = false;
Coord3D startPos;
// Find our command center location.
Object *obj;
for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
Player *owner = obj->getControllingPlayer();
if (owner==m_player) {
// See if it's a command center.
if (obj->isKindOf(KINDOF_COMMANDCENTER)) {
foundStart = true;
startPos = *obj->getPosition();
m_player->onStructureUndone(obj);
TheAI->pathfinder()->removeObjectFromPathfindMap(obj);
TheGameLogic->destroyObject(obj);
break;
}
}
}
if (!foundStart) {
DEBUG_LOG(("Couldn't find starting command center for ai player.\n"));
return;
}
// Find the location of the command center in the build list.
Bool foundInBuildList = false;
Coord3D buildPos;
BuildListInfo *cur = list;
while (cur) {
const ThingTemplate *tTemplate = TheThingFactory->findTemplate(cur->getTemplateName());
if (tTemplate && tTemplate->isKindOf(KINDOF_COMMANDCENTER)) {
foundInBuildList = true;
buildPos = *cur->getLocation();
cur->setInitiallyBuilt(true);
}
cur = cur->getNext();
}
Region3D bounds;
TheTerrainLogic->getMaximumPathfindExtent(&bounds);
/* calculate section of 3x3 grid:
6 7 8
3 4 5
0 1 2 */
Int gridIndex = 0;
if (startPos.x > bounds.lo.x + bounds.width()/3) {
gridIndex++;
}
if (startPos.x > bounds.lo.x + 2*bounds.width()/3) {
gridIndex++;
}
if (startPos.y > bounds.lo.y + bounds.height()/3) {
gridIndex+=3;
}
if (startPos.y > bounds.lo.y + 2*bounds.height()/3) {
gridIndex+=3;
}
Real angle = 0;
if (TheAI->getAiData()->m_rotateSkirmishBases) {
switch (gridIndex) {
case 0 : angle = 0; break;
case 1 : angle = PI/4; break;// 45 degrees.
case 2 : angle = PI/2; break; // 90 degrees;
case 3 : angle = -PI/4; break; // -45 degrees.
case 4 : angle = 0; break;
case 5 : angle = 3*PI/4; break; // 135 degrees.
case 6 : angle = -PI/2; break; // -90 degrees;
case 7 : angle = -3*PI/4; break; // -135 degrees.
case 8 : angle = PI; break; // 180 degrees.
}
}
angle += 3*PI/4;
Real s = sin(angle);
Real c = cos(angle);
cur = list;
while (cur) {
const ThingTemplate *tTemplate = TheThingFactory->findTemplate(list->getTemplateName());
if (tTemplate && tTemplate->isKindOf(KINDOF_COMMANDCENTER)) {
foundInBuildList = true;
Coord3D curPos = *cur->getLocation();
// Transform to new coords.
curPos.x -= buildPos.x;
curPos.y -= buildPos.y;
Real newX = curPos.x*c - curPos.y*s;
Real newY = curPos.y*c + curPos.x*s;
curPos.x = newX + startPos.x;
curPos.y = newY + startPos.y;
cur->setLocation(curPos);
cur->setAngle(cur->getAngle());
}
cur = cur->getNext();
}
}
//----------------------------------------------------------------------------------------------------------
/**
* Find any things that build stuff & add them to the build list. Then build any initially built
* buildings.
*/
void AISkirmishPlayer::newMap( void )
{
/* Get our proper build list. */
AsciiString mySide = m_player->getSide();
DEBUG_LOG(("AI Player side is %s\n", mySide.str()));
const AISideBuildList *build = TheAI->getAiData()->m_sideBuildLists;
while (build) {
if (build->m_side == mySide) {
BuildListInfo *buildList = build->m_buildList->duplicate();
adjustBuildList(buildList); // adjust to our start position.
m_player->setBuildList(buildList);
computeCenterAndRadiusOfBase(&m_baseCenter, &m_baseRadius);
break;
}
build = build->m_next;
}
DEBUG_ASSERTLOG(build!=NULL, ("Couldn't find build list for skirmish player.\n"));
// Build any with the initially built flag.
for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
{
AsciiString name = info->getTemplateName();
if (name.isEmpty()) continue;
const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
if (!bldgPlan) {
DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
continue;
}
if (info->isInitiallyBuilt()) {
buildStructureNow(bldgPlan, info);
} else {
info->incrementNumRebuilds(); // the initial build in the normal build list consumes a rebuild, so add one.
}
}
}
//----------------------------------------------------------------------------------------------------------
/**
* Queues up a dozer.
*/
void AISkirmishPlayer::queueDozer( void )
{
AIPlayer::queueDozer();
}
//----------------------------------------------------------------------------------------------------------
/**
* Finds a dozer that isn't building or collecting resources.
*/
Object * AISkirmishPlayer::findDozer( const Coord3D *pos )
{
return AIPlayer::findDozer(pos);
}
//----------------------------------------------------------------------------------------------------------
/**
* Find a good spot to fire a superweapon.
*/
Bool AISkirmishPlayer::computeSuperweaponTarget(const SpecialPowerTemplate *power, Coord3D *retPos, Int playerNdx, Real weaponRadius)
{
Region2D bounds;
getPlayerStructureBounds(&bounds, playerNdx);
if( power->getSpecialPowerType() == SPECIAL_CLUSTER_MINES || power->getSpecialPowerType() == NUKE_SPECIAL_CLUSTER_MINES )
{
// hackus brutus - mine the entrances to our base.
AsciiString pathLabel;
Int mode = GameLogicRandomValue(0, 2);
if (mode==1) {
pathLabel.format("%s%d", SKIRMISH_FLANK, m_player->getMpStartIndex()+1);
} else if (mode==2) {
pathLabel.format("%s%d", SKIRMISH_BACKDOOR, m_player->getMpStartIndex()+1);
} else {
pathLabel.format("%s%d", SKIRMISH_CENTER, m_player->getMpStartIndex()+1);
}
Coord3D goalPos = m_baseCenter;
Waypoint *way = TheTerrainLogic->getClosestWaypointOnPath( &goalPos, pathLabel );
if (way) {
goalPos = *way->getLocation();
} else {
Region2D bounds;
getPlayerStructureBounds(&bounds, getMyEnemyPlayerIndex());
goalPos.x = bounds.lo.x + bounds.width()/2;
goalPos.y = bounds.lo.y + bounds.height()/2;
}
Coord2D offset;
offset.x = goalPos.x-m_baseCenter.x;
offset.y = goalPos.y-m_baseCenter.y;
offset.normalize();
offset.x *= m_baseRadius;
offset.y *= m_baseRadius;
*retPos = m_baseCenter;
retPos->x += offset.x;
retPos->y += offset.y;
retPos->z = TheTerrainLogic->getGroundHeight(retPos->x, retPos->y);
return TRUE;
}
return AIPlayer::computeSuperweaponTarget(power, retPos, playerNdx, weaponRadius);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AISkirmishPlayer::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void AISkirmishPlayer::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// xfer base class info
AIPlayer::xfer( xfer );
// front base defense
xfer->xferInt( &m_curFrontBaseDefense );
// flank base defense
xfer->xferInt( &m_curFlankBaseDefense );
// front left defense angle
xfer->xferReal( &m_curFrontLeftDefenseAngle );
// front right defense angle
xfer->xferReal( &m_curFrontRightDefenseAngle );
// left flank left defense angle
xfer->xferReal( &m_curLeftFlankLeftDefenseAngle );
// left flank right defense angle
xfer->xferReal( &m_curLeftFlankRightDefenseAngle );
// right flank left defense angle
xfer->xferReal( &m_curRightFlankLeftDefenseAngle );
// right flank right defense angle
xfer->xferReal( &m_curRightFlankRightDefenseAngle );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AISkirmishPlayer::loadPostProcess( void )
{
} // end loadPostProcess