AISkirmishPlayer.cpp 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // AISkirmishPlayer.cpp
  24. // Computerized opponent
  25. // Author: Michael S. Booth, January 2002
  26. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  27. #include "Common/GameMemory.h"
  28. #include "Common/GlobalData.h"
  29. #include "Common/Player.h"
  30. #include "Common/PlayerList.h"
  31. #include "Common/Team.h"
  32. #include "Common/ThingFactory.h"
  33. #include "Common/BuildAssistant.h"
  34. #include "Common/SpecialPower.h"
  35. #include "Common/ThingTemplate.h"
  36. #include "Common/WellKnownKeys.h"
  37. #include "Common/Xfer.h"
  38. #include "GameLogic/GameLogic.h"
  39. #include "GameLogic/Object.h"
  40. #include "GameLogic/AISkirmishPlayer.h"
  41. #include "GameLogic/SidesList.h"
  42. #include "GameLogic/AI.h"
  43. #include "GameLogic/AIPathfind.h"
  44. #include "GameLogic/TerrainLogic.h"
  45. #include "GameLogic/Module/AIUpdate.h"
  46. #include "GameLogic/Module/DozerAIUpdate.h"
  47. #include "GameLogic/Module/RebuildHoleBehavior.h"
  48. #include "GameLogic/Module/UpdateModule.h"
  49. #include "GameLogic/PartitionManager.h"
  50. #include "GameLogic/ScriptEngine.h"
  51. #include "GameLogic/Module/ProductionUpdate.h"
  52. #include "GameClient/TerrainVisual.h"
  53. #ifdef _INTERNAL
  54. // for occasional debugging...
  55. //#pragma optimize("", off)
  56. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  57. #endif
  58. #define USE_DOZER 1
  59. ///////////////////////////////////////////////////////////////////////////////////////////////////
  60. // PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
  61. ///////////////////////////////////////////////////////////////////////////////////////////////////
  62. AISkirmishPlayer::AISkirmishPlayer( Player *p ) : AIPlayer(p),
  63. m_curFlankBaseDefense(0),
  64. m_curFrontBaseDefense(0),
  65. m_curFrontLeftDefenseAngle(0),
  66. m_curFrontRightDefenseAngle(0),
  67. m_curLeftFlankLeftDefenseAngle(0),
  68. m_curLeftFlankRightDefenseAngle(0),
  69. m_curRightFlankLeftDefenseAngle(0),
  70. m_curRightFlankRightDefenseAngle(0),
  71. m_frameToCheckEnemy(0),
  72. m_currentEnemy(NULL)
  73. {
  74. m_frameLastBuildingBuilt = TheGameLogic->getFrame();
  75. p->setCanBuildUnits(true); // turn on ai production by default.
  76. }
  77. AISkirmishPlayer::~AISkirmishPlayer()
  78. {
  79. clearTeamsInQueue();
  80. }
  81. /**
  82. * Build our base.
  83. */
  84. void AISkirmishPlayer::processBaseBuilding( void )
  85. {
  86. //
  87. // Refresh base buildings. Scan through list, if a building is missing,
  88. // rebuild it, unless it's rebuild count is zero.
  89. //
  90. if (m_readyToBuildStructure)
  91. {
  92. const ThingTemplate *bldgPlan=NULL;
  93. BuildListInfo *bldgInfo = NULL;
  94. Bool isPriority = false;
  95. Object *bldg = NULL;
  96. const ThingTemplate *powerPlan=NULL;
  97. BuildListInfo *powerInfo = NULL;
  98. Bool isUnderPowered = !m_player->getEnergy()->hasSufficientPower();
  99. Bool powerUnderConstruction = false;
  100. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  101. {
  102. AsciiString name = info->getTemplateName();
  103. if (name.isEmpty()) continue;
  104. const ThingTemplate *curPlan = TheThingFactory->findTemplate( name );
  105. if (!curPlan) {
  106. DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
  107. continue;
  108. }
  109. bldg = TheGameLogic->findObjectByID( info->getObjectID() );
  110. // check for hole.
  111. if (info->getObjectID() != INVALID_ID) {
  112. // used to have a building.
  113. Object *bldg = TheGameLogic->findObjectByID( info->getObjectID() );
  114. if (bldg==NULL) {
  115. // got destroyed.
  116. ObjectID priorID;
  117. priorID = info->getObjectID();
  118. info->setObjectID(INVALID_ID);
  119. info->setObjectTimestamp(TheGameLogic->getFrame()+1);
  120. // Scan for a GLA hole. KINDOF_REBUILD_HOLE
  121. Object *obj;
  122. for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) {
  123. if (!obj->isKindOf(KINDOF_REBUILD_HOLE)) continue;
  124. RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( obj );
  125. if( rhbi ) {
  126. ObjectID spawnerID = rhbi->getSpawnerID();
  127. if (priorID == spawnerID) {
  128. DEBUG_LOG(("AI Found hole to rebuild %s\n", curPlan->getName().str()));
  129. info->setObjectID(obj->getID());
  130. }
  131. }
  132. }
  133. } else {
  134. if (bldg->getControllingPlayer() == m_player) {
  135. // Check for built or dozer missing.
  136. if( bldg->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
  137. {
  138. if (bldg->isKindOf(KINDOF_FS_POWER) && !bldg->isKindOf(KINDOF_CASH_GENERATOR))
  139. {
  140. powerUnderConstruction = true;
  141. }
  142. // make sure dozer is working on him.
  143. ObjectID builder = bldg->getBuilderID();
  144. Object* myDozer = TheGameLogic->findObjectByID(builder);
  145. if (myDozer && ( myDozer->getControllingPlayer() != m_player || myDozer->isDisabledByType( DISABLED_UNMANNED ) ) )
  146. {//I don't expect this dozer to work well with me.
  147. myDozer = NULL;
  148. bldg->setBuilder( NULL );
  149. }
  150. if (myDozer==NULL) {
  151. DEBUG_LOG(("AI's Dozer got killed (or captured). Find another dozer.\n"));
  152. queueDozer();
  153. myDozer = findDozer(bldg->getPosition());
  154. if (myDozer==NULL || myDozer->getAI()==NULL) {
  155. continue;
  156. }
  157. myDozer->getAI()->aiResumeConstruction(bldg, CMD_FROM_AI);
  158. } else {
  159. // make sure he is building.
  160. myDozer->getAI()->aiResumeConstruction(bldg, CMD_FROM_AI);
  161. }
  162. }
  163. } else {
  164. // oops, got captured.
  165. info->setObjectID(INVALID_ID);
  166. info->setObjectTimestamp(TheGameLogic->getFrame()+1);
  167. }
  168. }
  169. }
  170. if (info->getObjectID()==INVALID_ID && info->getObjectTimestamp()>0) {
  171. // this object was built at some time, and got destroyed at or near objectTimestamp.
  172. // Wait a few seconds before initiating a rebuild.
  173. if (info->getObjectTimestamp()+TheAI->getAiData()->m_rebuildDelaySeconds*LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) {
  174. continue;
  175. } else {
  176. DEBUG_LOG(("Enabling rebuild for %s\n", info->getTemplateName().str()));
  177. info->setObjectTimestamp(0); // ready to build.
  178. }
  179. }
  180. if (bldg) {
  181. continue; // already built.
  182. }
  183. // Make sure it is safe to build here.
  184. if (!isLocationSafe(info->getLocation(), curPlan)) {
  185. continue;
  186. }
  187. if (info->isPriorityBuild()) {
  188. // Always take priority build, unless we already have priority build.
  189. if (!isPriority) {
  190. bldgPlan = curPlan;
  191. bldgInfo = info;
  192. isPriority = true;
  193. }
  194. }
  195. if (curPlan->isKindOf(KINDOF_FS_POWER)) {
  196. if (powerPlan==NULL && !curPlan->isKindOf(KINDOF_CASH_GENERATOR)) {
  197. if (isUnderPowered || info->isAutomaticBuild()) {
  198. powerPlan = curPlan;
  199. powerInfo = info;
  200. }
  201. }
  202. }
  203. if (!info->isAutomaticBuild()) {
  204. continue; // marked to not build automatically.
  205. }
  206. Object *dozer = findDozer(info->getLocation());
  207. if (dozer==NULL) {
  208. if (isUnderPowered) {
  209. queueDozer();
  210. }
  211. continue;
  212. }
  213. if (TheBuildAssistant->canMakeUnit(dozer, bldgPlan)!=CANMAKE_OK) {
  214. if (info->isBuildable()) {
  215. AsciiString bldgName = info->getTemplateName();
  216. bldgName.concat(" - Dozer unable to build - money or technology missing.");
  217. TheScriptEngine->AppendDebugMessage(bldgName, false);
  218. }
  219. continue;
  220. }
  221. // check if this building has any "rebuilds" left
  222. if (info->isBuildable())
  223. {
  224. if (bldgPlan == NULL) {
  225. bldgPlan = curPlan;
  226. bldgInfo = info;
  227. }
  228. }
  229. }
  230. if (powerPlan && powerInfo && !powerPlan->isEquivalentTo(bldgPlan)) {
  231. if (!powerUnderConstruction) {
  232. bldgPlan = powerPlan;
  233. bldgInfo = powerInfo;
  234. DEBUG_LOG(("Forcing build of power plant.\n"));
  235. }
  236. }
  237. if (bldgPlan && bldgInfo) {
  238. #ifdef USE_DOZER
  239. // dozer-construct the building
  240. bldg = buildStructureWithDozer(bldgPlan, bldgInfo);
  241. // store the object with the build order
  242. if (bldg)
  243. {
  244. bldgInfo->setObjectID( bldg->getID() );
  245. bldgInfo->decrementNumRebuilds();
  246. m_readyToBuildStructure = false;
  247. m_structureTimer = TheAI->getAiData()->m_structureSeconds*LOGICFRAMES_PER_SECOND;
  248. if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
  249. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresPoorMod;
  250. } else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
  251. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresWealthyMod;
  252. }
  253. m_frameLastBuildingBuilt = TheGameLogic->getFrame();
  254. // only build one building per delay loop
  255. } // bldg built
  256. #else
  257. // force delay between rebuilds
  258. if (TheGameLogic->getFrame() - m_frameLastBuildingBuilt < framesToBuild)
  259. {
  260. m_buildDelay = framesToBuild - (TheGameLogic->getFrame() - m_frameLastBuildingBuilt);
  261. return;
  262. } else {
  263. // building is missing, (re)build it
  264. // deduct money to build, if we have it
  265. Int cost = bldgPlan->calcCostToBuild( m_player );
  266. if (m_player->getMoney()->countMoney() >= cost)
  267. {
  268. // we have the money, deduct it
  269. m_player->getMoney()->withdraw( cost );
  270. // inst-construct the building
  271. bldg = buildStructureNow(bldgPlan, info, NULL);
  272. // store the object with the build order
  273. if (bldg)
  274. {
  275. info->setObjectID( bldg->getID() );
  276. info->decrementNumRebuilds();
  277. m_readyToBuildStructure = false;
  278. m_structureTimer = TheAI->getAiData()->m_structureSeconds*LOGICFRAMES_PER_SECOND;
  279. if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
  280. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresPoorMod;
  281. } else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
  282. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresWealthyMod;
  283. }
  284. m_frameLastBuildingBuilt = TheGameLogic->getFrame();
  285. // only build one building per delay loop
  286. break;
  287. } // bldg built
  288. } // have money
  289. } // rebuild delay ok
  290. #endif
  291. }
  292. }
  293. }
  294. /**
  295. * Invoked when a unit I am training comes into existence
  296. */
  297. void AISkirmishPlayer::onUnitProduced( Object *factory, Object *unit )
  298. {
  299. AIPlayer::onUnitProduced(factory, unit);
  300. }
  301. /**
  302. * Search the computer player's buildings for one that can build the given request
  303. * and start training the unit.
  304. * If busyOK is true, it will queue a unit even if one is building. This lets
  305. * script invoked teams "push" to the front of the queue.
  306. */
  307. Bool AISkirmishPlayer::startTraining( WorkOrder *order, Bool busyOK, AsciiString teamName)
  308. {
  309. Object *factory = findFactory(order->m_thing, busyOK);
  310. if( factory )
  311. {
  312. ProductionUpdateInterface *pu = factory->getProductionUpdateInterface();
  313. if (pu && pu->queueCreateUnit( order->m_thing, pu->requestUniqueUnitID() )) {
  314. order->m_factoryID = factory->getID();
  315. if (TheGlobalData->m_debugAI) {
  316. AsciiString teamStr = "Queuing ";
  317. teamStr.concat(order->m_thing->getName());
  318. teamStr.concat(" for ");
  319. teamStr.concat(teamName);
  320. TheScriptEngine->AppendDebugMessage(teamStr, false);
  321. }
  322. return true;
  323. }
  324. } // end if
  325. return FALSE;
  326. }
  327. /**
  328. * Check if this team is buildable, doesn't exceed maximum limits, meets conditions, and isn't under construction.
  329. */
  330. Bool AISkirmishPlayer::isAGoodIdeaToBuildTeam( TeamPrototype *proto )
  331. {
  332. // Check condition.
  333. if (!proto->evaluateProductionCondition()) {
  334. return false;
  335. }
  336. // check build limit
  337. if (proto->countTeamInstances() >= proto->getTemplateInfo()->m_maxInstances){
  338. if (TheGlobalData->m_debugAI) {
  339. AsciiString str;
  340. str.format("Team %s not chosen - %d already exist.", proto->getName().str(), proto->countTeamInstances());
  341. TheScriptEngine->AppendDebugMessage(str, false);
  342. }
  343. return false; // Max already built.
  344. }
  345. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  346. {
  347. TeamInQueue *team = iter.cur();
  348. if (team->m_team->getPrototype() == proto) {
  349. return false; // currently building one of these.
  350. }
  351. }
  352. Bool needMoney;
  353. if (!isPossibleToBuildTeam( proto, true, needMoney)) {
  354. if (TheGlobalData->m_debugAI) {
  355. AsciiString str;
  356. if (needMoney) {
  357. str.format("Team %s not chosen - Not enough money.", proto->getName().str());
  358. } else {
  359. str.format("Team %s not chosen - Factory/tech missing or busy.", proto->getName().str());
  360. }
  361. TheScriptEngine->AppendDebugMessage(str, false);
  362. }
  363. return false;
  364. }
  365. return true;
  366. }
  367. /**
  368. * See if any existing teams need reinforcements, and have higher priority.
  369. */
  370. Bool AISkirmishPlayer::selectTeamToReinforce( Int minPriority )
  371. {
  372. return AIPlayer::selectTeamToReinforce(minPriority);
  373. }
  374. /**
  375. * Determine the next team to build. Return true if one was selected.
  376. */
  377. Bool AISkirmishPlayer::selectTeamToBuild( void )
  378. {
  379. return AIPlayer::selectTeamToBuild();
  380. }
  381. /**
  382. Build a specific building.
  383. */
  384. void AISkirmishPlayer::buildSpecificAIBuilding(const AsciiString &thingName)
  385. {
  386. //
  387. Bool found = false;
  388. Bool foundUnbuilt = false;
  389. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  390. {
  391. if (info->getTemplateName()==thingName)
  392. {
  393. AsciiString name = info->getTemplateName();
  394. if (name.isEmpty()) continue;
  395. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
  396. if (!bldgPlan) {
  397. DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
  398. continue;
  399. }
  400. Object *bldg = TheGameLogic->findObjectByID( info->getObjectID() );
  401. found = true;
  402. if (bldg) {
  403. continue; // already built.
  404. }
  405. if (info->isPriorityBuild()) {
  406. continue; // already marked for priority build.
  407. }
  408. foundUnbuilt = true;
  409. info->markPriorityBuild();
  410. break;
  411. }
  412. }
  413. if (foundUnbuilt) {
  414. m_buildDelay = 0;
  415. AsciiString buildingStr = "Queueing building '";
  416. buildingStr.concat(thingName);
  417. buildingStr.concat("' for construction.");
  418. TheScriptEngine->AppendDebugMessage(buildingStr, false);
  419. } else if (found) {
  420. AsciiString buildingStr = "Warning - all instances of building '";
  421. buildingStr.concat(thingName);
  422. buildingStr.concat("' are already built or queued for build, not queueing.");
  423. TheScriptEngine->AppendDebugMessage(buildingStr, false);
  424. } else {
  425. AsciiString buildingStr = "Error - could not find building '";
  426. buildingStr.concat(thingName);
  427. buildingStr.concat("' in the building template list.");
  428. TheScriptEngine->AppendDebugMessage(buildingStr, false);
  429. }
  430. }
  431. /**
  432. Gets the player index of my enemy.
  433. */
  434. Int AISkirmishPlayer::getMyEnemyPlayerIndex(void) {
  435. Int playerNdx;
  436. if (m_currentEnemy) {
  437. return m_currentEnemy->getPlayerIndex();
  438. }
  439. // For now, return first human player, as there should only be one. jba
  440. for (playerNdx=0; playerNdx<ThePlayerList->getPlayerCount(); playerNdx++) {
  441. if (ThePlayerList->getNthPlayer(playerNdx)->getPlayerType() == PLAYER_HUMAN) {
  442. break;
  443. }
  444. }
  445. return playerNdx;
  446. }
  447. /**
  448. Get the AI's enemy. Recalc if it has been a while (5 seconds.)
  449. */
  450. void AISkirmishPlayer::acquireEnemy(void)
  451. {
  452. Player *bestEnemy = NULL;
  453. Real bestDistanceSqr = HUGE_DIST*HUGE_DIST;
  454. if (m_currentEnemy) {
  455. Bool inBadShape = !m_currentEnemy->hasAnyUnits() || !m_currentEnemy->hasAnyBuildFacility();
  456. if (!inBadShape) return;
  457. }
  458. // look for the closest enemy.
  459. Int i;
  460. for (i=0; i<ThePlayerList->getPlayerCount(); i++) {
  461. Player *curPlayer = ThePlayerList->getNthPlayer(i);
  462. if (m_player->getRelationship(curPlayer->getDefaultTeam()) == ENEMIES) {
  463. if (curPlayer->hasAnyObjects()==false) continue; // not much of an enemy.
  464. // ok, we got an enemy;
  465. // If a player is out of units, or out of build facilities, we can lower his priority.
  466. Bool inBadShape = !curPlayer->hasAnyUnits() || !curPlayer->hasAnyBuildFacility();
  467. Coord3D enemyPos = m_baseCenter;
  468. Region2D bounds;
  469. getPlayerStructureBounds(&bounds, i);
  470. enemyPos.x = bounds.lo.x + bounds.width()/2;
  471. enemyPos.y = bounds.lo.y + bounds.height()/2;
  472. Real curDistSqr = sqr(enemyPos.x-m_baseCenter.x) + sqr(enemyPos.y-m_baseCenter.y);
  473. //Fudge for in bad shape. If an enemy is crippled, concentrate on the other ones.
  474. if (inBadShape) {
  475. curDistSqr = HUGE_DIST*HUGE_DIST*0.5f;
  476. }
  477. // See if other ai's are attacking this target.
  478. // We don't want the ai's to gang up on one enemy.
  479. Int k;
  480. for (k=0; k<ThePlayerList->getPlayerCount(); k++) {
  481. if (k==i) continue; // don't count self.
  482. Player *somePlayer = ThePlayerList->getNthPlayer(k);
  483. if (somePlayer->isSkirmishAIPlayer() && (somePlayer->getCurrentEnemy()==curPlayer)) {
  484. // Some ai is already targeting this guy. Add a distance penalty.
  485. curDistSqr += (500*500);
  486. }
  487. if (somePlayer->isSkirmishAIPlayer() && (somePlayer->getCurrentEnemy()==m_player)) {
  488. // he is attacking me. So I will (gently) prefer to attack him.
  489. curDistSqr -= (25*25);
  490. if (curDistSqr<0) curDistSqr = 0;
  491. }
  492. }
  493. // Ai enemy - will take if we don't get a better offer.
  494. if (curDistSqr<bestDistanceSqr) {
  495. bestEnemy = curPlayer;
  496. bestDistanceSqr = curDistSqr;
  497. }
  498. }
  499. }
  500. if (bestEnemy!=NULL && (bestEnemy!=m_currentEnemy)) {
  501. m_currentEnemy = bestEnemy;
  502. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  503. msg.concat(" acquiring target enemy player: ");
  504. msg.concat(TheNameKeyGenerator->keyToName(m_currentEnemy->getPlayerNameKey()));
  505. TheScriptEngine->AppendDebugMessage( msg, false);
  506. }
  507. }
  508. /**
  509. Get the AI's enemy. Recalc if it has been a while (20 seconds.)
  510. */
  511. Player *AISkirmishPlayer::getAiEnemy(void)
  512. {
  513. if (TheGameLogic->getFrame()>=m_frameToCheckEnemy) {
  514. m_frameToCheckEnemy = TheGameLogic->getFrame() + 5*LOGICFRAMES_PER_SECOND;
  515. acquireEnemy();
  516. }
  517. return m_currentEnemy;
  518. }
  519. /**
  520. Build base defense structures on the front or flank of the base.
  521. */
  522. void AISkirmishPlayer::buildAIBaseDefense(Bool flank)
  523. {
  524. const AISideInfo *resInfo = TheAI->getAiData()->m_sideInfo;
  525. AsciiString defenseTemplateName;
  526. while (resInfo) {
  527. if (resInfo->m_side == m_player->getSide()) {
  528. defenseTemplateName = resInfo->m_baseDefenseStructure1;
  529. break;
  530. }
  531. resInfo = resInfo->m_next;
  532. }
  533. if (resInfo) {
  534. buildAIBaseDefenseStructure(resInfo->m_baseDefenseStructure1, flank);
  535. }
  536. }
  537. /**
  538. Build base defense structures on the front or flank of the base.
  539. Base defenses are placed as follows:
  540. m_baseCenter and m_baseRadius are calculated on map load.
  541. Defenses are placed along the this circle.
  542. Front defenses (!flank) are placed starting at the "Center" approach path.
  543. The first front defense is placed towards th Center path. Number 2 is placed
  544. to the left of #1, #3 is placed to the right of #1, #4 is placed to the left of
  545. #2 and so on. So it looks like:
  546. #1
  547. #2 #3
  548. #6 #4 #5 #7
  549. #8 #9
  550. The flank base defenses cover the "Flank" approach, and the "Backdoor" approach.
  551. They alternate between these two, so the first flank defense covers flank, and the second
  552. covers backdoor, and continue to alternate. They cover the approach using the same
  553. pattern as front above.
  554. John A.
  555. */
  556. void AISkirmishPlayer::buildAIBaseDefenseStructure(const AsciiString &thingName, Bool flank)
  557. {
  558. const ThingTemplate *tTemplate = TheThingFactory->findTemplate(thingName);
  559. if (tTemplate==NULL) {
  560. DEBUG_CRASH(("Couldn't find base defense structure '%s' for side %s", thingName.str(), m_player->getSide().str()));
  561. return;
  562. }
  563. do {
  564. AsciiString pathLabel;
  565. if (flank) {
  566. if (m_curFlankBaseDefense&1) {
  567. pathLabel.format("%s%d", SKIRMISH_FLANK, m_player->getMpStartIndex()+1);
  568. } else {
  569. pathLabel.format("%s%d", SKIRMISH_BACKDOOR, m_player->getMpStartIndex()+1);
  570. }
  571. } else {
  572. pathLabel.format("%s%d", SKIRMISH_CENTER, m_player->getMpStartIndex()+1);
  573. }
  574. Coord3D goalPos = m_baseCenter;
  575. Waypoint *way = TheTerrainLogic->getClosestWaypointOnPath( &goalPos, pathLabel );
  576. if (way) {
  577. goalPos = *way->getLocation();
  578. } else {
  579. if (flank) return;
  580. Region2D bounds;
  581. getPlayerStructureBounds(&bounds, getMyEnemyPlayerIndex());
  582. goalPos.x = bounds.lo.x + bounds.width()/2;
  583. goalPos.y = bounds.lo.y + bounds.height()/2;
  584. }
  585. Coord2D offset;
  586. offset.x = goalPos.x-m_baseCenter.x;
  587. offset.y = goalPos.y-m_baseCenter.y;
  588. offset.normalize();
  589. Real defenseDistance = m_baseRadius;
  590. defenseDistance += TheAI->getAiData()->m_skirmishBaseDefenseExtraDistance;
  591. offset.x *= defenseDistance;
  592. offset.y *= defenseDistance;
  593. Real structureRadius = tTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
  594. Real baseCircumference = 2*PI*defenseDistance;
  595. Real angleOffset = 2*PI*(structureRadius*4/baseCircumference);
  596. Int selector;
  597. Real angle;
  598. if (flank) {
  599. selector = m_curFlankBaseDefense>>1;
  600. if (m_curFlankBaseDefense&1) {
  601. if (selector&1) {
  602. m_curLeftFlankRightDefenseAngle -= angleOffset;
  603. angle = m_curLeftFlankRightDefenseAngle;
  604. } else {
  605. angle = m_curLeftFlankLeftDefenseAngle;
  606. m_curLeftFlankLeftDefenseAngle += angleOffset;
  607. }
  608. } else {
  609. if (selector&1) {
  610. m_curRightFlankRightDefenseAngle -= angleOffset;
  611. angle = m_curRightFlankRightDefenseAngle;
  612. } else {
  613. angle = m_curRightFlankLeftDefenseAngle;
  614. m_curRightFlankLeftDefenseAngle += angleOffset;
  615. }
  616. }
  617. } else {
  618. selector = m_curFrontBaseDefense;
  619. if (selector&1) {
  620. m_curFrontRightDefenseAngle -= angleOffset;
  621. angle = m_curFrontRightDefenseAngle;
  622. } else {
  623. angle = m_curFrontLeftDefenseAngle;
  624. m_curFrontLeftDefenseAngle += angleOffset;
  625. }
  626. }
  627. if (angle > PI/3) break;
  628. Real s = sin(angle);
  629. Real c = cos(angle);
  630. DEBUG_LOG(("buildAIBaseDefenseStructure -- Angle is %f sin %f, cos %f \n", 180*angle/PI, s, c));
  631. DEBUG_LOG(("buildAIBaseDefenseStructure -- Offset is %f %f, Final Position is %f, %f \n",
  632. offset.x, offset.y,
  633. offset.x*c - offset.y*s,
  634. offset.y*c + offset.x*s
  635. ));
  636. Coord3D buildPos = m_baseCenter;
  637. buildPos.x += offset.x*c - offset.y*s;
  638. buildPos.y += offset.y*c + offset.x*s;
  639. /* See if we can build there. */
  640. Bool canBuild;
  641. Real placeAngle = tTemplate->getPlacementViewAngle();
  642. canBuild = LBC_OK == TheBuildAssistant->isLocationLegalToBuild(&buildPos, tTemplate, placeAngle,
  643. BuildAssistant::TERRAIN_RESTRICTIONS|BuildAssistant::NO_OBJECT_OVERLAP, NULL, m_player);
  644. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  645. if (flank) {
  646. m_curFlankBaseDefense++;
  647. } else {
  648. m_curFrontBaseDefense++;
  649. }
  650. if (canBuild) {
  651. m_player->addToPriorityBuildList(thingName, &buildPos, placeAngle);
  652. break;
  653. }
  654. } while (true);
  655. }
  656. /**
  657. Checks bridges along a waypoint path. If any are destroyed, sends a dozer to fix, and returns true.
  658. If there is no bridge problem, returns false.
  659. */
  660. Bool AISkirmishPlayer::checkBridges(Object *unit, Waypoint *way)
  661. {
  662. Coord3D unitPos = *unit->getPosition();
  663. AIUpdateInterface *ai = unit->getAI();
  664. if (!ai) return false; // no ai
  665. const LocomotorSet& locoSet = ai->getLocomotorSet();
  666. Waypoint *curWay;
  667. for (curWay = way; curWay; curWay = curWay->getNext()) {
  668. if (TheAI->pathfinder()->clientSafeQuickDoesPathExist(locoSet, &unitPos, curWay->getLocation())) {
  669. continue;
  670. }
  671. ObjectID brokenBridge = INVALID_ID;
  672. if (TheAI->pathfinder()->findBrokenBridge(locoSet, &unitPos, curWay->getLocation(), &brokenBridge)) {
  673. repairStructure(brokenBridge);
  674. return true;
  675. }
  676. }
  677. return false;
  678. }
  679. /**
  680. Build a specific team. If priorityBuild, put at front of queue with priority set.
  681. */
  682. void AISkirmishPlayer::buildSpecificAITeam( TeamPrototype *teamProto, Bool priorityBuild)
  683. {
  684. AIPlayer::buildSpecificAITeam(teamProto, priorityBuild);
  685. }
  686. /**
  687. Recruit a specific team, within the specific radius of the home position.
  688. */
  689. void AISkirmishPlayer::recruitSpecificAITeam(TeamPrototype *teamProto, Real recruitRadius)
  690. {
  691. if (recruitRadius < 1) recruitRadius = 99999.0f;
  692. //
  693. // Create "Team in queue" based on team population
  694. //
  695. if (teamProto)
  696. {
  697. if (teamProto->getIsSingleton()) {
  698. Team *singletonTeam = TheTeamFactory->findTeam( teamProto->getName() );
  699. if (singletonTeam && singletonTeam->hasAnyObjects()) {
  700. AsciiString teamStr = "Unable to recruit singleton team '";
  701. teamStr.concat("' because team already exists.");
  702. TheScriptEngine->AppendDebugMessage(teamStr, false);
  703. return;
  704. }
  705. }
  706. if (!teamProto->getTemplateInfo()->m_hasHomeLocation)
  707. {
  708. AsciiString teamStr = "Error : team '";
  709. teamStr.concat(teamProto->getName());
  710. teamStr.concat("' has no Home Position (or Origin).");
  711. TheScriptEngine->AppendDebugMessage(teamStr, false);
  712. }
  713. // create inactive team to place members into as they are built
  714. // when team is complete, the team is activated
  715. Team *theTeam = TheTeamFactory->createInactiveTeam( teamProto->getName() );
  716. AsciiString teamName = teamProto->getName();
  717. teamName.concat(" - Recruiting.");
  718. TheScriptEngine->AppendDebugMessage(teamName, false);
  719. const TCreateUnitsInfo *unitInfo = &teamProto->getTemplateInfo()->m_unitsInfo[0];
  720. // WorkOrder *orders = NULL;
  721. Int i;
  722. Int unitsRecruited = 0;
  723. // Recruit.
  724. for( i=0; i<teamProto->getTemplateInfo()->m_numUnitsInfo; i++ )
  725. {
  726. const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
  727. if (thing)
  728. {
  729. int count = unitInfo[i].maxUnits;
  730. while (count>0) {
  731. Object *unit = theTeam->tryToRecruit(thing, &teamProto->getTemplateInfo()->m_homeLocation, recruitRadius);
  732. if (unit)
  733. {
  734. unitsRecruited++;
  735. AsciiString teamStr = "Team '";
  736. teamStr.concat(theTeam->getPrototype()->getName());
  737. teamStr.concat("' recruits ");
  738. teamStr.concat(thing->getName());
  739. teamStr.concat(" from team '");
  740. teamStr.concat(unit->getTeam()->getPrototype()->getName());
  741. teamStr.concat("'");
  742. TheScriptEngine->AppendDebugMessage(teamStr, false);
  743. unit->setTeam(theTeam);
  744. AIUpdateInterface *ai = unit->getAIUpdateInterface();
  745. if (ai)
  746. {
  747. #if defined(_DEBUG) || defined(_INTERNAL)
  748. Coord3D pos = *unit->getPosition();
  749. Coord3D to = teamProto->getTemplateInfo()->m_homeLocation;
  750. DEBUG_LOG(("Moving unit from %f,%f to %f,%f\n", pos.x, pos.y , to.x, to.y ));
  751. #endif
  752. ai->aiMoveToPosition( &teamProto->getTemplateInfo()->m_homeLocation, CMD_FROM_AI);
  753. }
  754. } else {
  755. break;
  756. }
  757. count--;
  758. }
  759. }
  760. }
  761. if (unitsRecruited>0)
  762. {
  763. /* We have something to build. */
  764. TeamInQueue *team = newInstance(TeamInQueue);
  765. // Put in front of queue.
  766. prependTo_TeamReadyQueue(team);
  767. team->m_priorityBuild = false;
  768. team->m_workOrders = NULL;
  769. team->m_frameStarted = TheGameLogic->getFrame();
  770. team->m_team = theTeam;
  771. AsciiString teamName = teamProto->getName();
  772. teamName.concat(" - Finished recruiting.");
  773. TheScriptEngine->AppendDebugMessage(teamName, false);
  774. } else {
  775. //disband.
  776. if (!theTeam->getPrototype()->getIsSingleton()) {
  777. theTeam->deleteInstance();
  778. theTeam = NULL;
  779. }
  780. AsciiString teamName = teamProto->getName();
  781. teamName.concat(" - Recruited 0 units, disbanding.");
  782. TheScriptEngine->AppendDebugMessage(teamName, false);
  783. }
  784. }
  785. }
  786. /**
  787. * Train our teams.
  788. */
  789. void AISkirmishPlayer::processTeamBuilding( void )
  790. {
  791. // select a new team
  792. if (selectTeamToBuild()) {
  793. queueUnits();
  794. }
  795. }
  796. //----------------------------------------------------------------------------------------------------------
  797. /**
  798. * See if it's time to build another base building.
  799. */
  800. void AISkirmishPlayer::doBaseBuilding( void )
  801. {
  802. if (m_player->getCanBuildBase()) {
  803. // See if we are ready to start trying a structure.
  804. if (!m_readyToBuildStructure) {
  805. m_structureTimer--;
  806. if (m_structureTimer<=0) {
  807. m_readyToBuildStructure = true;
  808. m_buildDelay = 0;
  809. }
  810. if (m_structureTimer > 3*LOGICFRAMES_PER_SECOND) {
  811. m_structureTimer = 3*LOGICFRAMES_PER_SECOND;
  812. }
  813. }
  814. // This timer is to keep from banging on the logic each frame. If something interesting
  815. // happens, like a building is added or a unit finished, the timers are shortcut.
  816. m_buildDelay--;
  817. if (m_buildDelay<1) {
  818. if (m_readyToBuildStructure) {
  819. processBaseBuilding();
  820. }
  821. if (m_buildDelay<1) { // processBaseBuilding may reset m_buildDelay.
  822. m_buildDelay = 2*LOGICFRAMES_PER_SECOND; // check again in 2 seconds.
  823. }
  824. // Note that this timer gets shortcut when a building is completed.
  825. }
  826. }
  827. }
  828. //----------------------------------------------------------------------------------------------------------
  829. /**
  830. * See if any ready teams have finished moving to the rally point.
  831. */
  832. void AISkirmishPlayer::checkReadyTeams( void )
  833. {
  834. AIPlayer::checkReadyTeams();
  835. }
  836. //----------------------------------------------------------------------------------------------------------
  837. /**
  838. * See if any queued teams have finished building, or have run out of time.
  839. */
  840. void AISkirmishPlayer::checkQueuedTeams( void )
  841. {
  842. AIPlayer::checkQueuedTeams();
  843. }
  844. //----------------------------------------------------------------------------------------------------------
  845. /**
  846. * See if it is time to start another ai team building.
  847. */
  848. void AISkirmishPlayer::doTeamBuilding( void )
  849. {
  850. // See if any teams are expired.
  851. if (m_player->getCanBuildUnits()) {
  852. // See if we are ready to start trying a team.
  853. if (!m_readyToBuildTeam) {
  854. m_teamTimer--;
  855. if (m_teamTimer<=0) {
  856. m_readyToBuildTeam = true;
  857. m_teamDelay = 0;
  858. }
  859. if (m_teamTimer > 3*LOGICFRAMES_PER_SECOND) {
  860. m_teamTimer = 3*LOGICFRAMES_PER_SECOND;
  861. }
  862. }
  863. // This timer is to keep from banging on the logic each frame. If something interesting
  864. // happens, like a building is added or a unit finished, the timers are shortcut.
  865. m_teamDelay--;
  866. if (m_teamDelay<1) {
  867. queueUnits(); // update the queues.
  868. if (m_readyToBuildTeam) {
  869. processTeamBuilding();
  870. }
  871. m_teamDelay = 2*LOGICFRAMES_PER_SECOND; // check again in 5 seconds.
  872. // Note that this timer gets shortcut when a unit or building is completed.
  873. }
  874. }
  875. }
  876. //----------------------------------------------------------------------------------------------------------
  877. /**
  878. * Perform computer-controlled player AI
  879. */
  880. void AISkirmishPlayer::update( void )
  881. {
  882. AIPlayer::update();
  883. }
  884. //----------------------------------------------------------------------------------------------------------
  885. /**
  886. * Adjusts the build list to match the starting position.
  887. */
  888. void AISkirmishPlayer::adjustBuildList(BuildListInfo *list)
  889. {
  890. Bool foundStart = false;
  891. Coord3D startPos;
  892. // Find our command center location.
  893. Object *obj;
  894. for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
  895. {
  896. Player *owner = obj->getControllingPlayer();
  897. if (owner==m_player) {
  898. // See if it's a command center.
  899. if (obj->isKindOf(KINDOF_COMMANDCENTER)) {
  900. foundStart = true;
  901. startPos = *obj->getPosition();
  902. m_player->onStructureUndone(obj);
  903. TheAI->pathfinder()->removeObjectFromPathfindMap(obj);
  904. TheGameLogic->destroyObject(obj);
  905. break;
  906. }
  907. }
  908. }
  909. if (!foundStart) {
  910. DEBUG_LOG(("Couldn't find starting command center for ai player.\n"));
  911. return;
  912. }
  913. // Find the location of the command center in the build list.
  914. Bool foundInBuildList = false;
  915. Coord3D buildPos;
  916. BuildListInfo *cur = list;
  917. while (cur) {
  918. const ThingTemplate *tTemplate = TheThingFactory->findTemplate(cur->getTemplateName());
  919. if (tTemplate && tTemplate->isKindOf(KINDOF_COMMANDCENTER)) {
  920. foundInBuildList = true;
  921. buildPos = *cur->getLocation();
  922. cur->setInitiallyBuilt(true);
  923. }
  924. cur = cur->getNext();
  925. }
  926. Region3D bounds;
  927. TheTerrainLogic->getMaximumPathfindExtent(&bounds);
  928. /* calculate section of 3x3 grid:
  929. 6 7 8
  930. 3 4 5
  931. 0 1 2 */
  932. Int gridIndex = 0;
  933. if (startPos.x > bounds.lo.x + bounds.width()/3) {
  934. gridIndex++;
  935. }
  936. if (startPos.x > bounds.lo.x + 2*bounds.width()/3) {
  937. gridIndex++;
  938. }
  939. if (startPos.y > bounds.lo.y + bounds.height()/3) {
  940. gridIndex+=3;
  941. }
  942. if (startPos.y > bounds.lo.y + 2*bounds.height()/3) {
  943. gridIndex+=3;
  944. }
  945. Real angle = 0;
  946. if (TheAI->getAiData()->m_rotateSkirmishBases) {
  947. switch (gridIndex) {
  948. case 0 : angle = 0; break;
  949. case 1 : angle = PI/4; break;// 45 degrees.
  950. case 2 : angle = PI/2; break; // 90 degrees;
  951. case 3 : angle = -PI/4; break; // -45 degrees.
  952. case 4 : angle = 0; break;
  953. case 5 : angle = 3*PI/4; break; // 135 degrees.
  954. case 6 : angle = -PI/2; break; // -90 degrees;
  955. case 7 : angle = -3*PI/4; break; // -135 degrees.
  956. case 8 : angle = PI; break; // 180 degrees.
  957. }
  958. }
  959. angle += 3*PI/4;
  960. Real s = sin(angle);
  961. Real c = cos(angle);
  962. cur = list;
  963. while (cur) {
  964. const ThingTemplate *tTemplate = TheThingFactory->findTemplate(list->getTemplateName());
  965. if (tTemplate && tTemplate->isKindOf(KINDOF_COMMANDCENTER)) {
  966. foundInBuildList = true;
  967. Coord3D curPos = *cur->getLocation();
  968. // Transform to new coords.
  969. curPos.x -= buildPos.x;
  970. curPos.y -= buildPos.y;
  971. Real newX = curPos.x*c - curPos.y*s;
  972. Real newY = curPos.y*c + curPos.x*s;
  973. curPos.x = newX + startPos.x;
  974. curPos.y = newY + startPos.y;
  975. cur->setLocation(curPos);
  976. cur->setAngle(cur->getAngle());
  977. }
  978. cur = cur->getNext();
  979. }
  980. }
  981. //----------------------------------------------------------------------------------------------------------
  982. /**
  983. * Find any things that build stuff & add them to the build list. Then build any initially built
  984. * buildings.
  985. */
  986. void AISkirmishPlayer::newMap( void )
  987. {
  988. /* Get our proper build list. */
  989. AsciiString mySide = m_player->getSide();
  990. DEBUG_LOG(("AI Player side is %s\n", mySide.str()));
  991. const AISideBuildList *build = TheAI->getAiData()->m_sideBuildLists;
  992. while (build) {
  993. if (build->m_side == mySide) {
  994. BuildListInfo *buildList = build->m_buildList->duplicate();
  995. adjustBuildList(buildList); // adjust to our start position.
  996. m_player->setBuildList(buildList);
  997. computeCenterAndRadiusOfBase(&m_baseCenter, &m_baseRadius);
  998. break;
  999. }
  1000. build = build->m_next;
  1001. }
  1002. DEBUG_ASSERTLOG(build!=NULL, ("Couldn't find build list for skirmish player.\n"));
  1003. // Build any with the initially built flag.
  1004. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  1005. {
  1006. AsciiString name = info->getTemplateName();
  1007. if (name.isEmpty()) continue;
  1008. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
  1009. if (!bldgPlan) {
  1010. DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
  1011. continue;
  1012. }
  1013. if (info->isInitiallyBuilt()) {
  1014. buildStructureNow(bldgPlan, info);
  1015. } else {
  1016. info->incrementNumRebuilds(); // the initial build in the normal build list consumes a rebuild, so add one.
  1017. }
  1018. }
  1019. }
  1020. //----------------------------------------------------------------------------------------------------------
  1021. /**
  1022. * Queues up a dozer.
  1023. */
  1024. void AISkirmishPlayer::queueDozer( void )
  1025. {
  1026. AIPlayer::queueDozer();
  1027. }
  1028. //----------------------------------------------------------------------------------------------------------
  1029. /**
  1030. * Finds a dozer that isn't building or collecting resources.
  1031. */
  1032. Object * AISkirmishPlayer::findDozer( const Coord3D *pos )
  1033. {
  1034. return AIPlayer::findDozer(pos);
  1035. }
  1036. //----------------------------------------------------------------------------------------------------------
  1037. /**
  1038. * Find a good spot to fire a superweapon.
  1039. */
  1040. Bool AISkirmishPlayer::computeSuperweaponTarget(const SpecialPowerTemplate *power, Coord3D *retPos, Int playerNdx, Real weaponRadius)
  1041. {
  1042. Region2D bounds;
  1043. getPlayerStructureBounds(&bounds, playerNdx);
  1044. if( power->getSpecialPowerType() == SPECIAL_CLUSTER_MINES || power->getSpecialPowerType() == NUKE_SPECIAL_CLUSTER_MINES )
  1045. {
  1046. // hackus brutus - mine the entrances to our base.
  1047. AsciiString pathLabel;
  1048. Int mode = GameLogicRandomValue(0, 2);
  1049. if (mode==1) {
  1050. pathLabel.format("%s%d", SKIRMISH_FLANK, m_player->getMpStartIndex()+1);
  1051. } else if (mode==2) {
  1052. pathLabel.format("%s%d", SKIRMISH_BACKDOOR, m_player->getMpStartIndex()+1);
  1053. } else {
  1054. pathLabel.format("%s%d", SKIRMISH_CENTER, m_player->getMpStartIndex()+1);
  1055. }
  1056. Coord3D goalPos = m_baseCenter;
  1057. Waypoint *way = TheTerrainLogic->getClosestWaypointOnPath( &goalPos, pathLabel );
  1058. if (way) {
  1059. goalPos = *way->getLocation();
  1060. } else {
  1061. Region2D bounds;
  1062. getPlayerStructureBounds(&bounds, getMyEnemyPlayerIndex());
  1063. goalPos.x = bounds.lo.x + bounds.width()/2;
  1064. goalPos.y = bounds.lo.y + bounds.height()/2;
  1065. }
  1066. Coord2D offset;
  1067. offset.x = goalPos.x-m_baseCenter.x;
  1068. offset.y = goalPos.y-m_baseCenter.y;
  1069. offset.normalize();
  1070. offset.x *= m_baseRadius;
  1071. offset.y *= m_baseRadius;
  1072. *retPos = m_baseCenter;
  1073. retPos->x += offset.x;
  1074. retPos->y += offset.y;
  1075. retPos->z = TheTerrainLogic->getGroundHeight(retPos->x, retPos->y);
  1076. return TRUE;
  1077. }
  1078. return AIPlayer::computeSuperweaponTarget(power, retPos, playerNdx, weaponRadius);
  1079. }
  1080. // ------------------------------------------------------------------------------------------------
  1081. /** CRC */
  1082. // ------------------------------------------------------------------------------------------------
  1083. void AISkirmishPlayer::crc( Xfer *xfer )
  1084. {
  1085. } // end crc
  1086. // ------------------------------------------------------------------------------------------------
  1087. /** Xfer method
  1088. * Version Info;
  1089. * 1: Initial version */
  1090. // ------------------------------------------------------------------------------------------------
  1091. void AISkirmishPlayer::xfer( Xfer *xfer )
  1092. {
  1093. // version
  1094. XferVersion currentVersion = 1;
  1095. XferVersion version = currentVersion;
  1096. xfer->xferVersion( &version, currentVersion );
  1097. // xfer base class info
  1098. AIPlayer::xfer( xfer );
  1099. // front base defense
  1100. xfer->xferInt( &m_curFrontBaseDefense );
  1101. // flank base defense
  1102. xfer->xferInt( &m_curFlankBaseDefense );
  1103. // front left defense angle
  1104. xfer->xferReal( &m_curFrontLeftDefenseAngle );
  1105. // front right defense angle
  1106. xfer->xferReal( &m_curFrontRightDefenseAngle );
  1107. // left flank left defense angle
  1108. xfer->xferReal( &m_curLeftFlankLeftDefenseAngle );
  1109. // left flank right defense angle
  1110. xfer->xferReal( &m_curLeftFlankRightDefenseAngle );
  1111. // right flank left defense angle
  1112. xfer->xferReal( &m_curRightFlankLeftDefenseAngle );
  1113. // right flank right defense angle
  1114. xfer->xferReal( &m_curRightFlankRightDefenseAngle );
  1115. } // end xfer
  1116. // ------------------------------------------------------------------------------------------------
  1117. /** Load post process */
  1118. // ------------------------------------------------------------------------------------------------
  1119. void AISkirmishPlayer::loadPostProcess( void )
  1120. {
  1121. } // end loadPostProcess