| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229 |
- /*
- ** 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 <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (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
- #if !defined(_PLAYTEST)
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // 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( BitTest( bldg->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) == TRUE) {
- 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==NULL) {
- DEBUG_LOG(("AI's Dozer got killed. 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<TeamInQueue> 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; playerNdx<ThePlayerList->getPlayerCount(); 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; i<ThePlayerList->getPlayerCount(); 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; k<ThePlayerList->getPlayerCount(); 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 (curDistSqr<bestDistanceSqr) {
- bestEnemy = curPlayer;
- bestDistanceSqr = curDistSqr;
- }
- }
- }
- if (bestEnemy!=NULL && (bestEnemy!=m_currentEnemy)) {
- m_currentEnemy = bestEnemy;
- AsciiString msg = TheNameKeyGenerator->keyToName(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();
- offset.x *= m_baseRadius;
- offset.y *= m_baseRadius;
- Real structureRadius = tTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
- Real baseCircumference = 2*PI*m_baseRadius;
- 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(("Angle is %f sin %f, cos %f \n", 180*angle/PI, s, c));
- DEBUG_LOG(("Offset is %f %f, new 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()->quickDoesPathExist(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; i<teamProto->getTemplateInfo()->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)
- {
- #ifdef DEBUG_LOGGING
- 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.
- */
- void AISkirmishPlayer::computeSuperweaponTarget(const SpecialPowerTemplate *power, Coord3D *retPos, Int playerNdx, Real weaponRadius)
- {
- Region2D bounds;
- getPlayerStructureBounds(&bounds, playerNdx);
- if (power->getName() == "SuperweaponClusterMines") {
- // 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;
- }
- 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
- #endif
|