AISkirmishPlayer.cpp 38 KB

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