AIPlayer.cpp 129 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855
  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. // AIPlayer.cpp ///////////////////////////////////////////////////////////////////////////////////
  24. // Computerized opponent
  25. // Author: Michael S. Booth, January 2002
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  29. #include "Common/GameMemory.h"
  30. #include "Common/GameState.h"
  31. #include "Common/GlobalData.h"
  32. #include "Common/PerfTimer.h"
  33. #include "Common/Player.h"
  34. #include "Common/SpecialPower.h"
  35. #include "Common/Team.h"
  36. #include "Common/ThingFactory.h"
  37. #include "Common/PlayerList.h"
  38. #include "Common/BuildAssistant.h"
  39. #include "Common/ThingTemplate.h"
  40. #include "Common/Upgrade.h"
  41. #include "Common/WellKnownKeys.h"
  42. #include "Common/Xfer.h"
  43. #include "GameClient/ControlBar.h"
  44. #include "GameClient/TerrainVisual.h"
  45. #include "GameLogic/GameLogic.h"
  46. #include "GameLogic/Object.h"
  47. #include "GameLogic/AIPlayer.h"
  48. #include "GameLogic/SidesList.h"
  49. #include "GameLogic/AI.h"
  50. #include "GameLogic/AIPathfind.h"
  51. #include "GameLogic/TerrainLogic.h"
  52. #include "GameLogic/Module/AIUpdate.h"
  53. #include "GameLogic/Module/DozerAIUpdate.h"
  54. #include "GameLogic/Module/UpdateModule.h"
  55. #include "GameLogic/ScriptEngine.h"
  56. #include "GameLogic/Module/ProductionUpdate.h"
  57. #include "GameLogic/Module/RebuildHoleBehavior.h"
  58. #include "GameLogic/Module/SupplyTruckAIUpdate.h"
  59. #include "GameLogic/Module/SupplyWarehouseDockUpdate.h"
  60. #include "GameLogic/PartitionManager.h"
  61. #ifdef _INTERNAL
  62. // for occasional debugging...
  63. //#pragma optimize("", off)
  64. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  65. #endif
  66. #define SUPPLY_CENTER_CLOSE_DIST (20*PATHFIND_CELL_SIZE_F)
  67. #define USE_DOZER 1
  68. // ------------------------------------------------------------------------------------------------
  69. // ------------------------------------------------------------------------------------------------
  70. AIPlayer::AIPlayer( Player *p ) :
  71. m_player(p),
  72. m_buildDelay(0),
  73. m_teamDelay(0),
  74. m_teamTimer(2), // Important - don't start building teams until frame 1.
  75. m_structureTimer(2), // Important - don't start building structures until frame 1.
  76. m_readyToBuildTeam(false),
  77. m_readyToBuildStructure(false),
  78. m_structuresInQueue(0),
  79. m_repairDozer(INVALID_ID),
  80. m_skillsetSelector(INVALID_SKILLSET_SELECTION),
  81. m_dozerQueuedForRepair(false),
  82. m_supplySourceAttackCheckFrame(0),
  83. m_attackedSupplyCenter(INVALID_ID),
  84. m_teamSeconds(10),
  85. m_curWarehouseID(INVALID_ID)
  86. {
  87. m_frameLastBuildingBuilt = TheGameLogic->getFrame();
  88. p->setCanBuildUnits(false); // turn off ai production by default.
  89. Int i;
  90. for (i=0; i<MAX_STRUCTURES_TO_REPAIR; i++) {
  91. m_structuresToRepair[i] = INVALID_ID;
  92. }
  93. m_repairDozerOrigin.zero();
  94. m_baseCenter.zero();
  95. m_baseCenterSet = false;
  96. m_difficulty = TheScriptEngine->getGlobalDifficulty();
  97. m_teamSeconds = TheAI->getAiData()->m_teamSeconds;
  98. }
  99. // ------------------------------------------------------------------------------------------------
  100. // ------------------------------------------------------------------------------------------------
  101. AIPlayer::~AIPlayer()
  102. {
  103. clearTeamsInQueue();
  104. }
  105. // ------------------------------------------------------------------------------------------------
  106. /** Invoked when a structure I am building is finished building. */
  107. // ------------------------------------------------------------------------------------------------
  108. void AIPlayer::onStructureProduced( Object *factory, Object *bldg )
  109. {
  110. m_teamDelay = 0; // Cause the update queues & selection to happen immediately.
  111. m_buildDelay = 0; // Cause
  112. /* Find the info building this. */
  113. BuildListInfo *info;
  114. for( info = m_player->getBuildList(); info; info = info->getNext() )
  115. {
  116. if (info->getObjectID() != bldg->getID()) continue;
  117. Dict d;
  118. d.setAsciiString(TheKey_objectName, info->getBuildingName());
  119. d.setAsciiString(TheKey_objectScriptAttachment, info->getScript());
  120. d.setInt(TheKey_objectInitialHealth, info->getHealth());
  121. d.setBool(TheKey_objectUnsellable, info->getUnsellable());
  122. info->setUnderConstruction(false);
  123. bldg->updateObjValuesFromMapProperties(&d);
  124. // clear the under construction status
  125. bldg->clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_UNDER_CONSTRUCTION, OBJECT_STATUS_RECONSTRUCTING ) );
  126. // UnderConstruction just cleared, so update our upgrades
  127. bldg->updateUpgradeModules();
  128. TheScriptEngine->addObjectToCache(bldg);
  129. TheScriptEngine->runObjectScript(info->getScript(), bldg);
  130. if (TheGlobalData->m_debugAI) {
  131. AsciiString bldgName = bldg->getTemplate()->getName();
  132. bldgName.concat(" - Building completed.");
  133. TheScriptEngine->AppendDebugMessage(bldgName, false);
  134. }
  135. checkForSupplyCenter(info, bldg);
  136. return;
  137. }
  138. // Look in build list & see if this is spawned from a hole.
  139. for( info = m_player->getBuildList(); info; info = info->getNext() )
  140. {
  141. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( info->getTemplateName() );
  142. if (!bldgPlan) {
  143. continue;
  144. }
  145. if (!bldgPlan->isEquivalentTo(bldg->getTemplate())) {
  146. continue; // not the same kind of building we're looking for.
  147. }
  148. // check for hole.
  149. if (info->getObjectID() != INVALID_ID) {
  150. // used to have a building.
  151. Object *obj = TheGameLogic->findObjectByID( info->getObjectID() );
  152. if (obj!=NULL) {
  153. if (obj->isKindOf(KINDOF_REBUILD_HOLE)) {
  154. RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( obj );
  155. if( rhbi ) {
  156. ObjectID spawnedID = rhbi->getReconstructedBuildingID();
  157. if (bldg->getID() == spawnedID) {
  158. DEBUG_LOG(("AI got rebuilt %s\n", bldgPlan->getName().str()));
  159. info->setObjectID(bldg->getID());
  160. return;
  161. }
  162. }
  163. }
  164. }
  165. }
  166. }
  167. if (TheGameLogic->getFrame()>0) {
  168. DEBUG_LOG(("***AI PLAYER-Structure not found in production queue.\n"));
  169. }
  170. }
  171. // ------------------------------------------------------------------------------------------------
  172. /** See if the building is a supply center, and see how many supply trucks we want. */
  173. // ------------------------------------------------------------------------------------------------
  174. void AIPlayer::checkForSupplyCenter( BuildListInfo *info, Object *bldg )
  175. {
  176. class SupplyCenterDockUpdate;
  177. // if it is a supply center, I must have boxes
  178. static const NameKeyType key_centerUpdate = NAMEKEY("SupplyCenterDockUpdate");
  179. SupplyCenterDockUpdate *centerModule = (SupplyCenterDockUpdate*)bldg->findUpdateModule( key_centerUpdate );
  180. if( centerModule )
  181. {
  182. info->setSupplyBuilding(true);
  183. Int desiredGatherers = 0;
  184. const AISideInfo *resInfo = TheAI->getAiData()->m_sideInfo;
  185. while (resInfo) {
  186. if (resInfo->m_side == m_player->getSide()) {
  187. GameDifficulty difficulty = m_difficulty;
  188. if (difficulty == DIFFICULTY_EASY) {
  189. desiredGatherers = resInfo->m_easy;
  190. }
  191. if (difficulty == DIFFICULTY_NORMAL) {
  192. desiredGatherers = resInfo->m_normal;
  193. }
  194. if (difficulty == DIFFICULTY_HARD) {
  195. desiredGatherers = resInfo->m_hard;
  196. }
  197. }
  198. resInfo = resInfo->m_next;
  199. }
  200. info->setSupplyBuilding(true);
  201. info->setCurrentGatherers(-1);
  202. info->setDesiredGatherers(desiredGatherers+1); // get a freebie with the supply depots.
  203. }
  204. }
  205. // ------------------------------------------------------------------------------------------------
  206. /** Queue up a supply truck to be built. */
  207. // ------------------------------------------------------------------------------------------------
  208. void AIPlayer::queueSupplyTruck( void )
  209. {
  210. Bool truckInQueue = false;
  211. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  212. {
  213. TeamInQueue *team = iter.cur();
  214. WorkOrder *order;
  215. for( order = team->m_workOrders; order; order = order->m_next )
  216. {
  217. // GLA dozers (workers) are also resource gatherers, so make sure it isn't a worker. jba.
  218. if (order->m_isResourceGatherer) {
  219. truckInQueue = true;
  220. }
  221. }
  222. }
  223. if (truckInQueue) {
  224. return; // already building a supply truck.
  225. }
  226. Int totalHarvesters = 0;
  227. // See how many harvesters we have servicing this supply src.
  228. // Scan my units.
  229. Player::PlayerTeamList::const_iterator it;
  230. for (it = m_player->getPlayerTeams()->begin(); it != m_player->getPlayerTeams()->end(); ++it) {
  231. for (DLINK_ITERATOR<Team> iter = (*it)->iterate_TeamInstanceList(); !iter.done(); iter.advance()) {
  232. Team *team = iter.cur();
  233. if (!team) {
  234. continue;
  235. }
  236. for (DLINK_ITERATOR<Object> objIter = team->iterate_TeamMemberList(); !objIter.done(); objIter.advance()) {
  237. Object *obj = objIter.cur();
  238. if (!obj) continue;
  239. if (!obj->isKindOf(KINDOF_HARVESTER)) continue;
  240. if (!obj->getAI()) continue;
  241. SupplyTruckAIInterface* supplyTruckAI = obj->getAI()->getSupplyTruckAIInterface();
  242. if( supplyTruckAI ) {
  243. totalHarvesters++;
  244. }
  245. }
  246. }
  247. }
  248. /* Find the info building this. */
  249. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  250. {
  251. if (info->isSupplyBuilding() == false) continue;
  252. Int desiredGatherers = info->getDesiredGatherers();
  253. Int curGatherers = info->getCurrentGatherers();
  254. if (curGatherers>=desiredGatherers) {
  255. // Check & see if any have died.
  256. Object *supplyCenter = TheGameLogic->findObjectByID(info->getObjectID());
  257. // Check for supplies.
  258. if (supplyCenter) {
  259. if (supplyCenter->isKindOf(KINDOF_REBUILD_HOLE)) {
  260. continue; // don't consider rebuild holes.
  261. }
  262. // Make sure we have a supplies near it.
  263. Coord3D center = *supplyCenter->getPosition();
  264. Real radius = SUPPLY_CENTER_CLOSE_DIST + supplyCenter->getGeometryInfo().getBoundingCircleRadius();
  265. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_SUPPLY_SOURCE), KINDOFMASK_NONE);
  266. PartitionFilterPlayer f2(m_player, false); // Only find other.
  267. PartitionFilterOnMap filterMapStatus;
  268. PartitionFilter *filters[] = { &f1, &f2, &filterMapStatus, 0 };
  269. Object *supplySource = ThePartitionManager->getClosestObject(&center, radius, FROM_BOUNDINGSPHERE_2D, filters);
  270. if (!supplySource) {
  271. // No supplies.
  272. continue;
  273. }
  274. static const NameKeyType key_warehouseUpdate = NAMEKEY("SupplyWarehouseDockUpdate");
  275. SupplyWarehouseDockUpdate *warehouseModule = (SupplyWarehouseDockUpdate*)supplySource->findUpdateModule( key_warehouseUpdate );
  276. if( warehouseModule ) {
  277. Int availableCash = warehouseModule->getBoxesStored()*TheGlobalData->m_baseValuePerSupplyBox;
  278. if (availableCash<=0) continue;
  279. if( m_player->getRelationship(supplySource->getTeam()) == ENEMIES ) {
  280. continue;
  281. }
  282. }
  283. // Ok, it has supplies available near it.
  284. checkForSupplyCenter(info, supplyCenter);
  285. Int curGatherers = 0;
  286. // See how many harvesters we have servicing this supply src.
  287. // Scan my units.
  288. Player::PlayerTeamList::const_iterator it;
  289. for (it = m_player->getPlayerTeams()->begin(); it != m_player->getPlayerTeams()->end(); ++it) {
  290. for (DLINK_ITERATOR<Team> iter = (*it)->iterate_TeamInstanceList(); !iter.done(); iter.advance()) {
  291. Team *team = iter.cur();
  292. if (!team) {
  293. continue;
  294. }
  295. for (DLINK_ITERATOR<Object> objIter = team->iterate_TeamMemberList(); !objIter.done(); objIter.advance()) {
  296. Object *obj = objIter.cur();
  297. if (!obj) continue;
  298. if (!obj->isKindOf(KINDOF_HARVESTER)) continue;
  299. if (!obj->getAI()) continue;
  300. SupplyTruckAIInterface* supplyTruckAI = obj->getAI()->getSupplyTruckAIInterface();
  301. if( supplyTruckAI ) {
  302. ObjectID dock = supplyTruckAI->getPreferredDockID();
  303. if (dock == supplyCenter->getID()) {
  304. curGatherers++;
  305. if (!supplyTruckAI->isCurrentlyFerryingSupplies()) {
  306. // Note - although this is the ai, we are sending in CMD_FROM_PLAYER.
  307. // This causes the dock object to stick in the docking interface.
  308. // The supply truck ai issues dock commands, and they become confused.
  309. // Thus, player. jba. ;(
  310. obj->getAI()->aiDock(supplyCenter, CMD_FROM_PLAYER);
  311. }
  312. }
  313. }
  314. }
  315. }
  316. }
  317. //DEBUG_LOG(("Expected %d harvesters, found %d, need %d\n", info->getDesiredGatherers(),
  318. // curGatherers, info->getDesiredGatherers()-curGatherers) );
  319. info->setCurrentGatherers(curGatherers);
  320. }
  321. } else {
  322. /* See if we have any "loose" harvesters (cause my supply center got nuked.) */
  323. Player::PlayerTeamList::const_iterator it;
  324. for (it = m_player->getPlayerTeams()->begin(); it != m_player->getPlayerTeams()->end(); ++it) {
  325. for (DLINK_ITERATOR<Team> iter = (*it)->iterate_TeamInstanceList(); !iter.done(); iter.advance()) {
  326. Team *team = iter.cur();
  327. if (!team) continue;
  328. for (DLINK_ITERATOR<Object> objIter = team->iterate_TeamMemberList(); !objIter.done(); objIter.advance()) {
  329. Object *obj = objIter.cur();
  330. if (!obj) continue;
  331. if (!obj->isKindOf(KINDOF_HARVESTER)) continue;
  332. if (!obj->getAI()) continue;
  333. SupplyTruckAIInterface* supplyTruckAI = obj->getAI()->getSupplyTruckAIInterface();
  334. if( supplyTruckAI ) {
  335. ObjectID dock = supplyTruckAI->getPreferredDockID();
  336. if (TheGameLogic->findObjectByID(dock)!=NULL) continue;
  337. if (supplyTruckAI->isCurrentlyFerryingSupplies() || supplyTruckAI->isForcedIntoWantingState())
  338. {
  339. // This thinks he is a gatherer, but doesn't have a preferred dock id.
  340. Object *center = TheGameLogic->findObjectByID(info->getObjectID());
  341. if (center) {
  342. info->setCurrentGatherers(info->getCurrentGatherers()+1);
  343. // Note - although this is the ai, we are sending in CMD_FROM_PLAYER.
  344. // This causes the dock object to stick in the docking interface.
  345. // The supply truck ai issues dock commands, and they become confused.
  346. // Thus, player. jba. ;(
  347. obj->getAI()->aiDock(center, CMD_FROM_PLAYER);
  348. DEBUG_LOG(("Re-attaching supply truck to supply center.\n"));
  349. return;
  350. }
  351. }
  352. }
  353. }
  354. }
  355. }
  356. if (totalHarvesters >= desiredGatherers*3) {
  357. continue; // we got lotsa gatherers.
  358. }
  359. Bool canBuildUnits = m_player->getCanBuildUnits();
  360. // If we need a supply truck thingy, turn on unit building for a moment.
  361. m_player->setCanBuildUnits(true);
  362. const ThingTemplate *tTemplate = TheThingFactory->firstTemplate();
  363. while (tTemplate) {
  364. Bool isSupplyTruck = tTemplate->isKindOf(KINDOF_HARVESTER);;
  365. if (isSupplyTruck) {
  366. Object *factory = findFactory(tTemplate, false);
  367. if (factory) {
  368. // we can build one.
  369. WorkOrder *order = newInstance(WorkOrder);
  370. order->m_thing = tTemplate;
  371. order->m_factoryID = INVALID_ID;
  372. order->m_numRequired = 1;
  373. order->m_required = true;
  374. order->m_isResourceGatherer =true;
  375. // prepend to head of list
  376. order->m_next = NULL;
  377. TeamInQueue *team = newInstance(TeamInQueue);
  378. // Put in front of queue.
  379. prependTo_TeamBuildQueue(team);
  380. team->m_priorityBuild = true;
  381. team->m_workOrders = order;
  382. team->m_frameStarted = TheGameLogic->getFrame();
  383. // Stick it on the default team
  384. team->m_team = m_player->getDefaultTeam();
  385. AsciiString teamName = "Supply truck - building one at the ";
  386. teamName.concat(factory->getTemplate()->getName());
  387. TheScriptEngine->AppendDebugMessage(teamName, false);
  388. m_teamDelay = 0;
  389. if (info->getCurrentGatherers()==-1) {
  390. // First one is automatic. jba.
  391. order->m_factoryID = factory->getID();
  392. info->setCurrentGatherers(0);
  393. } else {
  394. startTraining( order, team->m_priorityBuild, team->m_team->getName());
  395. }
  396. break;
  397. }
  398. }
  399. tTemplate = tTemplate->friend_getNextTemplate();
  400. }
  401. m_player->setCanBuildUnits(canBuildUnits);
  402. }
  403. }
  404. }
  405. // ------------------------------------------------------------------------------------------------
  406. // ------------------------------------------------------------------------------------------------
  407. static void deleteQueue(TeamInQueue* o)
  408. {
  409. if (o)
  410. {
  411. o->deleteInstance();
  412. }
  413. }
  414. // ------------------------------------------------------------------------------------------------
  415. /** Clear the current work order */
  416. // ------------------------------------------------------------------------------------------------
  417. void AIPlayer::clearTeamsInQueue( void )
  418. {
  419. removeAll_TeamBuildQueue(deleteQueue);
  420. removeAll_TeamReadyQueue(deleteQueue);
  421. }
  422. // ------------------------------------------------------------------------------------------------
  423. // ------------------------------------------------------------------------------------------------
  424. Object *AIPlayer::buildStructureNow(const ThingTemplate *bldgPlan, BuildListInfo *info)
  425. {
  426. // inst-construct the building
  427. Object *bldg = TheBuildAssistant->buildObjectNow( NULL,
  428. bldgPlan,
  429. info->getLocation(),
  430. info->getAngle(),
  431. m_player );
  432. // store the object with the build order
  433. if (bldg)
  434. {
  435. Dict d;
  436. d.setAsciiString(TheKey_objectName, info->getBuildingName());
  437. d.setAsciiString(TheKey_objectScriptAttachment, info->getScript());
  438. d.setInt(TheKey_objectInitialHealth, info->getHealth());
  439. d.setBool(TheKey_objectUnsellable, info->getUnsellable());
  440. bldg->updateObjValuesFromMapProperties(&d);
  441. info->setObjectID( bldg->getID() );
  442. info->setObjectTimestamp( TheGameLogic->getFrame()+1 ); // has to be non-zero, so just add 1.
  443. // clear the under construction status
  444. bldg->clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_UNDER_CONSTRUCTION, OBJECT_STATUS_RECONSTRUCTING ) );
  445. // UnderConstruction just cleared, so update our upgrades
  446. bldg->updateUpgradeModules();
  447. if (TheGlobalData->m_debugAI) {
  448. AsciiString bldgName = bldgPlan->getName();
  449. bldgName.concat(" - Building completed.");
  450. TheScriptEngine->AppendDebugMessage(bldgName, false);
  451. }
  452. TheScriptEngine->addObjectToCache(bldg);
  453. TheScriptEngine->runObjectScript(info->getScript(), bldg);
  454. checkForSupplyCenter(info, bldg);
  455. ExitInterface *exitInterface = bldg->getObjectExitInterface();
  456. if( exitInterface )
  457. {
  458. Coord3D rallyPoint;
  459. Bool gotOffset = false;
  460. if (fabs(info->getRallyOffset()->x) > 1.0f || fabs(info->getRallyOffset()->y)>1.0f) {
  461. gotOffset;
  462. }
  463. if (!exitInterface->getNaturalRallyPoint(rallyPoint)) {
  464. rallyPoint = *info->getLocation();
  465. }
  466. if (gotOffset) {
  467. rallyPoint.x += info->getRallyOffset()->x;
  468. rallyPoint.y += info->getRallyOffset()->y;
  469. exitInterface->setRallyPoint(&rallyPoint);
  470. }
  471. }
  472. } // bldg built
  473. return bldg;
  474. }
  475. // ------------------------------------------------------------------------------------------------
  476. // ------------------------------------------------------------------------------------------------
  477. Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildListInfo *info)
  478. {
  479. // Find a dozer.
  480. Object *dozer = findDozer(info->getLocation());
  481. if (dozer==NULL) {
  482. return NULL;
  483. }
  484. // Check available funds.
  485. Money *money = m_player->getMoney();
  486. if (money->countMoney()<bldgPlan->calcCostToBuild(m_player)) {
  487. return NULL;
  488. }
  489. // construct the building
  490. Coord3D pos = *info->getLocation();
  491. pos.z += TheTerrainLogic->getGroundHeight(pos.x, pos.y);
  492. if( !dozer->getAIUpdateInterface() )
  493. {
  494. return NULL;
  495. }
  496. Real angle = info->getAngle();
  497. if( TheBuildAssistant->isLocationLegalToBuild( &pos, bldgPlan, angle,
  498. BuildAssistant::NO_ENEMY_OBJECT_OVERLAP,
  499. dozer, m_player ) != LBC_OK ) {
  500. // If there's enemy units or structures, don't build/rebuild.
  501. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  502. return NULL;
  503. }
  504. // validate the the position to build at is valid
  505. if( TheBuildAssistant->isLocationLegalToBuild( &pos, bldgPlan, angle,
  506. BuildAssistant::CLEAR_PATH |
  507. BuildAssistant::TERRAIN_RESTRICTIONS |
  508. BuildAssistant::NO_OBJECT_OVERLAP,
  509. dozer, m_player ) != LBC_OK ) {
  510. // Warn.
  511. AsciiString bldgName = bldgPlan->getName();
  512. bldgName.concat(" - Dozer unable to place. Attempting to adjust position.");
  513. TheScriptEngine->AppendDebugMessage(bldgName, false);
  514. // try to fix.
  515. Real posOffset;
  516. Bool valid = false;
  517. // Wiggle it a little :)
  518. Real limit = 10*PATHFIND_CELL_SIZE_F;
  519. if (isSkirmishAI()) {
  520. limit = 120*PATHFIND_CELL_SIZE_F;
  521. }
  522. Coord3D newPos = pos;
  523. for (posOffset = 0; posOffset<limit; posOffset += 2*PATHFIND_CELL_SIZE_F) {
  524. if (isSkirmishAI()) {
  525. posOffset += 2*PATHFIND_CELL_SIZE_F;
  526. }
  527. Real offset = posOffset/2;
  528. Real xPos, yPos;
  529. yPos = pos.y-offset;
  530. for (xPos = pos.x-offset; xPos <= pos.x+offset; xPos+=PATHFIND_CELL_SIZE_F) {
  531. if (isSkirmishAI()) xPos += PATHFIND_CELL_SIZE_F;
  532. newPos.x = xPos;
  533. newPos.y = yPos;
  534. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, bldgPlan, angle,
  535. BuildAssistant::CLEAR_PATH |
  536. BuildAssistant::TERRAIN_RESTRICTIONS |
  537. BuildAssistant::NO_OBJECT_OVERLAP,
  538. dozer, m_player ) == LBC_OK;
  539. if (valid) break;
  540. newPos.y = yPos+posOffset;
  541. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, bldgPlan, angle,
  542. BuildAssistant::CLEAR_PATH |
  543. BuildAssistant::TERRAIN_RESTRICTIONS |
  544. BuildAssistant::NO_OBJECT_OVERLAP,
  545. dozer, m_player ) == LBC_OK;
  546. }
  547. if (valid) break;
  548. xPos = pos.x-offset;
  549. for (yPos = pos.y-offset; yPos <= pos.y+offset; yPos+=PATHFIND_CELL_SIZE_F) {
  550. if (isSkirmishAI()) yPos += PATHFIND_CELL_SIZE_F;
  551. newPos.x = xPos;
  552. newPos.y = yPos;
  553. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, bldgPlan, angle,
  554. BuildAssistant::CLEAR_PATH |
  555. BuildAssistant::TERRAIN_RESTRICTIONS |
  556. BuildAssistant::NO_OBJECT_OVERLAP,
  557. dozer, m_player ) == LBC_OK;
  558. if (valid) break;
  559. newPos.x = xPos+posOffset;
  560. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, bldgPlan, angle,
  561. BuildAssistant::CLEAR_PATH |
  562. BuildAssistant::TERRAIN_RESTRICTIONS |
  563. BuildAssistant::NO_OBJECT_OVERLAP,
  564. dozer, m_player ) == LBC_OK;
  565. }
  566. if (valid) break;
  567. }
  568. if (valid) pos = newPos;
  569. if (!valid) {
  570. valid = TheBuildAssistant->isLocationLegalToBuild( &pos, bldgPlan, angle,
  571. BuildAssistant::NO_ENEMY_OBJECT_OVERLAP,
  572. dozer, m_player ) == LBC_OK;
  573. if (!valid) {
  574. return NULL;
  575. }
  576. }
  577. }
  578. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  579. if (!TheAI->pathfinder()->clientSafeQuickDoesPathExist(dozer->getAI()->getLocomotorSet(),
  580. dozer->getPosition(), &pos)) {
  581. AsciiString bldgName = bldgPlan->getName();
  582. bldgName.concat(" - Dozer unable to reach building. Teleporting.");
  583. TheScriptEngine->AppendDebugMessage(bldgName, false);
  584. dozer->setPosition(&pos);
  585. }
  586. Object *bldg = TheBuildAssistant->buildObjectNow( dozer,
  587. bldgPlan,
  588. &pos,
  589. angle,
  590. m_player );
  591. #if defined _DEBUG || defined _INTERNAL
  592. if (TheGlobalData->m_debugAI == AI_DEBUG_PATHS)
  593. {
  594. extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color);
  595. RGBColor color;
  596. color.blue = 0;
  597. color.red = 1;
  598. color.green = 0;
  599. Coord3D myPos;
  600. myPos = *dozer->getPosition();
  601. myPos.z = TheTerrainLogic->getGroundHeight( myPos.x, myPos.y ) + 0.5f;
  602. addIcon(&myPos, 2*PATHFIND_CELL_SIZE_F, 120, color);
  603. myPos = pos;
  604. myPos.z = TheTerrainLogic->getGroundHeight( myPos.x, myPos.y ) + 0.5f;
  605. addIcon(&myPos, 2*PATHFIND_CELL_SIZE_F, 120, color);
  606. Real dx, dy;
  607. dx = dozer->getPosition()->x - pos.x;
  608. dy = dozer->getPosition()->y - pos.y;
  609. Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2);
  610. if (count<2) count = 2;
  611. Int i;
  612. color.green = 1;
  613. for (i=1; i<count; i++) {
  614. myPos.x = dozer->getPosition()->x + (pos.x-dozer->getPosition()->x)*i/count;
  615. myPos.y = dozer->getPosition()->y + (pos.y-dozer->getPosition()->y)*i/count;
  616. myPos.z = TheTerrainLogic->getGroundHeight( myPos.x, myPos.y ) + 0.5f;
  617. addIcon(&myPos, PATHFIND_CELL_SIZE_F/2, 120, color);
  618. }
  619. }
  620. #endif
  621. // store the object with the build order
  622. if (bldg)
  623. {
  624. ExitInterface *exitInterface = bldg->getObjectExitInterface();
  625. if( exitInterface )
  626. {
  627. Coord3D rallyPoint;
  628. Bool gotOffset = false;
  629. if (fabs(info->getRallyOffset()->x) > 1.0f || fabs(info->getRallyOffset()->y)>1.0f) {
  630. gotOffset;
  631. }
  632. if (!exitInterface->getNaturalRallyPoint(rallyPoint)) {
  633. rallyPoint = *info->getLocation();
  634. }
  635. if (gotOffset) {
  636. rallyPoint.x += info->getRallyOffset()->x;
  637. rallyPoint.y += info->getRallyOffset()->y;
  638. exitInterface->setRallyPoint(&rallyPoint);
  639. }
  640. }
  641. info->setObjectID( bldg->getID() );
  642. info->setObjectTimestamp( TheGameLogic->getFrame()+1 ); // Has to be non-zero, so add 1.
  643. info->setUnderConstruction(true);
  644. if (TheGlobalData->m_debugAI) {
  645. AsciiString bldgName = bldgPlan->getName();
  646. bldgName.concat(" - Building started.");
  647. TheScriptEngine->AppendDebugMessage(bldgName, false);
  648. }
  649. } // bldg built
  650. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  651. return bldg;
  652. }
  653. // ------------------------------------------------------------------------------------------------
  654. /** Build our base. */
  655. // ------------------------------------------------------------------------------------------------
  656. void AIPlayer::processBaseBuilding( void )
  657. {
  658. //
  659. // Refresh base buildings. Scan through list, if a building is missing,
  660. // rebuild it, unless it's rebuild count is zero.
  661. //
  662. if (m_readyToBuildStructure)
  663. {
  664. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  665. {
  666. AsciiString name = info->getTemplateName();
  667. if (name.isEmpty()) continue;
  668. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
  669. if (!bldgPlan) {
  670. DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
  671. continue;
  672. }
  673. // check for hole.
  674. if (info->getObjectID() != INVALID_ID) {
  675. // used to have a building.
  676. Object *bldg = TheGameLogic->findObjectByID( info->getObjectID() );
  677. if (bldg==NULL) {
  678. // got destroyed.
  679. ObjectID priorID;
  680. priorID = info->getObjectID();
  681. info->setObjectID(INVALID_ID);
  682. info->setObjectTimestamp(TheGameLogic->getFrame()+1);
  683. // Scan for a GLA hole. KINDOF_REBUILD_HOLE
  684. Object *obj;
  685. for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) {
  686. if (!obj->isKindOf(KINDOF_REBUILD_HOLE)) continue;
  687. RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( obj );
  688. if( rhbi ) {
  689. ObjectID spawnerID = rhbi->getSpawnerID();
  690. if (priorID == spawnerID) {
  691. DEBUG_LOG(("AI Found hole to rebuild %s\n", bldgPlan->getName().str()));
  692. info->setObjectID(obj->getID());
  693. }
  694. }
  695. }
  696. } else {
  697. if (bldg->getControllingPlayer() == m_player) {
  698. // Check for built or dozer missing.
  699. if( bldg->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
  700. {
  701. // make sure dozer is working on him.
  702. ObjectID builder = bldg->getBuilderID();
  703. Object* myDozer = TheGameLogic->findObjectByID(builder);
  704. if (myDozer==NULL) {
  705. DEBUG_LOG(("AI's Dozer got killed. Find another dozer.\n"));
  706. myDozer = findDozer(bldg->getPosition());
  707. if (myDozer==NULL || myDozer->getAI()==NULL) {
  708. continue;
  709. }
  710. myDozer->getAI()->aiResumeConstruction(bldg, CMD_FROM_AI);
  711. } else {
  712. // make sure he is building.
  713. myDozer->getAI()->aiResumeConstruction(bldg, CMD_FROM_AI);
  714. }
  715. }
  716. } else {
  717. // oops, got captured.
  718. info->setObjectID(INVALID_ID);
  719. info->setObjectTimestamp(TheGameLogic->getFrame()+1);
  720. }
  721. }
  722. }
  723. if (info->getObjectID()==INVALID_ID && info->getObjectTimestamp()>0) {
  724. // this object was built at some time, and got destroyed at or near objectTimestamp.
  725. // Wait a few seconds before initiating a rebuild.
  726. if (info->getObjectTimestamp()+TheAI->getAiData()->m_rebuildDelaySeconds*LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) {
  727. continue;
  728. } else {
  729. DEBUG_LOG(("Enabling rebuild for %s\n", info->getTemplateName().str()));
  730. info->setObjectTimestamp(0); // ready to build.
  731. }
  732. }
  733. // check if this building has any "rebuilds" left
  734. if (info->isBuildable())
  735. {
  736. Object *bldg = TheGameLogic->findObjectByID( info->getObjectID() );
  737. if (bldg == NULL)
  738. {
  739. #ifdef USE_DOZER
  740. // dozer-construct the building
  741. bldg = buildStructureWithDozer(bldgPlan, info);
  742. // store the object with the build order
  743. if (bldg)
  744. {
  745. info->setObjectID( bldg->getID() );
  746. info->decrementNumRebuilds();
  747. m_readyToBuildStructure = false;
  748. m_structureTimer = TheAI->getAiData()->m_structureSeconds*LOGICFRAMES_PER_SECOND;
  749. if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
  750. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresPoorMod;
  751. } else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
  752. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresWealthyMod;
  753. }
  754. m_frameLastBuildingBuilt = TheGameLogic->getFrame();
  755. // only build one building per delay loop
  756. break;
  757. } // bldg built
  758. #else
  759. // force delay between rebuilds
  760. if (TheGameLogic->getFrame() - m_frameLastBuildingBuilt < framesToBuild)
  761. {
  762. m_buildDelay = framesToBuild - (TheGameLogic->getFrame() - m_frameLastBuildingBuilt);
  763. return;
  764. } else {
  765. // building is missing, (re)build it
  766. // deduct money to build, if we have it
  767. Int cost = bldgPlan->calcCostToBuild( m_player );
  768. if (m_player->getMoney()->countMoney() >= cost)
  769. {
  770. // we have the money, deduct it
  771. m_player->getMoney()->withdraw( cost );
  772. // inst-construct the building
  773. bldg = buildStructureNow(bldgPlan, info, NULL);
  774. // store the object with the build order
  775. if (bldg)
  776. {
  777. info->setObjectID( bldg->getID() );
  778. info->decrementNumRebuilds();
  779. m_readyToBuildStructure = false;
  780. m_structureTimer = TheAI->getAiData()->m_structureSeconds*LOGICFRAMES_PER_SECOND;
  781. if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
  782. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresPoorMod;
  783. } else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
  784. m_structureTimer = m_structureTimer/TheAI->getAiData()->m_structuresWealthyMod;
  785. }
  786. m_frameLastBuildingBuilt = TheGameLogic->getFrame();
  787. // only build one building per delay loop
  788. break;
  789. } // bldg built
  790. } // have money
  791. } // rebuild delay ok
  792. #endif
  793. } // building missing
  794. } // is buildable
  795. }
  796. }
  797. }
  798. //-------------------------------------------------------------------------------------------------
  799. /** A team is about to be destroyed */
  800. //-------------------------------------------------------------------------------------------------
  801. void AIPlayer::aiPreTeamDestroy( const Team *deletedTeam )
  802. {
  803. {
  804. for (DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  805. {
  806. TeamInQueue *team = iter.cur();
  807. if (team->m_team == deletedTeam) {
  808. // The members of the team all got killed before we could finish building the team.
  809. removeFrom_TeamBuildQueue(team);
  810. team->deleteInstance();
  811. iter = iterate_TeamBuildQueue();
  812. }
  813. }
  814. }
  815. {
  816. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamReadyQueue(); !iter.done(); iter.advance())
  817. {
  818. TeamInQueue *team = iter.cur();
  819. if (team->m_team == deletedTeam) {
  820. // The members of the team all got killed before we could activate the team.
  821. removeFrom_TeamReadyQueue(team);
  822. team->deleteInstance();
  823. iter = iterate_TeamReadyQueue();
  824. }
  825. }
  826. }
  827. }
  828. //-------------------------------------------------------------------------------------------------
  829. /** Guard supply center */
  830. //-------------------------------------------------------------------------------------------------
  831. void AIPlayer::guardSupplyCenter( Team *team, Int minSupplies )
  832. {
  833. m_supplySourceAttackCheckFrame = 0; // force check.
  834. Object *warehouse = NULL;
  835. if (isSupplySourceAttacked()) {
  836. warehouse = TheGameLogic->findObjectByID(m_attackedSupplyCenter);
  837. }
  838. if (warehouse==NULL) {
  839. warehouse = findSupplyCenter(minSupplies);
  840. }
  841. if (warehouse) {
  842. AIGroup* theGroup = TheAI->createGroup();
  843. if (!theGroup) {
  844. return;
  845. }
  846. team->getTeamAsAIGroup(theGroup);
  847. Coord3D location = *warehouse->getPosition();
  848. // It's probably a defensive move - position towards the enemy.
  849. Region2D bounds;
  850. Int enemyNdx = TheScriptEngine->getSkirmishEnemyPlayer()->getPlayerIndex();
  851. getPlayerStructureBounds(&bounds, enemyNdx);
  852. Coord3D offset;
  853. offset.zero();
  854. offset.x = location.x - (bounds.lo.x+bounds.hi.x)*0.5f;
  855. offset.y = location.y - (bounds.lo.y+bounds.hi.y)*0.5f;
  856. offset.normalize();
  857. Real radius = warehouse->getGeometryInfo().getBoundingCircleRadius()*0.8f;
  858. location.x -= offset.x*radius;
  859. location.y -= offset.y*radius;
  860. theGroup->groupGuardPosition( &location, GUARDMODE_NORMAL, CMD_FROM_SCRIPT );
  861. }
  862. }
  863. //-------------------------------------------------------------------------------------------------
  864. /** Is a supply source attacked? */
  865. //-------------------------------------------------------------------------------------------------
  866. Bool AIPlayer::isSupplySourceAttacked( void )
  867. {
  868. const Int SCAN_RATE = 10; // don't scan more often than every 10 seconds.
  869. UnsignedInt curFrame = TheGameLogic->getFrame();
  870. if (curFrame==0) {
  871. m_supplySourceAttackCheckFrame = curFrame+SCAN_RATE;
  872. return false; // can't be attacked on first frame.
  873. }
  874. m_attackedSupplyCenter = INVALID_ID;
  875. if (curFrame < m_supplySourceAttackCheckFrame) {
  876. return false;
  877. }
  878. if (m_player->getAttackedFrame()+SCAN_RATE < curFrame) {
  879. return false; // haven't been attacked recently.
  880. }
  881. m_supplySourceAttackCheckFrame = curFrame+SCAN_RATE;
  882. // Scan my units.
  883. Player::PlayerTeamList::const_iterator it;
  884. for (it = m_player->getPlayerTeams()->begin(); it != m_player->getPlayerTeams()->end(); ++it) {
  885. for (DLINK_ITERATOR<Team> iter = (*it)->iterate_TeamInstanceList(); !iter.done(); iter.advance()) {
  886. Team *team = iter.cur();
  887. if (!team) {
  888. continue;
  889. }
  890. for (DLINK_ITERATOR<Object> objIter = team->iterate_TeamMemberList(); !objIter.done(); objIter.advance()) {
  891. Object *obj = objIter.cur();
  892. if (!obj) {
  893. continue;
  894. }
  895. if (!obj->isKindOf(KINDOF_CASH_GENERATOR) && !obj->isKindOf(KINDOF_DOZER) &&
  896. !obj->isKindOf(KINDOF_HARVESTER)) {
  897. continue;
  898. }
  899. // check for attacked.
  900. BodyModuleInterface *body = obj->getBodyModule();
  901. if (body) {
  902. const DamageInfo *info = body->getLastDamageInfo();
  903. if (info) {
  904. if (info->out.m_noEffect) {
  905. continue;
  906. }
  907. if (body->getLastDamageTimestamp() + SCAN_RATE > curFrame) {
  908. // winner.
  909. m_attackedSupplyCenter = obj->getID();
  910. return true;
  911. }
  912. }
  913. }
  914. }
  915. }
  916. }
  917. return false;
  918. }
  919. //-------------------------------------------------------------------------------------------------
  920. /** Is the nearest supply source safe? */
  921. //-------------------------------------------------------------------------------------------------
  922. Bool AIPlayer::isSupplySourceSafe( Int minSupplies )
  923. {
  924. Object *warehouse = findSupplyCenter(minSupplies);
  925. if (warehouse==NULL) return true; // it's safe cause it doesn't exist.
  926. return (isLocationSafe(warehouse->getPosition(), warehouse->getTemplate()));
  927. }
  928. //-------------------------------------------------------------------------------------------------
  929. /** Is this location safe for building this thing? */
  930. //-------------------------------------------------------------------------------------------------
  931. Bool AIPlayer::isLocationSafe(const Coord3D *pos, const ThingTemplate *tthing )
  932. {
  933. if (tthing == NULL) return 0;
  934. // See if we have enemies.
  935. Real radius = TheAI->getAiData()->m_supplyCenterSafeRadius;
  936. radius += tthing->getTemplateGeometryInfo().getBoundingCircleRadius();
  937. // only consider enemies.
  938. PartitionFilterPlayerAffiliation filterTeam(m_player, (ALLOW_ALLIES|ALLOW_NEUTRAL), false);
  939. // and only stuff that is not dead
  940. PartitionFilterAlive filterAlive;
  941. // and only stuff that isn't stealthed (and not detected)
  942. // (note that stealthed allies aren't hidden from us, but we're only looking for enemies here)
  943. PartitionFilterRejectByObjectStatus filterStealth( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_STEALTHED ),
  944. MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_DETECTED, OBJECT_STATUS_DISGUISED ) );
  945. // (optional) only stuff that is significant
  946. PartitionFilterInsignificantBuildings filterInsignificant(true, false);
  947. PartitionFilterRejectByKindOf filterHarvesters(MAKE_KINDOF_MASK(KINDOF_HARVESTER), KINDOFMASK_NONE);
  948. PartitionFilterRejectByKindOf filterDozer(MAKE_KINDOF_MASK(KINDOF_DOZER), KINDOFMASK_NONE);
  949. PartitionFilter *filters[16];
  950. Int numFilters = 0;
  951. filters[numFilters++] = &filterTeam;
  952. filters[numFilters++] = &filterAlive;
  953. filters[numFilters++] = &filterStealth;
  954. filters[numFilters++] = &filterInsignificant;
  955. filters[numFilters++] = &filterHarvesters;
  956. filters[numFilters++] = &filterDozer;
  957. filters[numFilters] = NULL;
  958. Object *enemy = ThePartitionManager->getClosestObject( pos, radius, FROM_BOUNDINGSPHERE_2D, filters );
  959. if (enemy!=NULL) {
  960. return false;
  961. }
  962. return true;
  963. } // isSupplySourceSafe
  964. // ------------------------------------------------------------------------------------------------
  965. /** Invoked when a unit I am training comes into existence */
  966. // ------------------------------------------------------------------------------------------------
  967. void AIPlayer::onUnitProduced( Object *factory, Object *unit )
  968. {
  969. Bool found = false;
  970. Bool supplyTruck;
  971. // factory could be NULL at the start of the game.
  972. if (factory == NULL) {
  973. return;
  974. }
  975. for (DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  976. {
  977. TeamInQueue *team = iter.cur();
  978. // find work order entry and delete it
  979. WorkOrder *order;
  980. if (found) break;
  981. for( order = team->m_workOrders; order; order = order->m_next )
  982. {
  983. if (order->m_factoryID == factory->getID() && order->m_numCompleted < order->m_numRequired && unit->getTemplate()->isEquivalentTo(order->m_thing))
  984. {
  985. // found associated order, mark it complete.
  986. order->m_numCompleted++;
  987. // put new unit into the team under construction
  988. if (team->m_team)
  989. unit->setTeam( team->m_team );
  990. if (team->m_reinforcement) {
  991. team->m_reinforcementID = unit->getID();
  992. }
  993. AIUpdateInterface *ai = unit->getAIUpdateInterface();
  994. if (team->m_team->getPrototype()->getTemplateInfo()->m_hasHomeLocation) {
  995. if (ai) {
  996. std::vector<Coord3D> path;
  997. path.push_back( *ai->getGoalPosition() );
  998. path.push_back(team->m_team->getPrototype()->getTemplateInfo()->m_homeLocation);
  999. ai->aiFollowExitProductionPath(&path, NULL, CMD_FROM_AI);
  1000. }
  1001. }
  1002. order->m_factoryID = INVALID_ID; // no longer using this factory.
  1003. if (ai) {
  1004. // tell it to start gathering resources.
  1005. // Here is the special bit for this exit style, force wanting on SupplyTruck types
  1006. SupplyTruckAIInterface* supplyTruckAI = ai->getSupplyTruckAIInterface();
  1007. if( supplyTruckAI ) {
  1008. if (order->m_isResourceGatherer) {
  1009. supplyTruck = true;
  1010. } else {
  1011. supplyTruck = false;
  1012. }
  1013. supplyTruckAI->setForceWantingState(supplyTruck);
  1014. if (supplyTruck) {
  1015. // assign to a supply depot.
  1016. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  1017. {
  1018. if (info->isSupplyBuilding() && info->getDesiredGatherers()>0 &&
  1019. info->getDesiredGatherers()>info->getCurrentGatherers()) {
  1020. Object *obj = TheGameLogic->findObjectByID(info->getObjectID());
  1021. if (obj) {
  1022. info->setCurrentGatherers(info->getCurrentGatherers()+1);
  1023. // Note - although this is the ai, we are sending in CMD_FROM_PLAYER.
  1024. // This causes the dock object to stick in the docking interface.
  1025. // The supply truck ai issues dock commands, and they become confused.
  1026. // Thus, player. jba. ;(
  1027. ai->aiDock(obj, CMD_FROM_PLAYER);
  1028. }
  1029. }
  1030. }
  1031. }
  1032. }
  1033. }
  1034. found = true;
  1035. break;
  1036. }
  1037. }
  1038. }
  1039. if (!supplyTruck && unit->isKindOf(KINDOF_DOZER)) {
  1040. if (m_dozerQueuedForRepair) {
  1041. m_repairDozer = unit->getID();
  1042. m_dozerQueuedForRepair =false;
  1043. } else {
  1044. m_buildDelay = 0;
  1045. m_structureTimer = 1;
  1046. }
  1047. }
  1048. if (!found) {
  1049. DEBUG_LOG(("***AI PLAYER-Unit not found in production queue.\n"));
  1050. }
  1051. m_teamDelay = 0; // Cause the update queues & selection to happen immediately.
  1052. }
  1053. //----------------------------------------------------------------------------------------------------------
  1054. /**
  1055. * Find a good spot to fire a superweapon.
  1056. */
  1057. Bool AIPlayer::computeSuperweaponTarget(const SpecialPowerTemplate *power, Coord3D *retPos, Int playerNdx, Real weaponRadius)
  1058. {
  1059. Bool success = FALSE;
  1060. Region2D bounds;
  1061. getPlayerStructureBounds(&bounds, playerNdx);
  1062. if( bounds.hi.x == 0
  1063. && bounds.lo.x == 0
  1064. && bounds.hi.y == 0
  1065. && bounds.lo.y == 0
  1066. )
  1067. {
  1068. Region3D bounds3D;
  1069. // Degenerate bounds because he has no buildings. Don't give up, scan the whole map for the leftovers.
  1070. TheTerrainLogic->getExtent(&bounds3D);
  1071. bounds.hi.x = bounds3D.hi.x;
  1072. bounds.lo.x = bounds3D.lo.x;
  1073. bounds.hi.y = bounds3D.hi.y;
  1074. bounds.lo.y = bounds3D.lo.y;
  1075. }
  1076. if (weaponRadius<1.0f) {
  1077. weaponRadius = 1.0f; // sanity to avoid divide by 0.
  1078. }
  1079. Int xCount, yCount;
  1080. bounds.lo.x += weaponRadius;
  1081. bounds.hi.x -= weaponRadius;
  1082. if (bounds.hi.x<bounds.lo.x) {
  1083. bounds.hi.x = bounds.lo.x = (bounds.hi.x+bounds.lo.x)/2.0f;
  1084. }
  1085. if (bounds.hi.y<bounds.lo.y) {
  1086. bounds.hi.y = bounds.lo.y = (bounds.hi.y+bounds.lo.y)/2.0f;
  1087. }
  1088. xCount = REAL_TO_INT_CEIL(bounds.width()/weaponRadius)+1;
  1089. yCount = REAL_TO_INT_CEIL(bounds.height()/weaponRadius)+1;
  1090. if (xCount>10) xCount = 10;
  1091. if (yCount>10) yCount = 10;
  1092. Int cash = -1;
  1093. Coord3D pos;
  1094. Coord3D bestPos;
  1095. Int x, y, xDelta, yDelta, xIndex, yIndex, xStart, yStart;
  1096. Bool targetMilitaryUnits = TRUE;
  1097. if( power->getSpecialPowerType() == SPECIAL_SNEAK_ATTACK )
  1098. {
  1099. //Military units will have a negative effect on where to drop the special power.
  1100. //We want to target rich areas that are poorly defended or undefended.
  1101. targetMilitaryUnits = FALSE;
  1102. }
  1103. //Randomize which way we iterate the grid. We don't always want to start in the bottom left corner incase
  1104. //of a bad calculation, it'll would always end up there.
  1105. switch( GameLogicRandomValue( 1, 4 ) )
  1106. {
  1107. case 1:
  1108. //x min to max, y min to max
  1109. xDelta = 1, yDelta = 1;
  1110. xStart = 0, yStart = 0;
  1111. break;
  1112. case 2:
  1113. //x max to min, y min to max
  1114. xDelta = -1, yDelta = 1;
  1115. xStart = xCount, yStart = 0;
  1116. break;
  1117. case 3:
  1118. //x min to max, y max to min
  1119. xDelta = 1, yDelta = -1;
  1120. xStart = 0, yStart = yCount;
  1121. break;
  1122. case 4:
  1123. default:
  1124. //x max to min, y max to min
  1125. xDelta = -1, yDelta = -1;
  1126. xStart = xCount, yStart = yCount;
  1127. break;
  1128. }
  1129. //Calculate the generally best position
  1130. xIndex = xStart;
  1131. for( x = 0; x < xCount; x++, xIndex += xDelta )
  1132. {
  1133. yIndex = yStart;
  1134. for( y = 0; y < yCount; y++, yIndex += yDelta )
  1135. {
  1136. pos.x = bounds.lo.x + ( bounds.width() * xIndex ) / xCount;
  1137. pos.y = bounds.lo.y + ( bounds.height() * yIndex ) / yCount;
  1138. pos.z = 0;
  1139. Int curCash = getPlayerSuperweaponValue( &pos, playerNdx, 2*weaponRadius, targetMilitaryUnits );
  1140. if ( curCash > cash)
  1141. {
  1142. cash = curCash;
  1143. bestPos = pos;
  1144. }
  1145. }
  1146. }
  1147. //Fine tune that position by looking at a even smaller radius.
  1148. Coord3D veryBestPos;
  1149. xCount = 11;
  1150. yCount = 11;
  1151. cash = -1;
  1152. Int count = 0;
  1153. for( x = 0; x < xCount; x++ )
  1154. {
  1155. for( y = 0; y < yCount; y++ )
  1156. {
  1157. pos.x = bestPos.x + (x-5)*(weaponRadius/10);
  1158. pos.y = bestPos.y + (x-5)*(weaponRadius/10);
  1159. pos.z = 0;
  1160. Int curCash = getPlayerSuperweaponValue( &pos, playerNdx, weaponRadius, targetMilitaryUnits );
  1161. if ( curCash > cash)
  1162. {
  1163. cash = curCash;
  1164. veryBestPos = pos;
  1165. count = 1;
  1166. }
  1167. else if (curCash==cash)
  1168. {
  1169. veryBestPos.x += pos.x;
  1170. veryBestPos.y += pos.y;
  1171. count++;
  1172. }
  1173. }
  1174. }
  1175. if (count>1) {
  1176. veryBestPos.x /= count;
  1177. veryBestPos.y /= count;
  1178. }
  1179. veryBestPos.z = TheTerrainLogic->getGroundHeight(veryBestPos.x, veryBestPos.y);
  1180. *retPos = veryBestPos;
  1181. success = ( cash > -1 );
  1182. return success;
  1183. }
  1184. //----------------------------------------------------------------------------------------------------------
  1185. /**
  1186. * Get the target value for structures in an area.
  1187. */
  1188. Int AIPlayer::getPlayerSuperweaponValue(Coord3D *center, Int playerNdx, Real radius, Bool includeMilitaryUnits )
  1189. {
  1190. if (radius < 4*PATHFIND_CELL_SIZE_F)
  1191. {
  1192. radius = 4*PATHFIND_CELL_SIZE_F;
  1193. }
  1194. Player::PlayerTeamList::const_iterator it;
  1195. Real cash = 0;
  1196. Real radSqr = sqr(radius);
  1197. Player* pPlayer = ThePlayerList->getNthPlayer(playerNdx);
  1198. if (pPlayer == NULL)
  1199. return 0;
  1200. for (it = pPlayer->getPlayerTeams()->begin(); it != pPlayer->getPlayerTeams()->end(); ++it)
  1201. {
  1202. for (DLINK_ITERATOR<Team> iter = (*it)->iterate_TeamInstanceList(); !iter.done(); iter.advance())
  1203. {
  1204. Team *team = iter.cur();
  1205. if (!team) continue;
  1206. for (DLINK_ITERATOR<Object> iter = team->iterate_TeamMemberList(); !iter.done(); iter.advance())
  1207. {
  1208. Object *pObj = iter.cur();
  1209. if (!pObj)
  1210. continue;
  1211. Bool applyNegValue = FALSE;
  1212. if( !includeMilitaryUnits )
  1213. {
  1214. if( pObj->isKindOf( KINDOF_FS_BASE_DEFENSE ) || pObj->isKindOf( KINDOF_TECH_BASE_DEFENSE ) )
  1215. {
  1216. //Hostile structure
  1217. applyNegValue = TRUE;
  1218. }
  1219. else if( pObj->isKindOf( KINDOF_VEHICLE ) || pObj->isKindOf( KINDOF_INFANTRY ) )
  1220. {
  1221. if( !pObj->isKindOf( KINDOF_DOZER ) && !pObj->isKindOf( KINDOF_HARVESTER ) )
  1222. {
  1223. //Hostile unit.
  1224. applyNegValue = TRUE;
  1225. }
  1226. }
  1227. }
  1228. else if (pObj->isKindOf(KINDOF_AIRCRAFT))
  1229. {
  1230. if (pObj->isSignificantlyAboveTerrain())
  1231. {
  1232. continue; // Don't target flying aircraft. OK if in the airstrip.
  1233. }
  1234. }
  1235. Coord3D pos = *pObj->getPosition();
  1236. Real dx = center->x - pos.x;
  1237. Real dy = center->y - pos.y;
  1238. if (dx*dx+dy*dy<radSqr)
  1239. {
  1240. Real dist = sqrt(dx*dx+dy*dy);
  1241. Real factor = 1.0f - (dist/(2*radius)); // 1.0 in center, 0.5 on edges.
  1242. Real value = pObj->getTemplate()->calcCostToBuild(pPlayer);
  1243. if (pObj->isKindOf(KINDOF_COMMANDCENTER))
  1244. {
  1245. if( !includeMilitaryUnits )
  1246. value = value * 5.0f; //Command centers are prime targets for sneak attacks.
  1247. else
  1248. value = value / 10; // Command centers cannot be killed by any superweapon, so we don't want to target them as highly. jba.
  1249. }
  1250. if (pObj->isKindOf( KINDOF_FS_SUPERWEAPON ) )
  1251. {
  1252. if( !includeMilitaryUnits )
  1253. value = value * 5.0f; //Superweapons are prime targets for sneak attacks.
  1254. else
  1255. value = value / 10; // Superweapons cannot be killed by any superweapon, so we don't want to target them as highly. jba.
  1256. }
  1257. if( applyNegValue )
  1258. {
  1259. cash -= factor * value * 5.0f; //Extremely undesired
  1260. }
  1261. else
  1262. {
  1263. cash += factor * value;
  1264. }
  1265. }
  1266. }
  1267. }
  1268. }
  1269. return cash;
  1270. }
  1271. // ------------------------------------------------------------------------------------------------
  1272. /** Search the computer player's buildings for one that can build the given request
  1273. * and start training the unit.
  1274. * If busyOK is true, it will queue a unit even if one is building. This lets
  1275. * script invoked teams "push" to the front of the queue. */
  1276. // ------------------------------------------------------------------------------------------------
  1277. Bool AIPlayer::startTraining( WorkOrder *order, Bool busyOK, AsciiString teamName)
  1278. {
  1279. Object *factory = findFactory(order->m_thing, busyOK);
  1280. if( factory )
  1281. {
  1282. ProductionUpdateInterface *pu = factory->getProductionUpdateInterface();
  1283. if (pu && pu->queueCreateUnit( order->m_thing, pu->requestUniqueUnitID() )) {
  1284. order->m_factoryID = factory->getID();
  1285. if (TheGlobalData->m_debugAI) {
  1286. AsciiString teamStr = "Queuing ";
  1287. teamStr.concat(order->m_thing->getName());
  1288. teamStr.concat(" for ");
  1289. teamStr.concat(teamName);
  1290. TheScriptEngine->AppendDebugMessage(teamStr, false);
  1291. }
  1292. return true;
  1293. }
  1294. } // end if
  1295. return FALSE;
  1296. }
  1297. // ------------------------------------------------------------------------------------------------
  1298. /** Search the computer player's buildings for one that can build the given request.
  1299. * If busyOK is true, it will return a busy factory if there are no idle ones. This is
  1300. * used for script invoked teams "push" to the front of the queue. */
  1301. // ------------------------------------------------------------------------------------------------
  1302. Object *AIPlayer::findFactory(const ThingTemplate *thing, Bool busyOK)
  1303. {
  1304. Object *busyFactory = NULL; // We prefer a factory that isn't busy.
  1305. for( BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  1306. {
  1307. Object *factory = TheGameLogic->findObjectByID( info->getObjectID() );
  1308. if( factory )
  1309. {
  1310. if (factory->getControllingPlayer() != m_player) {
  1311. info->setObjectID(INVALID_ID);
  1312. continue;
  1313. }
  1314. // ignore buildings that are under construction.
  1315. if (factory->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION))
  1316. continue;
  1317. // also ignore buildings that are being sold.
  1318. if (factory->testStatus(OBJECT_STATUS_SOLD))
  1319. continue;
  1320. ProductionUpdateInterface *pu = factory->getProductionUpdateInterface();
  1321. // If it doesn't produce, continue.
  1322. if (!pu) continue;
  1323. // if we can't create the unit do nothing
  1324. if( TheBuildAssistant->isPossibleToMakeUnit( factory, thing ) == FALSE )
  1325. continue;
  1326. // If the factory is not busy, return it.
  1327. Bool busy = pu->getProductionCount()>0;
  1328. if (!busy) return factory; // found a not busy factory.
  1329. if (busyOK) busyFactory = factory;
  1330. } // end if
  1331. } // end for
  1332. // We didn't find an idle factory, so return the busy one.
  1333. if (busyOK) return busyFactory;
  1334. return NULL;
  1335. }
  1336. // ------------------------------------------------------------------------------------------------
  1337. /** Return true if team can be considered for building */
  1338. // ------------------------------------------------------------------------------------------------
  1339. Bool AIPlayer::isPossibleToBuildTeam( TeamPrototype *proto, Bool requireIdleFactory, Bool &notEnoughMoney)
  1340. {
  1341. /* Make sure we have at least one idle factory, and factories for all unit types. */
  1342. Bool anyIdle = false;
  1343. Int cost=0;
  1344. notEnoughMoney = false;
  1345. for( int i=0; i<proto->getTemplateInfo()->m_numUnitsInfo; i++ )
  1346. {
  1347. const TCreateUnitsInfo *unitInfo = &proto->getTemplateInfo()->m_unitsInfo[0];
  1348. const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
  1349. if (thing) {
  1350. Int thingCost = thing->calcCostToBuild(m_player);
  1351. if (NULL == findFactory(thing, true)) {
  1352. // Couldn't find a factory.
  1353. return false;
  1354. }
  1355. if (NULL != findFactory(thing, false)) {
  1356. // Found an idle factory.
  1357. anyIdle = true;
  1358. }
  1359. cost += thingCost * ((unitInfo[i].maxUnits+unitInfo[i].minUnits)/2.0f);
  1360. }
  1361. }
  1362. cost *= TheAI->getAiData()->m_teamResourcesToBuild;
  1363. if (m_player->getMoney()->countMoney() < cost) {
  1364. notEnoughMoney = true;
  1365. return false; // too expensive
  1366. }
  1367. if (anyIdle)
  1368. {
  1369. return true;
  1370. }
  1371. if (!requireIdleFactory)
  1372. {
  1373. // Doesn't require an idle factory, so we're ok.
  1374. return true;
  1375. }
  1376. return false;
  1377. }
  1378. // ------------------------------------------------------------------------------------------------
  1379. /** Check if this team is buildable, doesn't exceed maximum limits, meets conditions,
  1380. * and isn't under construction. */
  1381. // ------------------------------------------------------------------------------------------------
  1382. Bool AIPlayer::isAGoodIdeaToBuildTeam( TeamPrototype *proto )
  1383. {
  1384. // Check condition.
  1385. if (!proto->evaluateProductionCondition()) {
  1386. return false;
  1387. }
  1388. // check build limit
  1389. if (proto->countTeamInstances() >= proto->getTemplateInfo()->m_maxInstances){
  1390. if (TheGlobalData->m_debugAI) {
  1391. AsciiString str;
  1392. str.format("Team %s not chosen - %d already exist.", proto->getName().str(), proto->countTeamInstances());
  1393. TheScriptEngine->AppendDebugMessage(str, false);
  1394. }
  1395. return false; // Max already built.
  1396. }
  1397. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  1398. {
  1399. TeamInQueue *team = iter.cur();
  1400. if (team->m_team->getPrototype() == proto) {
  1401. return false; // currently building one of these.
  1402. }
  1403. }
  1404. Bool needMoney;
  1405. if (!isPossibleToBuildTeam( proto, true, needMoney)) {
  1406. if (TheGlobalData->m_debugAI) {
  1407. AsciiString str;
  1408. if (needMoney) {
  1409. str.format("Team %s not chosen - Not enough money.", proto->getName().str());
  1410. } else {
  1411. str.format("Team %s not chosen - Factory/tech missing or busy.", proto->getName().str());
  1412. }
  1413. TheScriptEngine->AppendDebugMessage(str, false);
  1414. }
  1415. return false;
  1416. }
  1417. return true;
  1418. }
  1419. // ------------------------------------------------------------------------------------------------
  1420. /** See if any existing teams need reinforcements, and have higher priority. */
  1421. // ------------------------------------------------------------------------------------------------
  1422. Bool AIPlayer::selectTeamToReinforce( Int minPriority )
  1423. {
  1424. // Find a high production priority team that needs reinforcements.
  1425. Player::PlayerTeamList::const_iterator t;
  1426. Team *curTeam = NULL;
  1427. Int curPriority = minPriority; // Don't reinforce a team unless it is above min priority.
  1428. const ThingTemplate *curThing = NULL;
  1429. for (t = m_player->getPlayerTeams()->begin(); t != m_player->getPlayerTeams()->end(); ++t)
  1430. {
  1431. TeamPrototype *proto = (*t);
  1432. Bool busy = false;
  1433. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  1434. {
  1435. TeamInQueue *team = iter.cur();
  1436. if (team->m_team->getPrototype() == proto) {
  1437. busy = true; // currently building one of these.
  1438. }
  1439. }
  1440. if (busy) continue;
  1441. if (proto->getTemplateInfo()->m_automaticallyReinforce && proto->getTemplateInfo()->m_productionPriority>curPriority) {
  1442. // Check the team instances.
  1443. for (DLINK_ITERATOR<Team> iter = proto->iterate_TeamInstanceList(); !iter.done(); iter.advance())
  1444. {
  1445. Team *team = iter.cur();
  1446. if (team->hasAnyUnits() == false)
  1447. {
  1448. continue; // empty.
  1449. }
  1450. const TCreateUnitsInfo *unitInfo = &team->getPrototype()->getTemplateInfo()->m_unitsInfo[0];
  1451. for( int i=0; i<team->getPrototype()->getTemplateInfo()->m_numUnitsInfo; i++ )
  1452. {
  1453. if (unitInfo[i].maxUnits < 1) continue;
  1454. const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
  1455. if (thing==NULL) continue;
  1456. Int count=0;
  1457. team->countObjectsByThingTemplate(1, &thing, false, &count);
  1458. if (count < unitInfo[i].maxUnits)
  1459. {
  1460. // See if there is a factory available.
  1461. if (NULL != findFactory(thing, false))
  1462. {
  1463. curTeam = team;
  1464. curPriority = proto->getTemplateInfo()->m_productionPriority;
  1465. curThing = thing;
  1466. }
  1467. }
  1468. }
  1469. }
  1470. }
  1471. }
  1472. if (curTeam && curThing)
  1473. {
  1474. /* We have something to build. */
  1475. TeamInQueue *teamQ = newInstance(TeamInQueue);
  1476. // Put in front of queue.
  1477. prependTo_TeamBuildQueue(teamQ);
  1478. teamQ->m_priorityBuild = false;
  1479. teamQ->m_reinforcement = true;
  1480. WorkOrder *order = newInstance(WorkOrder);
  1481. order->m_thing = curThing;
  1482. order->m_factoryID = INVALID_ID;
  1483. order->m_numRequired = 1;
  1484. order->m_required = true;
  1485. // prepend to head of list
  1486. order->m_next = NULL;
  1487. teamQ->m_workOrders = order;
  1488. teamQ->m_frameStarted = TheGameLogic->getFrame();
  1489. teamQ->m_team = curTeam;
  1490. AsciiString teamName = curTeam->getPrototype()->getName();
  1491. teamName.concat(" - AutoReinforcing one ");
  1492. teamName.concat(curThing->getName());
  1493. TheScriptEngine->AppendDebugMessage(teamName, false);
  1494. // start the creation of a new unit
  1495. Coord3D origin;
  1496. origin = curTeam->getPrototype()->getTemplateInfo()->m_homeLocation;
  1497. if (curTeam->getFirstItemIn_TeamMemberList())
  1498. {
  1499. origin = *curTeam->getFirstItemIn_TeamMemberList()->getPosition();
  1500. }
  1501. Object *unit = curTeam->tryToRecruit(curThing, &origin, TheAI->getAiData()->m_maxRecruitDistance);
  1502. if (unit)
  1503. {
  1504. order->m_numCompleted = 1;
  1505. AsciiString teamStr = "Team '";
  1506. teamStr.concat(curTeam->getPrototype()->getName());
  1507. teamStr.concat("' recruits ");
  1508. teamStr.concat(curThing->getName());
  1509. teamStr.concat(" from team '");
  1510. teamStr.concat(unit->getTeam()->getPrototype()->getName());
  1511. teamStr.concat("'");
  1512. TheScriptEngine->AppendDebugMessage(teamStr, false);
  1513. unit->setTeam(curTeam);
  1514. teamQ->m_reinforcementID = unit->getID();
  1515. AIUpdateInterface *ai = unit->getAIUpdateInterface();
  1516. if (ai)
  1517. {
  1518. ai->aiIdle(CMD_FROM_AI);
  1519. }
  1520. } else {
  1521. startTraining( order, teamQ->m_priorityBuild, teamQ->m_team->getName());
  1522. }
  1523. m_teamDelay = 0;
  1524. return true;
  1525. }
  1526. return false;
  1527. }
  1528. // ------------------------------------------------------------------------------------------------
  1529. /** Determine the next team to build. Return true if one was selected. */
  1530. // ------------------------------------------------------------------------------------------------
  1531. Bool AIPlayer::selectTeamToBuild( void )
  1532. {
  1533. // find the highest priority of all teams
  1534. Player::PlayerTeamList::const_iterator t;
  1535. const Int invalidPri = -99999;
  1536. Int hiPri = invalidPri;
  1537. // collect all teams that are possible to build, and are at the highest priority
  1538. Player::PlayerTeamList candidateList1;
  1539. for (t = m_player->getPlayerTeams()->begin(); t != m_player->getPlayerTeams()->end(); ++t)
  1540. {
  1541. if (isAGoodIdeaToBuildTeam(*t))
  1542. {
  1543. candidateList1.push_back( (*t) );
  1544. Int pri = (*t)->getTemplateInfo()->m_productionPriority;
  1545. if (pri > hiPri)
  1546. {
  1547. hiPri = pri;
  1548. }
  1549. }
  1550. }
  1551. if (selectTeamToReinforce(hiPri)) {
  1552. return true;
  1553. }
  1554. // check if no team prototypes are valid for production
  1555. if (hiPri == invalidPri)
  1556. return false;
  1557. if (TheGlobalData->m_debugAI) {
  1558. TheScriptEngine->AppendDebugMessage("**AI** Selecting team to build", false);
  1559. }
  1560. // collect all teams that are possible to build, and are at the highest priority
  1561. Player::PlayerTeamList candidateList;
  1562. Int count = 0;
  1563. for (t = candidateList1.begin(); t != candidateList1.end(); ++t)
  1564. {
  1565. if ((*t)->getTemplateInfo()->m_productionPriority == hiPri)
  1566. {
  1567. candidateList.push_back( (*t) );
  1568. count++;
  1569. }
  1570. }
  1571. // pick a random team from the hi-priority set
  1572. Int which = GameLogicRandomValue( 0, count-1 );
  1573. TeamPrototype *teamProto = NULL;
  1574. Int i = 0;
  1575. for (t = candidateList.begin(); t != candidateList.end(); ++t)
  1576. {
  1577. if (i == which)
  1578. {
  1579. teamProto = (*t);
  1580. break;
  1581. }
  1582. i++;
  1583. }
  1584. if (teamProto) {
  1585. if (!teamProto->getTemplateInfo()->m_hasHomeLocation && !isSkirmishAI()) {
  1586. AsciiString teamStr = "Error : team '";
  1587. teamStr.concat(teamProto->getName());
  1588. teamStr.concat("' has no Home Position (or Origin).");
  1589. TheScriptEngine->AppendDebugMessage(teamStr, false);
  1590. }
  1591. // Build it at low priority, as we have selected it automagically.
  1592. buildSpecificAITeam(teamProto, false);
  1593. m_readyToBuildTeam = false;
  1594. m_teamTimer = m_teamSeconds*LOGICFRAMES_PER_SECOND;
  1595. if (m_player->getMoney()->countMoney() < TheAI->getAiData()->m_resourcesPoor) {
  1596. m_teamTimer = m_teamTimer/TheAI->getAiData()->m_teamPoorMod;
  1597. } else if (m_player->getMoney()->countMoney() > TheAI->getAiData()->m_resourcesWealthy) {
  1598. m_teamTimer = m_teamTimer/TheAI->getAiData()->m_teamWealthyMod;
  1599. }
  1600. return true;
  1601. }
  1602. return false;
  1603. }
  1604. // ------------------------------------------------------------------------------------------------
  1605. /** Build a specific team. If priorityBuild, put at front of queue with priority set. */
  1606. // ------------------------------------------------------------------------------------------------
  1607. void AIPlayer::buildSpecificAIBuilding(const AsciiString &thingName)
  1608. {
  1609. //
  1610. AsciiString teamStr = "Error : Solo ai doesn't support BuildSpecificBuilding. '";
  1611. teamStr.concat(thingName);
  1612. teamStr.concat("' not built.");
  1613. TheScriptEngine->AppendDebugMessage(teamStr, false);
  1614. }
  1615. // ------------------------------------------------------------------------------------------------
  1616. /** Build an upgrade. */
  1617. // ------------------------------------------------------------------------------------------------
  1618. void AIPlayer::buildUpgrade(const AsciiString &upgrade)
  1619. {
  1620. const UpgradeTemplate *curUpgrade = TheUpgradeCenter->findUpgrade(upgrade);
  1621. if (curUpgrade==NULL) {
  1622. AsciiString msg = "Upgrade ";
  1623. msg.concat(upgrade);
  1624. msg.concat(" does not exist. Ignoring request.");
  1625. TheScriptEngine->AppendDebugMessage( msg, false);
  1626. return;
  1627. }
  1628. if (curUpgrade->getUpgradeType()==UPGRADE_TYPE_OBJECT) {
  1629. AsciiString msg = "Player build upgrade: Upgrade ";
  1630. msg.concat(upgrade);
  1631. msg.concat(" is an object, not a player upgrade. Ignoring request.");
  1632. TheScriptEngine->AppendDebugMessage( msg, false);
  1633. return;
  1634. }
  1635. // See if it is in progress.
  1636. if (m_player->hasUpgradeInProduction(curUpgrade)) {
  1637. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  1638. msg.concat(" already has upgrade ");
  1639. msg.concat(upgrade);
  1640. msg.concat(" queued. Ignoring request.");
  1641. TheScriptEngine->AppendDebugMessage( msg, false);
  1642. return;
  1643. }
  1644. // See if it is in progress.
  1645. if (m_player->hasUpgradeComplete(curUpgrade)) {
  1646. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  1647. msg.concat(" already has upgrade ");
  1648. msg.concat(upgrade);
  1649. msg.concat(" completed. Ignoring request.");
  1650. TheScriptEngine->AppendDebugMessage( msg, false);
  1651. return;
  1652. }
  1653. // No money.
  1654. if( TheUpgradeCenter->canAffordUpgrade( m_player, curUpgrade ) == FALSE ) {
  1655. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  1656. msg.concat(" lacks money to build upgrade ");
  1657. msg.concat(upgrade);
  1658. msg.concat(" at this time. Ignoring request.");
  1659. TheScriptEngine->AppendDebugMessage( msg, false);
  1660. return;
  1661. }
  1662. // Find a production queue.
  1663. for( const BuildListInfo *info = m_player->getBuildList(); info; info = info->getNext() )
  1664. {
  1665. Object *factory = TheGameLogic->findObjectByID( info->getObjectID() );
  1666. if( factory )
  1667. {
  1668. if( factory->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
  1669. continue;
  1670. if( factory->getStatusBits().test( OBJECT_STATUS_SOLD ) )
  1671. continue;
  1672. Bool canUpgradeHere = false;
  1673. const CommandSet *commandSet = TheControlBar->findCommandSet( factory->getCommandSetString() );
  1674. if( commandSet == NULL) continue;
  1675. for( Int j = 0; j < MAX_COMMANDS_PER_SET; j++ )
  1676. {
  1677. //Get the command button.
  1678. const CommandButton *commandButton = commandSet->getCommandButton(j);
  1679. if (commandButton==NULL) continue;
  1680. if (commandButton->getName().isEmpty() ) continue;
  1681. if (commandButton->getUpgradeTemplate() == NULL ) continue;
  1682. if (commandButton->getUpgradeTemplate()->getUpgradeName() == curUpgrade->getUpgradeName()) {
  1683. canUpgradeHere = true;
  1684. }
  1685. }
  1686. if (!canUpgradeHere) continue;
  1687. ProductionUpdateInterface *pu = factory->getProductionUpdateInterface();
  1688. // If it doesn't produce, continue.
  1689. if (!pu) continue;
  1690. // Try to queue it.
  1691. if (pu->queueUpgrade(curUpgrade)) {
  1692. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  1693. msg.concat(" queues ");
  1694. msg.concat(curUpgrade->getUpgradeName());
  1695. msg.concat(" at ");
  1696. msg.concat(factory->getTemplate()->getName());
  1697. TheScriptEngine->AppendDebugMessage( msg, false);
  1698. return;
  1699. }
  1700. } // end if
  1701. } // end for
  1702. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  1703. msg.concat(" lacks factory to build upgrade ");
  1704. msg.concat(upgrade);
  1705. msg.concat(" at this time. Ignoring request.");
  1706. TheScriptEngine->AppendDebugMessage( msg, false);
  1707. return;
  1708. }
  1709. // ------------------------------------------------------------------------------------------------
  1710. /** Build a supply center near a supply source with minimumCash or more resources. */
  1711. // ------------------------------------------------------------------------------------------------
  1712. void AIPlayer::buildBySupplies(Int minimumCash, const AsciiString& thingName)
  1713. {
  1714. Object *bestSupplyWarehouse = findSupplyCenter(minimumCash);
  1715. const ThingTemplate* tTemplate = TheThingFactory->findTemplate(thingName);
  1716. if (!tTemplate->isKindOf(KINDOF_CASH_GENERATOR)) {
  1717. // Build by the current warehouse.
  1718. Object *curWarehouse = TheGameLogic->findObjectByID(m_curWarehouseID);
  1719. if (curWarehouse) {
  1720. bestSupplyWarehouse = curWarehouse;
  1721. }
  1722. }
  1723. if (bestSupplyWarehouse && tTemplate) {
  1724. Coord3D location;
  1725. location = *bestSupplyWarehouse->getPosition();
  1726. // offset back towards the base.
  1727. Coord2D offset;
  1728. offset.x = location.x - m_baseCenter.x;
  1729. offset.y = location.y - m_baseCenter.y;
  1730. offset.normalize();
  1731. Real radius = 3*PATHFIND_CELL_SIZE_F;
  1732. if (!tTemplate->isKindOf(KINDOF_CASH_GENERATOR)) {
  1733. // It's probably a defensive structure - build towards the enemy.
  1734. Region2D bounds;
  1735. Int enemyNdx = TheScriptEngine->getSkirmishEnemyPlayer()->getPlayerIndex();
  1736. getPlayerStructureBounds(&bounds, enemyNdx);
  1737. offset.x = location.x - (bounds.lo.x+bounds.hi.x)*0.5f;
  1738. offset.y = location.y - (bounds.lo.y+bounds.hi.y)*0.5f;
  1739. offset.normalize();
  1740. radius = bestSupplyWarehouse->getGeometryInfo().getBoundingCircleRadius();
  1741. }
  1742. location.x -= offset.x*radius;
  1743. location.y -= offset.y*radius;
  1744. Real angle = tTemplate->getPlacementViewAngle();
  1745. // validate the the position to build at is valid
  1746. Bool valid=false;
  1747. Coord3D newPos = location;
  1748. if( TheBuildAssistant->isLocationLegalToBuild( &location, tTemplate, angle,
  1749. BuildAssistant::NO_OBJECT_OVERLAP,
  1750. NULL, m_player ) != LBC_OK ) {
  1751. // Warn.
  1752. const Coord3D *warehouseLocation = bestSupplyWarehouse->getPosition();
  1753. AsciiString debugMessage;
  1754. debugMessage.format(" %s - buildBySupplies unable to place near dock at (%.2f,%.2f). Attempting to adjust position.",
  1755. tTemplate->getName().str(),
  1756. warehouseLocation->x,
  1757. warehouseLocation->y
  1758. );
  1759. TheScriptEngine->AppendDebugMessage(debugMessage, false);
  1760. if( TheGlobalData->m_debugSupplyCenterPlacement )
  1761. DEBUG_LOG(("%s", debugMessage.str()));
  1762. // try to fix.
  1763. Real posOffset;
  1764. // Wiggle it a little :)
  1765. for (posOffset = 0; posOffset<2*SUPPLY_CENTER_CLOSE_DIST; posOffset += 2*PATHFIND_CELL_SIZE_F) {
  1766. Real offset = posOffset/2;
  1767. Real xPos, yPos;
  1768. yPos = location.y-offset;
  1769. for (xPos = location.x-offset; xPos <= location.x+offset; xPos+=PATHFIND_CELL_SIZE_F) {
  1770. newPos.x = xPos;
  1771. newPos.y = yPos;
  1772. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1773. BuildAssistant::CLEAR_PATH |
  1774. BuildAssistant::TERRAIN_RESTRICTIONS |
  1775. BuildAssistant::NO_OBJECT_OVERLAP,
  1776. NULL, m_player ) == LBC_OK;
  1777. if (valid) break;
  1778. if( TheGlobalData->m_debugSupplyCenterPlacement )
  1779. DEBUG_LOG(("buildBySupplies -- Fail at (%.2f,%.2f)\n", newPos.x, newPos.y));
  1780. newPos.y = yPos+posOffset;
  1781. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1782. BuildAssistant::CLEAR_PATH |
  1783. BuildAssistant::TERRAIN_RESTRICTIONS |
  1784. BuildAssistant::NO_OBJECT_OVERLAP,
  1785. NULL, m_player ) == LBC_OK;
  1786. if (valid) break;
  1787. if( TheGlobalData->m_debugSupplyCenterPlacement )
  1788. DEBUG_LOG(("buildBySupplies -- Fail at (%.2f,%.2f)\n", newPos.x, newPos.y));
  1789. }
  1790. if (valid) break;
  1791. xPos = location.x-offset;
  1792. for (yPos = location.y-offset; yPos <= location.y+offset; yPos+=PATHFIND_CELL_SIZE_F) {
  1793. newPos.x = xPos;
  1794. newPos.y = yPos;
  1795. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1796. BuildAssistant::CLEAR_PATH |
  1797. BuildAssistant::TERRAIN_RESTRICTIONS |
  1798. BuildAssistant::NO_OBJECT_OVERLAP,
  1799. NULL, m_player ) == LBC_OK;
  1800. if (valid) break;
  1801. if( TheGlobalData->m_debugSupplyCenterPlacement )
  1802. DEBUG_LOG(("buildBySupplies -- Fail at (%.2f,%.2f)\n", newPos.x, newPos.y));
  1803. newPos.x = xPos+posOffset;
  1804. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1805. BuildAssistant::CLEAR_PATH |
  1806. BuildAssistant::TERRAIN_RESTRICTIONS |
  1807. BuildAssistant::NO_OBJECT_OVERLAP,
  1808. NULL, m_player ) == LBC_OK;
  1809. if (valid) break;
  1810. if( TheGlobalData->m_debugSupplyCenterPlacement )
  1811. DEBUG_LOG(("buildBySupplies -- Fail at (%.2f,%.2f)\n", newPos.x, newPos.y));
  1812. }
  1813. if (valid) break;
  1814. }
  1815. }
  1816. if (valid)
  1817. {
  1818. if( TheGlobalData->m_debugSupplyCenterPlacement )
  1819. DEBUG_LOG(("buildAISupplyCenter -- SUCCESS at (%.2f,%.2f)\n", newPos.x, newPos.y));
  1820. location = newPos;
  1821. }
  1822. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  1823. location.z = 0; // All build list locations are ground relative.
  1824. m_player->addToPriorityBuildList(thingName, &location, angle);
  1825. m_curWarehouseID = bestSupplyWarehouse->getID();
  1826. }
  1827. }
  1828. // ------------------------------------------------------------------------------------------------
  1829. /** Calculates the closest construction zone location based on a template. */
  1830. // ------------------------------------------------------------------------------------------------
  1831. Bool AIPlayer::calcClosestConstructionZoneLocation( const ThingTemplate *constructTemplate, Coord3D *location )
  1832. {
  1833. if( !constructTemplate || !location )
  1834. {
  1835. return FALSE;
  1836. }
  1837. Bool success = FALSE;
  1838. // offset back towards the base.
  1839. Coord2D offset;
  1840. offset.x = location->x - m_baseCenter.x;
  1841. offset.y = location->y - m_baseCenter.y;
  1842. offset.normalize();
  1843. Real angle = constructTemplate->getPlacementViewAngle();
  1844. // validate the the position to build at is valid
  1845. Bool valid=false;
  1846. Coord3D newPos = *location;
  1847. if( TheBuildAssistant->isLocationLegalToBuild( location, constructTemplate, angle, BuildAssistant::NO_OBJECT_OVERLAP, NULL, m_player ) != LBC_OK )
  1848. {
  1849. // Warn.
  1850. AsciiString bldgName = constructTemplate->getName();
  1851. bldgName.concat(" - calcClosestConstructionZoneLocation unable to place. Attempting to adjust position.");
  1852. TheScriptEngine->AppendDebugMessage( bldgName, false );
  1853. // try to fix.
  1854. Real posOffset;
  1855. // Wiggle it a little :)
  1856. for( posOffset = 0; posOffset < 2 * SUPPLY_CENTER_CLOSE_DIST; posOffset += 2 * PATHFIND_CELL_SIZE_F )
  1857. {
  1858. Real offset = posOffset / 2;
  1859. Real xPos, yPos;
  1860. yPos = location->y - offset;
  1861. for( xPos = location->x - offset; xPos <= location->x + offset; xPos += PATHFIND_CELL_SIZE_F )
  1862. {
  1863. newPos.x = xPos;
  1864. newPos.y = yPos;
  1865. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, constructTemplate, angle,
  1866. BuildAssistant::CLEAR_PATH |
  1867. BuildAssistant::TERRAIN_RESTRICTIONS |
  1868. BuildAssistant::NO_OBJECT_OVERLAP,
  1869. NULL, m_player ) == LBC_OK;
  1870. if( valid )
  1871. break;
  1872. newPos.y = yPos + posOffset;
  1873. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, constructTemplate, angle,
  1874. BuildAssistant::CLEAR_PATH |
  1875. BuildAssistant::TERRAIN_RESTRICTIONS |
  1876. BuildAssistant::NO_OBJECT_OVERLAP,
  1877. NULL, m_player ) == LBC_OK;
  1878. }
  1879. if( valid )
  1880. break;
  1881. xPos = location->x - offset;
  1882. for( yPos = location->y - offset; yPos <= location->y + offset; yPos += PATHFIND_CELL_SIZE_F )
  1883. {
  1884. newPos.x = xPos;
  1885. newPos.y = yPos;
  1886. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, constructTemplate, angle,
  1887. BuildAssistant::CLEAR_PATH |
  1888. BuildAssistant::TERRAIN_RESTRICTIONS |
  1889. BuildAssistant::NO_OBJECT_OVERLAP,
  1890. NULL, m_player ) == LBC_OK;
  1891. if( valid )
  1892. break;
  1893. newPos.x = xPos + posOffset;
  1894. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, constructTemplate, angle,
  1895. BuildAssistant::CLEAR_PATH |
  1896. BuildAssistant::TERRAIN_RESTRICTIONS |
  1897. BuildAssistant::NO_OBJECT_OVERLAP,
  1898. NULL, m_player ) == LBC_OK;
  1899. }
  1900. if( valid )
  1901. break;
  1902. }
  1903. }
  1904. if( valid )
  1905. {
  1906. //We succeeded in calculating the best position.
  1907. location->set( &newPos );
  1908. success = TRUE;
  1909. }
  1910. else
  1911. {
  1912. //We failed to calculate a position, so zero out the position.
  1913. location->zero();
  1914. success = FALSE;
  1915. }
  1916. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  1917. return success;
  1918. }
  1919. // ------------------------------------------------------------------------------------------------
  1920. /** Build a specific building nearest specified team. */
  1921. // ------------------------------------------------------------------------------------------------
  1922. void AIPlayer::buildSpecificBuildingNearestTeam( const AsciiString &thingName, const Team *team )
  1923. {
  1924. const ThingTemplate *tTemplate = TheThingFactory->findTemplate( thingName );
  1925. if( !tTemplate || !team )
  1926. {
  1927. //Assert will already happen in the failed findTemplate call.
  1928. return;
  1929. }
  1930. //From the team's location, find the most valid build location.
  1931. const Coord3D *location = team->getEstimateTeamPosition();
  1932. if( !location )
  1933. {
  1934. return;
  1935. }
  1936. // offset back towards the base.
  1937. Coord2D offset;
  1938. offset.x = location->x - m_baseCenter.x;
  1939. offset.y = location->y - m_baseCenter.y;
  1940. offset.normalize();
  1941. Real angle = tTemplate->getPlacementViewAngle();
  1942. // validate the the position to build at is valid
  1943. Bool valid=false;
  1944. Coord3D newPos = *location;
  1945. if( TheBuildAssistant->isLocationLegalToBuild( location, tTemplate, angle, BuildAssistant::NO_OBJECT_OVERLAP, NULL, m_player ) != LBC_OK )
  1946. {
  1947. // Warn.
  1948. AsciiString bldgName = tTemplate->getName();
  1949. bldgName.concat(" - buildSpecificBuildingNearestTeam unable to place. Attempting to adjust position.");
  1950. TheScriptEngine->AppendDebugMessage( bldgName, false );
  1951. // try to fix.
  1952. Real posOffset;
  1953. // Wiggle it a little :)
  1954. for( posOffset = 0; posOffset < 2 * SUPPLY_CENTER_CLOSE_DIST; posOffset += 2 * PATHFIND_CELL_SIZE_F )
  1955. {
  1956. Real offset = posOffset / 2;
  1957. Real xPos, yPos;
  1958. yPos = location->y-offset;
  1959. for( xPos = location->x - offset; xPos <= location->x + offset; xPos += PATHFIND_CELL_SIZE_F )
  1960. {
  1961. newPos.x = xPos;
  1962. newPos.y = yPos;
  1963. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1964. BuildAssistant::CLEAR_PATH |
  1965. BuildAssistant::TERRAIN_RESTRICTIONS |
  1966. BuildAssistant::NO_OBJECT_OVERLAP,
  1967. NULL, m_player ) == LBC_OK;
  1968. if( valid )
  1969. break;
  1970. newPos.y = yPos + posOffset;
  1971. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1972. BuildAssistant::CLEAR_PATH |
  1973. BuildAssistant::TERRAIN_RESTRICTIONS |
  1974. BuildAssistant::NO_OBJECT_OVERLAP,
  1975. NULL, m_player ) == LBC_OK;
  1976. }
  1977. if( valid )
  1978. break;
  1979. xPos = location->x - offset;
  1980. for( yPos = location->y - offset; yPos <= location->y + offset; yPos += PATHFIND_CELL_SIZE_F )
  1981. {
  1982. newPos.x = xPos;
  1983. newPos.y = yPos;
  1984. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1985. BuildAssistant::CLEAR_PATH |
  1986. BuildAssistant::TERRAIN_RESTRICTIONS |
  1987. BuildAssistant::NO_OBJECT_OVERLAP,
  1988. NULL, m_player ) == LBC_OK;
  1989. if( valid )
  1990. break;
  1991. newPos.x = xPos + posOffset;
  1992. valid = TheBuildAssistant->isLocationLegalToBuild( &newPos, tTemplate, angle,
  1993. BuildAssistant::CLEAR_PATH |
  1994. BuildAssistant::TERRAIN_RESTRICTIONS |
  1995. BuildAssistant::NO_OBJECT_OVERLAP,
  1996. NULL, m_player ) == LBC_OK;
  1997. }
  1998. if( valid )
  1999. break;
  2000. }
  2001. }
  2002. if( valid )
  2003. {
  2004. newPos.z = 0; // All build list locations are ground relative.
  2005. m_player->addToPriorityBuildList( thingName, &newPos, angle );
  2006. }
  2007. TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba.
  2008. }
  2009. // ------------------------------------------------------------------------------------------------
  2010. /** Find a supply center we haven't built a supply depot near yet. */
  2011. // ------------------------------------------------------------------------------------------------
  2012. Object *AIPlayer::findSupplyCenter(Int minimumCash)
  2013. {
  2014. Object *bestSupplyWarehouse = NULL;
  2015. Real bestDistSqr = 0;
  2016. Object *obj;
  2017. Coord3D enemyCenter;
  2018. enemyCenter.zero();
  2019. Region2D bounds;
  2020. Player *enemy = getAiEnemy();
  2021. if (enemy) {
  2022. getPlayerStructureBounds(&bounds, enemy->getPlayerIndex());
  2023. enemyCenter.set( (bounds.lo.x+bounds.hi.x)*0.5f, (bounds.lo.y+bounds.hi.y)*0.5f, 0);
  2024. }
  2025. do {
  2026. for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
  2027. {
  2028. if (!obj->isKindOf(KINDOF_STRUCTURE)) continue;
  2029. if (!obj->isKindOf(KINDOF_SUPPLY_SOURCE)) continue;
  2030. static const NameKeyType key_warehouseUpdate = NAMEKEY("SupplyWarehouseDockUpdate");
  2031. SupplyWarehouseDockUpdate *warehouseModule = (SupplyWarehouseDockUpdate*)obj->findUpdateModule( key_warehouseUpdate );
  2032. if( warehouseModule ) {
  2033. Int availableCash = warehouseModule->getBoxesStored()*TheGlobalData->m_baseValuePerSupplyBox;
  2034. if (availableCash<minimumCash) continue;
  2035. if( m_player->getRelationship(obj->getTeam()) == ENEMIES ) {
  2036. continue;
  2037. }
  2038. // Make sure we don't have a supply center near it.
  2039. Coord3D center = *obj->getPosition();
  2040. Real radius = SUPPLY_CENTER_CLOSE_DIST + obj->getGeometryInfo().getBoundingCircleRadius();
  2041. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_CASH_GENERATOR), KINDOFMASK_NONE);
  2042. PartitionFilterPlayer f2(m_player, true); // Only find your own units.
  2043. PartitionFilterOnMap filterMapStatus;
  2044. PartitionFilter *filters[] = { &f1, &f2, &filterMapStatus, 0 };
  2045. Object *supplyCenter = ThePartitionManager->getClosestObject(&center, radius, FROM_BOUNDINGSPHERE_2D, filters);
  2046. if (supplyCenter) {
  2047. // We already have a supply center.
  2048. continue;
  2049. }
  2050. Real dx, dy;
  2051. dx = obj->getPosition()->x - m_baseCenter.x;
  2052. dy = obj->getPosition()->y - m_baseCenter.y;
  2053. Real distSqr = dx*dx + dy*dy;
  2054. if (enemy) {
  2055. // make sure this isn't closer to our enemy than us.
  2056. dx = obj->getPosition()->x - enemyCenter.x;
  2057. dy = obj->getPosition()->y - enemyCenter.y;
  2058. if (distSqr*0.4>(dx*dx+dy*dy)*0.6f) {
  2059. // closer than 60/40 to enemy than to us, probably not a good candidate for expansion.
  2060. continue;
  2061. }
  2062. }
  2063. if (bestSupplyWarehouse==NULL) {
  2064. bestSupplyWarehouse = obj;
  2065. bestDistSqr = distSqr;
  2066. } else if (bestDistSqr>distSqr) {
  2067. bestSupplyWarehouse = obj;
  2068. bestDistSqr = distSqr;
  2069. }
  2070. }
  2071. }
  2072. if (bestSupplyWarehouse) break;
  2073. minimumCash /= 2;
  2074. } while (minimumCash > 100);
  2075. return bestSupplyWarehouse;
  2076. }
  2077. // ------------------------------------------------------------------------------------------------
  2078. /** Build a base defense. */
  2079. // ------------------------------------------------------------------------------------------------
  2080. void AIPlayer::buildAIBaseDefense(Bool flank)
  2081. {
  2082. //
  2083. AsciiString teamStr = "Error : Solo ai doesn't support buildAIBaseDefense. '";
  2084. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2085. }
  2086. // ------------------------------------------------------------------------------------------------
  2087. /** Build a base defense. */
  2088. // ------------------------------------------------------------------------------------------------
  2089. void AIPlayer::buildAIBaseDefenseStructure(const AsciiString &thingName, Bool flank)
  2090. {
  2091. //
  2092. AsciiString teamStr = "Error : Solo ai doesn't support buildAIBaseDefenseStructure. '";
  2093. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2094. }
  2095. // ------------------------------------------------------------------------------------------------
  2096. /** Repair a bridge or other structure. */
  2097. // ------------------------------------------------------------------------------------------------
  2098. void AIPlayer::repairStructure(ObjectID structure)
  2099. {
  2100. Object *structureObj = TheGameLogic->findObjectByID(structure);
  2101. if (structureObj==NULL) return;
  2102. if (structureObj->getBodyModule()==NULL) return;
  2103. // If the structure is not noticably damaged, don't bother.
  2104. enum BodyDamageType structureState = structureObj->getBodyModule()->getDamageState();
  2105. if (structureState==BODY_PRISTINE) {
  2106. return;
  2107. }
  2108. if (structureObj->isKindOf(KINDOF_BRIDGE)) {
  2109. // Locate the correct post to repair.
  2110. }
  2111. Int i;
  2112. for (i=0; i<m_structuresInQueue; i++) {
  2113. if (m_structuresToRepair[i] == structureObj->getID()) {
  2114. DEBUG_LOG(("info - Bridge already queued for repair.\n"));
  2115. return;
  2116. }
  2117. }
  2118. if (m_structuresInQueue==MAX_STRUCTURES_TO_REPAIR) {
  2119. DEBUG_LOG(("Structure repair queue is full, ignoring repair request. JBA\n"));
  2120. return;
  2121. }
  2122. m_structuresToRepair[m_structuresInQueue] = structureObj->getID();
  2123. m_structuresInQueue++;
  2124. }
  2125. // ------------------------------------------------------------------------------------------------
  2126. /** select a skillset for the player. */
  2127. // ------------------------------------------------------------------------------------------------
  2128. void AIPlayer::selectSkillset(Int skillset)
  2129. {
  2130. DEBUG_ASSERTCRASH(m_skillsetSelector == INVALID_SKILLSET_SELECTION,
  2131. ("Selecting a skill set (%d) after one has already been chosen (%d) means some points have been incorrectly spent.\n", skillset + 1, m_skillsetSelector + 1));
  2132. m_skillsetSelector = skillset;
  2133. }
  2134. // ------------------------------------------------------------------------------------------------
  2135. /** Do per frame work (if any) repairing bridges. */
  2136. // ------------------------------------------------------------------------------------------------
  2137. void AIPlayer::updateBridgeRepair(void)
  2138. {
  2139. if (m_structuresInQueue == 0) return;
  2140. // Check once a second.
  2141. m_bridgeTimer--;
  2142. if (m_bridgeTimer>0) return;
  2143. m_bridgeTimer = LOGICFRAMES_PER_SECOND;
  2144. Object *bridgeObj=NULL;
  2145. while (bridgeObj==NULL && m_structuresInQueue>0) {
  2146. bridgeObj = TheGameLogic->findObjectByID(m_structuresToRepair[0]);
  2147. if (bridgeObj==NULL) {
  2148. Int i;
  2149. for (i=0; i<m_structuresInQueue-1; i++) {
  2150. m_structuresToRepair[i] = m_structuresToRepair[i+1];
  2151. }
  2152. m_structuresInQueue--;
  2153. }
  2154. }
  2155. if (m_structuresInQueue == 0) return;
  2156. // Got a bridge to repair.
  2157. Object *dozer = NULL;
  2158. Coord3D bridgePos = *bridgeObj->getPosition();
  2159. enum BodyDamageType bridgeState = bridgeObj->getBodyModule()->getDamageState();
  2160. if (m_repairDozer==INVALID_ID) {
  2161. m_dozerIsRepairing = false;
  2162. // Need a dozer.
  2163. if (m_dozerQueuedForRepair) {
  2164. return; // we're waiting for one.
  2165. }
  2166. dozer = findDozer(&bridgePos);
  2167. if (dozer) {
  2168. m_repairDozer = dozer->getID();
  2169. m_repairDozerOrigin = *dozer->getPosition();
  2170. dozer->getAI()->aiRepair(bridgeObj, CMD_FROM_AI);
  2171. DEBUG_LOG(("Telling dozer to repair\n"));
  2172. m_dozerIsRepairing = true;
  2173. return;
  2174. }
  2175. queueDozer();
  2176. m_dozerQueuedForRepair = true;
  2177. return;
  2178. }
  2179. dozer = TheGameLogic->findObjectByID(m_repairDozer);
  2180. if (dozer==NULL) {
  2181. m_repairDozer=INVALID_ID; // we got killed.
  2182. m_bridgeTimer=0;
  2183. return; // Just try to find a dozer next frame.
  2184. }
  2185. DozerAIInterface* dozerAI = dozer->getAI()->getDozerAIInterface();
  2186. if (dozerAI==NULL) {
  2187. DEBUG_CRASH(("Unexpected - dozer doesn't have dozer interface."));
  2188. return;
  2189. }
  2190. if (m_dozerIsRepairing) {
  2191. if (!dozerAI->isAnyTaskPending()) {
  2192. // should be done repairing.
  2193. if (bridgeState==BODY_PRISTINE) {
  2194. DEBUG_LOG(("Dozer finished repairing structure.\n"));
  2195. // we're done.
  2196. Int i;
  2197. for (i=0; i<m_structuresInQueue-1; i++) {
  2198. m_structuresToRepair[i] = m_structuresToRepair[i+1];
  2199. }
  2200. m_structuresInQueue--;
  2201. m_dozerIsRepairing = false;
  2202. if (m_structuresInQueue==0) {
  2203. // Go home.
  2204. Coord3D pos = m_baseCenter;
  2205. if (!m_baseCenterSet) {
  2206. pos = m_repairDozerOrigin;
  2207. }
  2208. AIUpdateInterface *ai=dozer->getAI();
  2209. TheAI->pathfinder()->adjustToPossibleDestination(dozer, ai->getLocomotorSet(), &pos);
  2210. dozer->getAI()->aiMoveToPosition(&pos, CMD_FROM_AI);
  2211. return;
  2212. }
  2213. }
  2214. } else {
  2215. // dozer should be working on the bridge.
  2216. return;
  2217. }
  2218. }
  2219. dozer->getAI()->aiRepair(bridgeObj, CMD_FROM_AI);
  2220. m_dozerIsRepairing = true;
  2221. DEBUG_LOG(("Telling dozer to repair\n"));
  2222. }
  2223. // ------------------------------------------------------------------------------------------------
  2224. /** Build a specific team. If priorityBuild, put at front of queue with priority set. */
  2225. // ------------------------------------------------------------------------------------------------
  2226. void AIPlayer::buildSpecificAITeam( TeamPrototype *teamProto, Bool priorityBuild)
  2227. {
  2228. //
  2229. // Create "Team in queue" based on team population
  2230. //
  2231. if (teamProto)
  2232. {
  2233. if (!m_player->getCanBuildUnits()) {
  2234. AsciiString teamStr = "Can't build team '";
  2235. teamStr.concat(teamProto->getName());
  2236. teamStr.concat("' because build units is disabled.");
  2237. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2238. return;
  2239. }
  2240. if (priorityBuild && teamProto->getIsSingleton()) {
  2241. Team *singletonTeam = TheTeamFactory->findTeam( teamProto->getName() );
  2242. if (singletonTeam && singletonTeam->hasAnyObjects()) {
  2243. AsciiString teamStr = "Unable to build singleton team '";
  2244. teamStr.concat("' because team already exists.");
  2245. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2246. return;
  2247. }
  2248. }
  2249. // Check & make sure we have factories.
  2250. Bool needMoney;
  2251. if (!isPossibleToBuildTeam(teamProto, false, needMoney)) {
  2252. if (needMoney) {
  2253. // Queue it up anyway.
  2254. AsciiString teamStr = "Note - queueing team '";
  2255. teamStr.concat(teamProto->getName());
  2256. teamStr.concat("' but there is enough money.");
  2257. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2258. } else {
  2259. // Tech tree doesn't work.
  2260. AsciiString teamStr = "Unable to build team '";
  2261. teamStr.concat(teamProto->getName());
  2262. if (needMoney) {
  2263. teamStr.concat("' - Not enough money.");
  2264. } else {
  2265. teamStr.concat("' because required factories/tech don't exist.");
  2266. }
  2267. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2268. return;
  2269. }
  2270. }
  2271. const TCreateUnitsInfo *unitInfo = &teamProto->getTemplateInfo()->m_unitsInfo[0];
  2272. WorkOrder *orders = NULL;
  2273. Int i;
  2274. // Queue up optional units.
  2275. for( i=0; i<teamProto->getTemplateInfo()->m_numUnitsInfo; i++ )
  2276. {
  2277. const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
  2278. if (thing)
  2279. {
  2280. int count = unitInfo[i].maxUnits-unitInfo[i].minUnits;
  2281. if (count>0) {
  2282. WorkOrder *order = newInstance(WorkOrder);
  2283. order->m_thing = thing;
  2284. order->m_factoryID = INVALID_ID;
  2285. order->m_numRequired = count;
  2286. // prepend to head of list
  2287. order->m_next = orders;
  2288. orders = order;
  2289. }
  2290. }
  2291. }
  2292. // Queue up required units.
  2293. for( i=0; i<teamProto->getTemplateInfo()->m_numUnitsInfo; i++ )
  2294. {
  2295. const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
  2296. if (thing)
  2297. {
  2298. int count = unitInfo[i].minUnits;
  2299. WorkOrder *order = newInstance(WorkOrder);
  2300. order->m_thing = thing;
  2301. order->m_factoryID = INVALID_ID;
  2302. order->m_numRequired = count;
  2303. order->m_required = true;
  2304. // prepend to head of list
  2305. order->m_next = orders;
  2306. orders = order;
  2307. }
  2308. }
  2309. if (orders)
  2310. {
  2311. /* We have something to build. */
  2312. TeamInQueue *team = newInstance(TeamInQueue);
  2313. if (priorityBuild) {
  2314. // Put in front of queue.
  2315. prependTo_TeamBuildQueue(team);
  2316. team->m_priorityBuild = true;
  2317. } else {
  2318. // Put in back of queue.
  2319. reverse_TeamBuildQueue();
  2320. prependTo_TeamBuildQueue(team);
  2321. reverse_TeamBuildQueue();
  2322. team->m_priorityBuild = false;
  2323. }
  2324. team->m_workOrders = orders;
  2325. team->m_frameStarted = TheGameLogic->getFrame();
  2326. // create inactive team to place members into as they are built
  2327. // when team is complete, the team is activated
  2328. team->m_team = TheTeamFactory->createInactiveTeam( teamProto->getName() );
  2329. AsciiString teamName = teamProto->getName();
  2330. teamName.concat(" - starting team build.");
  2331. TheScriptEngine->AppendDebugMessage(teamName, false);
  2332. m_teamDelay = 0;
  2333. if (team->m_team->getPrototype()->getTemplateInfo()->m_executeActions) {
  2334. const Script *script = TheScriptEngine->findScriptByName(team->m_team->getPrototype()->getTemplateInfo()->m_productionCondition);
  2335. if (script && script->getAction()) {
  2336. TheScriptEngine->friend_executeAction(script->getAction(), team->m_team);
  2337. }
  2338. }
  2339. } else {
  2340. if (TheGlobalData->m_debugAI) {
  2341. AsciiString teamName = teamProto->getName();
  2342. teamName.concat(" - contains 0 buildable units.");
  2343. TheScriptEngine->AppendDebugMessage(teamName, false);
  2344. }
  2345. }
  2346. }
  2347. }
  2348. // ------------------------------------------------------------------------------------------------
  2349. /** Recruit a specific team, within the specific radius of the home position. */
  2350. // ------------------------------------------------------------------------------------------------
  2351. void AIPlayer::recruitSpecificAITeam(TeamPrototype *teamProto, Real recruitRadius)
  2352. {
  2353. if (recruitRadius < 1) recruitRadius = 99999.0f;
  2354. //
  2355. // Create "Team in queue" based on team population
  2356. //
  2357. if (teamProto)
  2358. {
  2359. if (teamProto->getIsSingleton()) {
  2360. Team *singletonTeam = TheTeamFactory->findTeam( teamProto->getName() );
  2361. if (singletonTeam && singletonTeam->hasAnyObjects()) {
  2362. AsciiString teamStr = "Unable to recruit singleton team '";
  2363. teamStr.concat("' because team already exists.");
  2364. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2365. return;
  2366. }
  2367. }
  2368. if (!teamProto->getTemplateInfo()->m_hasHomeLocation && !isSkirmishAI())
  2369. {
  2370. AsciiString teamStr = "Error : team '";
  2371. teamStr.concat(teamProto->getName());
  2372. teamStr.concat("' has no Home Position (or Origin).");
  2373. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2374. }
  2375. // create inactive team to place members into as they are built
  2376. // when team is complete, the team is activated
  2377. Team *theTeam = TheTeamFactory->createInactiveTeam( teamProto->getName() );
  2378. AsciiString teamName = teamProto->getName();
  2379. teamName.concat(" - Recruiting.");
  2380. TheScriptEngine->AppendDebugMessage(teamName, false);
  2381. const TCreateUnitsInfo *unitInfo = &teamProto->getTemplateInfo()->m_unitsInfo[0];
  2382. // WorkOrder *orders = NULL;
  2383. Int i;
  2384. Int unitsRecruited = 0;
  2385. // Recruit.
  2386. for( i=0; i<teamProto->getTemplateInfo()->m_numUnitsInfo; i++ )
  2387. {
  2388. const ThingTemplate *thing = TheThingFactory->findTemplate( unitInfo[i].unitThingName );
  2389. if (thing)
  2390. {
  2391. int count = unitInfo[i].maxUnits;
  2392. while (count>0) {
  2393. Object *unit = theTeam->tryToRecruit(thing, &teamProto->getTemplateInfo()->m_homeLocation, recruitRadius);
  2394. if (unit)
  2395. {
  2396. unitsRecruited++;
  2397. AsciiString teamStr = "Team '";
  2398. teamStr.concat(theTeam->getPrototype()->getName());
  2399. teamStr.concat("' recruits ");
  2400. teamStr.concat(thing->getName());
  2401. teamStr.concat(" from team '");
  2402. teamStr.concat(unit->getTeam()->getPrototype()->getName());
  2403. teamStr.concat("'");
  2404. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2405. unit->setTeam(theTeam);
  2406. AIUpdateInterface *ai = unit->getAIUpdateInterface();
  2407. if (ai)
  2408. {
  2409. #if defined(_DEBUG) || defined(_INTERNAL)
  2410. Coord3D pos = *unit->getPosition();
  2411. Coord3D to = teamProto->getTemplateInfo()->m_homeLocation;
  2412. DEBUG_LOG(("Moving unit from %f,%f to %f,%f\n", pos.x, pos.y , to.x, to.y ));
  2413. #endif
  2414. ai->aiMoveToPosition( &teamProto->getTemplateInfo()->m_homeLocation, CMD_FROM_AI);
  2415. }
  2416. } else {
  2417. break;
  2418. }
  2419. count--;
  2420. }
  2421. }
  2422. }
  2423. if (unitsRecruited>0)
  2424. {
  2425. /* We have something to build. */
  2426. TeamInQueue *team = newInstance(TeamInQueue);
  2427. // Put in front of queue.
  2428. prependTo_TeamReadyQueue(team);
  2429. team->m_priorityBuild = false;
  2430. team->m_workOrders = NULL;
  2431. team->m_frameStarted = TheGameLogic->getFrame();
  2432. team->m_team = theTeam;
  2433. AsciiString teamName = teamProto->getName();
  2434. teamName.concat(" - Finished recruiting.");
  2435. TheScriptEngine->AppendDebugMessage(teamName, false);
  2436. } else {
  2437. //disband.
  2438. if (!theTeam->getPrototype()->getIsSingleton()) {
  2439. theTeam->deleteInstance();
  2440. theTeam = NULL;
  2441. }
  2442. AsciiString teamName = teamProto->getName();
  2443. teamName.concat(" - Recruited 0 units, disbanding.");
  2444. TheScriptEngine->AppendDebugMessage(teamName, false);
  2445. }
  2446. }
  2447. }
  2448. // ------------------------------------------------------------------------------------------------
  2449. /** Train our teams. */
  2450. // ------------------------------------------------------------------------------------------------
  2451. void AIPlayer::processTeamBuilding( void )
  2452. {
  2453. // select a new team
  2454. if (selectTeamToBuild()) {
  2455. queueUnits();
  2456. }
  2457. }
  2458. // ------------------------------------------------------------------------------------------------
  2459. // ------------------------------------------------------------------------------------------------
  2460. void AIPlayer::queueUnits( void )
  2461. {
  2462. queueSupplyTruck();
  2463. // For each member of the current team to build, try to find a faction building to build it.
  2464. //
  2465. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  2466. {
  2467. TeamInQueue *team = iter.cur();
  2468. for( WorkOrder *order = team->m_workOrders; order; order = order->m_next )
  2469. {
  2470. // check if there is a unit on the map that we can steal (recruit) instead of building
  2471. // @todo: Should this try to alter the home location of the recruiting area to
  2472. // the center of the team, or to the home area of this player?
  2473. Coord3D home = team->m_team->getPrototype()->getTemplateInfo()->m_homeLocation;
  2474. Bool hasHome = false;
  2475. if (team->m_team->getPrototype()->getTemplateInfo()->m_hasHomeLocation) {
  2476. hasHome = true;
  2477. } else {
  2478. hasHome = getBaseCenter(&home);
  2479. }
  2480. while (order->isWaitingToBuild()) {
  2481. Object *unit = team->m_team->tryToRecruit(order->m_thing, &home, TheAI->getAiData()->m_maxRecruitDistance);
  2482. if (unit)
  2483. {
  2484. order->m_numCompleted++;
  2485. AsciiString teamStr = "Team '";
  2486. teamStr.concat(team->m_team->getPrototype()->getName());
  2487. teamStr.concat("' recruits ");
  2488. teamStr.concat(order->m_thing->getName());
  2489. teamStr.concat(" from team '");
  2490. teamStr.concat(unit->getTeam()->getPrototype()->getName());
  2491. teamStr.concat("'");
  2492. TheScriptEngine->AppendDebugMessage(teamStr, false);
  2493. unit->setTeam(team->m_team);
  2494. AIUpdateInterface *ai = unit->getAIUpdateInterface();
  2495. if (hasHome) {
  2496. ai->aiMoveToPosition( &home, CMD_FROM_AI);
  2497. } else {
  2498. ai->aiIdle(CMD_FROM_AI); // stop, you've been recruited.
  2499. }
  2500. } else {
  2501. break;
  2502. }
  2503. }
  2504. if (order->isWaitingToBuild())
  2505. {
  2506. // start the creation of a new unit
  2507. startTraining( order, team->m_priorityBuild, team->m_team->getName());
  2508. }
  2509. else
  2510. {
  2511. // we are under construction, verify our factory still exists
  2512. order->validateFactory(m_player);
  2513. }
  2514. }
  2515. }
  2516. }
  2517. //----------------------------------------------------------------------------------------------------------
  2518. /**
  2519. * See if it's time to build another base building.
  2520. */
  2521. void AIPlayer::doBaseBuilding( void )
  2522. {
  2523. if (m_player->getCanBuildBase()) {
  2524. // See if we are ready to start trying a structure.
  2525. if (!m_readyToBuildStructure) {
  2526. m_structureTimer--;
  2527. if (m_structureTimer<=0) {
  2528. m_readyToBuildStructure = true;
  2529. m_buildDelay = 0;
  2530. }
  2531. }
  2532. // This timer is to keep from banging on the logic each frame. If something interesting
  2533. // happens, like a building is added or a unit finished, the timers are shortcut.
  2534. m_buildDelay--;
  2535. if (m_buildDelay<1) {
  2536. if (m_readyToBuildStructure) {
  2537. processBaseBuilding();
  2538. }
  2539. if (m_buildDelay<1) { // processBaseBuilding may reset m_buildDelay.
  2540. m_buildDelay = 2*LOGICFRAMES_PER_SECOND; // check again in 2 seconds.
  2541. }
  2542. // Note that this timer gets shortcut when a building is completed.
  2543. }
  2544. }
  2545. }
  2546. //----------------------------------------------------------------------------------------------------------
  2547. /**
  2548. * See if any ready teams have finished moving to the rally point.
  2549. */
  2550. void AIPlayer::checkReadyTeams( void )
  2551. {
  2552. // See if any ready teams are gathered at their rally point
  2553. { // needed to scope iter. silly ms c++.
  2554. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamReadyQueue(); !iter.done(); iter.advance())
  2555. {
  2556. TeamInQueue *team = iter.cur();
  2557. // If 60 seconds passed, start anyway.
  2558. Bool timeExpired = team->m_frameStarted+60*LOGICFRAMES_PER_SECOND < TheGameLogic->getFrame();
  2559. Bool allIdle=TRUE;
  2560. Bool anyIdle = FALSE;
  2561. if (team->m_reinforcement) {
  2562. Object *obj = TheGameLogic->findObjectByID(team->m_reinforcementID);
  2563. if (obj && obj->getAIUpdateInterface()) {
  2564. allIdle = obj->getAIUpdateInterface()->isIdle();
  2565. anyIdle = allIdle;
  2566. }
  2567. } else {
  2568. allIdle = team->m_team->isIdle();
  2569. for (DLINK_ITERATOR<Object> iter = team->m_team->iterate_TeamMemberList(); !iter.done(); iter.advance()) {
  2570. Object *obj = iter.cur();
  2571. if (obj->getAI() && obj->getAI()->isIdle()) {
  2572. anyIdle = true;
  2573. }
  2574. }
  2575. }
  2576. if (anyIdle && team->m_team->getPrototype()->getTemplateInfo()->m_executeActions) {
  2577. const Script *script = TheScriptEngine->findScriptByName(team->m_team->getPrototype()->getTemplateInfo()->m_productionCondition);
  2578. if (script && script->getAction()) {
  2579. // we have a start action. So don't wait for allIdle as the team may be guarding.
  2580. allIdle = true;
  2581. }
  2582. }
  2583. if (timeExpired) allIdle = true;
  2584. if (allIdle) {
  2585. if (!team->m_sentToStartLocation) {
  2586. team->m_sentToStartLocation = true;
  2587. /*
  2588. if (team->m_team->getPrototype()->getTemplateInfo()->m_hasHomeLocation &&
  2589. !team->m_reinforcement) {
  2590. AIGroup* theGroup = TheAI->createGroup();
  2591. if (theGroup) {
  2592. team->m_team->getTeamAsAIGroup(theGroup);
  2593. Coord3D destination = team->m_team->getPrototype()->getTemplateInfo()->m_homeLocation;
  2594. theGroup->groupTightenToPosition( &destination, false, CMD_FROM_AI );
  2595. team->m_frameStarted = TheGameLogic->getFrame();
  2596. continue;
  2597. }
  2598. }
  2599. */
  2600. }
  2601. // Start the team up.
  2602. removeFrom_TeamReadyQueue(team);
  2603. if (team->m_reinforcement) {
  2604. Object *obj = TheGameLogic->findObjectByID(team->m_reinforcementID);
  2605. if (obj&&obj->getAIUpdateInterface()) {
  2606. obj->getAIUpdateInterface()->joinTeam();
  2607. }
  2608. } else {
  2609. // mark our completed team as "active" - this will invoke any OnCreate scripts, etc.
  2610. team->m_team->setActive();
  2611. if (isSkirmishAI()) {
  2612. TheScriptEngine->clearTeamFlags();
  2613. }
  2614. if (TheGlobalData->m_debugAI) {
  2615. AsciiString teamName = team->m_team->getPrototype()->getName();
  2616. teamName.concat(" - team activated.");
  2617. TheScriptEngine->AppendDebugMessage(teamName, false);
  2618. }
  2619. }
  2620. team->deleteInstance();
  2621. iter = iterate_TeamReadyQueue();
  2622. }
  2623. }
  2624. }
  2625. }
  2626. //----------------------------------------------------------------------------------------------------------
  2627. /**
  2628. * See if any queued teams have finished building, or have run out of time.
  2629. */
  2630. void AIPlayer::checkQueuedTeams( void )
  2631. {
  2632. // See if any teams are expired.
  2633. { // needed to scope iter. silly ms c++.
  2634. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  2635. {
  2636. TeamInQueue *team = iter.cur();
  2637. if (team && team->isBuildTimeExpired()) {
  2638. if (team->isMinimumBuilt())
  2639. {
  2640. if (team->areBuildsComplete()) {
  2641. // Move to ready queue
  2642. removeFrom_TeamBuildQueue(team);
  2643. prependTo_TeamReadyQueue(team);
  2644. } else {
  2645. continue;
  2646. }
  2647. } else {
  2648. // Disband.
  2649. removeFrom_TeamBuildQueue(team);
  2650. team->disband();
  2651. team->deleteInstance();
  2652. if (isSkirmishAI()) {
  2653. TheScriptEngine->clearTeamFlags();
  2654. }
  2655. }
  2656. iter = iterate_TeamBuildQueue();
  2657. }
  2658. }
  2659. }
  2660. // See if any teams are ready.
  2661. { // needed to scope iter. silly ms c++.
  2662. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  2663. {
  2664. TeamInQueue *team = iter.cur();
  2665. if (team && team->isAllBuilt())
  2666. {
  2667. // Move to ready queue
  2668. removeFrom_TeamBuildQueue(team);
  2669. prependTo_TeamReadyQueue(team);
  2670. iter = iterate_TeamBuildQueue();
  2671. continue;
  2672. }
  2673. Bool anyIdle = false;
  2674. for (DLINK_ITERATOR<Object> iter = team->m_team->iterate_TeamMemberList(); !iter.done(); iter.advance()) {
  2675. Object *obj = iter.cur();
  2676. if (obj && obj->getAI() && obj->getAI()->isIdle()) {
  2677. anyIdle = true;
  2678. }
  2679. }
  2680. if (anyIdle) {
  2681. if (team->m_team->getPrototype()->getTemplateInfo()->m_executeActions) {
  2682. const Script *script = TheScriptEngine->findScriptByName(team->m_team->getPrototype()->getTemplateInfo()->m_productionCondition);
  2683. if (script) {
  2684. TheScriptEngine->friend_executeAction(script->getAction(), team->m_team);
  2685. }
  2686. }
  2687. }
  2688. }
  2689. }
  2690. }
  2691. //----------------------------------------------------------------------------------------------------------
  2692. /**
  2693. * See if it is time to start another ai team building.
  2694. */
  2695. void AIPlayer::doTeamBuilding( void )
  2696. {
  2697. // See if any teams are expired.
  2698. if (m_player->getCanBuildUnits()) {
  2699. // See if we are ready to start trying a team.
  2700. if (!m_readyToBuildTeam) {
  2701. m_teamTimer--;
  2702. if (m_teamTimer<=0) {
  2703. m_readyToBuildTeam = true;
  2704. m_teamDelay = 0;
  2705. }
  2706. }
  2707. // This timer is to keep from banging on the logic each frame. If something interesting
  2708. // happens, like a building is added or a unit finished, the timers are shortcut.
  2709. m_teamDelay--;
  2710. if (m_teamDelay<1) {
  2711. queueUnits(); // update the queues.
  2712. if (m_readyToBuildTeam) {
  2713. processTeamBuilding();
  2714. }
  2715. m_teamDelay = 5*LOGICFRAMES_PER_SECOND; // check again in 5 seconds.
  2716. // Note that this timer gets shortcut when a unit or building is completed.
  2717. }
  2718. }
  2719. }
  2720. //----------------------------------------------------------------------------------------------------------
  2721. /**
  2722. * See if it is time to start another upgrade or skill building.
  2723. */
  2724. void AIPlayer::doUpgradesAndSkills( void )
  2725. {
  2726. if (TheGameLogic->getFrame() < 2) {
  2727. // can't do updates on the first few frames
  2728. return;
  2729. }
  2730. Bool checkScience = m_player->getSciencePurchasePoints()>0;
  2731. if (!checkScience) {
  2732. return;
  2733. }
  2734. const AISideInfo *sideInfo = TheAI->getAiData()->m_sideInfo;
  2735. while (sideInfo) {
  2736. if (sideInfo->m_side == m_player->getSide()) {
  2737. break;
  2738. }
  2739. sideInfo = sideInfo->m_next;
  2740. }
  2741. if (sideInfo == NULL) return;
  2742. if (m_skillsetSelector == INVALID_SKILLSET_SELECTION) {
  2743. Int limit = 0;
  2744. // Pick randomly among the skillsets that have skills.
  2745. // Designers sometimes only define skillset 1 & 2, or some such. jba.
  2746. if (sideInfo->m_skillSet2.m_numSkills>0) {
  2747. limit = 1;
  2748. if (sideInfo->m_skillSet3.m_numSkills>0) {
  2749. limit = 2;
  2750. if (sideInfo->m_skillSet4.m_numSkills>0) {
  2751. limit = 3;
  2752. if (sideInfo->m_skillSet5.m_numSkills>0) {
  2753. limit = 4;
  2754. }
  2755. }
  2756. }
  2757. }
  2758. if (isSkirmishAI()) {
  2759. m_skillsetSelector = GameLogicRandomValue(0, limit);
  2760. } else {
  2761. m_skillsetSelector = 0; // Non-skirmish default to 0. jba.
  2762. }
  2763. }
  2764. // SKILLS
  2765. if (m_player->getSciencePurchasePoints()>0) {
  2766. const TSkillSet *skillset;
  2767. switch(m_skillsetSelector) {
  2768. default:
  2769. case 0: skillset = &sideInfo->m_skillSet1; break;
  2770. case 1: skillset = &sideInfo->m_skillSet2; break;
  2771. case 2: skillset = &sideInfo->m_skillSet3; break;
  2772. case 3: skillset = &sideInfo->m_skillSet4; break;
  2773. case 4: skillset = &sideInfo->m_skillSet5; break;
  2774. }
  2775. Int i;
  2776. for (i=0; i<skillset->m_numSkills; i++) {
  2777. ScienceType science = skillset->m_skills[i];
  2778. if (m_player->isCapableOfPurchasingScience(science)) {
  2779. if (m_player->attemptToPurchaseScience(science)) {
  2780. AsciiString msg = TheNameKeyGenerator->keyToName(m_player->getPlayerNameKey());
  2781. msg.concat(" purchases from SkillSet");
  2782. msg.concat('1'+m_skillsetSelector);
  2783. msg.concat(' ');
  2784. msg.concat(TheScienceStore->getInternalNameForScience(science));
  2785. msg.concat(".");
  2786. TheScriptEngine->AppendDebugMessage( msg, false);
  2787. }
  2788. }
  2789. }
  2790. }
  2791. }
  2792. //----------------------------------------------------------------------------------------------------------
  2793. /**
  2794. * Perform computer-controlled player AI
  2795. */
  2796. //DECLARE_PERF_TIMER(AIPlayer_update)
  2797. void AIPlayer::update( void )
  2798. {
  2799. //USE_PERF_TIMER(AIPlayer_update)
  2800. doBaseBuilding(); // See if it's time to build another building.
  2801. checkReadyTeams(); // See if any teams are ready to start.
  2802. checkQueuedTeams(); // See if any teams are complete.
  2803. doTeamBuilding(); // See if it's time to start another team.
  2804. doUpgradesAndSkills(); // See if it's time to build an upgrade or buy a skill.
  2805. updateBridgeRepair(); // Handle any bridge repairs.
  2806. }
  2807. //----------------------------------------------------------------------------------------------------------
  2808. /**
  2809. * Find any things that build stuff & add them to the build list. Then build any initially built
  2810. * buildings.
  2811. */
  2812. void AIPlayer::newMap( void )
  2813. {
  2814. BuildListInfo *info = m_player->getBuildList();
  2815. // Add any factories placed to the build list.
  2816. Object *obj;
  2817. for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
  2818. {
  2819. Player *owner = obj->getControllingPlayer();
  2820. if (owner==m_player) {
  2821. // See if it's a factory.
  2822. ProductionUpdateInterface *pu = obj->getProductionUpdateInterface();
  2823. // If it doesn't produce, continue.
  2824. if (!pu) continue;
  2825. m_player->addToBuildList(obj);
  2826. }
  2827. }
  2828. computeCenterAndRadiusOfBase(&m_baseCenter, &m_baseRadius);
  2829. // Build any with the initially built flag.
  2830. for( /* nothing */; info; info = info->getNext() )
  2831. {
  2832. AsciiString name = info->getTemplateName();
  2833. if (name.isEmpty()) continue;
  2834. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
  2835. if (!bldgPlan) {
  2836. DEBUG_LOG(("*** ERROR - Build list building '%s' doesn't exist.\n", name.str()));
  2837. continue;
  2838. }
  2839. if (info->isInitiallyBuilt()) {
  2840. buildStructureNow(bldgPlan, info);
  2841. } else {
  2842. info->incrementNumRebuilds(); // the initial build in the normal build list consumes a rebuild, so add one.
  2843. }
  2844. }
  2845. }
  2846. // ------------------------------------------------------------------------------------------------
  2847. /** Find the center of the base and the radius of buildings. */
  2848. // ------------------------------------------------------------------------------------------------
  2849. void AIPlayer::computeCenterAndRadiusOfBase(Coord3D *center, Real *radius)
  2850. {
  2851. //
  2852. BuildListInfo *info;
  2853. Coord2D totalPos;
  2854. totalPos.x = 0;
  2855. totalPos.y = 0;
  2856. Int numBldg=0;
  2857. for( info = m_player->getBuildList(); info; info = info->getNext() )
  2858. {
  2859. AsciiString name = info->getTemplateName();
  2860. if (name.isEmpty()) continue;
  2861. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
  2862. if (!bldgPlan) {
  2863. continue;
  2864. }
  2865. Coord3D pos = *info->getLocation();
  2866. totalPos.x += pos.x;
  2867. totalPos.y += pos.y;
  2868. numBldg++;
  2869. }
  2870. if (numBldg>0) {
  2871. totalPos.x /= numBldg;
  2872. totalPos.y /= numBldg;
  2873. }
  2874. m_baseCenterSet = numBldg>0;
  2875. center->x = totalPos.x;
  2876. center->y = totalPos.y;
  2877. Real maxRadSqr = 0;
  2878. //
  2879. for( info = m_player->getBuildList(); info; info = info->getNext() )
  2880. {
  2881. AsciiString name = info->getTemplateName();
  2882. if (name.isEmpty()) continue;
  2883. const ThingTemplate *bldgPlan = TheThingFactory->findTemplate( name );
  2884. if (!bldgPlan) {
  2885. continue;
  2886. }
  2887. Coord3D pos = *info->getLocation();
  2888. Real dx = pos.x-center->x;
  2889. Real dy = pos.y-center->y;
  2890. if (dx<0) dx = -dx;
  2891. if (dy<0) dy = -dy;
  2892. Real bldgRadius = bldgPlan->getTemplateGeometryInfo().getBoundingCircleRadius()*0.4f;
  2893. dx += bldgRadius;
  2894. dy += bldgRadius;
  2895. Real radSqr = dx*dx+dy*dy;
  2896. if (radSqr>maxRadSqr) maxRadSqr=radSqr;
  2897. }
  2898. *radius = sqrt(maxRadSqr);
  2899. }
  2900. //----------------------------------------------------------------------------------------------------------
  2901. /**
  2902. * Checks to see if we're building a dozer.
  2903. */
  2904. Bool AIPlayer::dozerInQueue( void )
  2905. {
  2906. { // needed to scope iter. silly ms c++.
  2907. for ( DLINK_ITERATOR<TeamInQueue> iter = iterate_TeamBuildQueue(); !iter.done(); iter.advance())
  2908. {
  2909. TeamInQueue *team = iter.cur();
  2910. if (team && team->includesADozer() )
  2911. {
  2912. return true; // dozer is building already.
  2913. }
  2914. }
  2915. }
  2916. return false;
  2917. }
  2918. //----------------------------------------------------------------------------------------------------------
  2919. /**
  2920. * Queues up a dozer.
  2921. */
  2922. void AIPlayer::queueDozer( void )
  2923. {
  2924. if (dozerInQueue()) return;
  2925. // Find a factory that can build a dozer.
  2926. Bool canBuildUnits = m_player->getCanBuildUnits();
  2927. // If we need a dozer, turn on unit building for a moment.
  2928. m_player->setCanBuildUnits(true);
  2929. const ThingTemplate *tTemplate = TheThingFactory->firstTemplate();
  2930. while (tTemplate) {
  2931. if (tTemplate->isKindOf(KINDOF_DOZER)) {
  2932. Object *factory = findFactory(tTemplate, true);
  2933. if (factory) {
  2934. // we can build one.
  2935. WorkOrder *order = newInstance(WorkOrder);
  2936. order->m_thing = tTemplate;
  2937. order->m_factoryID = INVALID_ID;
  2938. order->m_numRequired = 1;
  2939. order->m_required = true;
  2940. order->m_isResourceGatherer = FALSE;
  2941. // prepend to head of list
  2942. order->m_next = NULL;
  2943. TeamInQueue *team = newInstance(TeamInQueue);
  2944. // Put in front of queue.
  2945. prependTo_TeamBuildQueue(team);
  2946. team->m_priorityBuild = true;
  2947. team->m_workOrders = order;
  2948. team->m_frameStarted = TheGameLogic->getFrame();
  2949. // Stick it on the default team
  2950. team->m_team = m_player->getDefaultTeam();
  2951. AsciiString teamName = "DOZER - building one at the ";
  2952. teamName.concat(factory->getTemplate()->getName());
  2953. TheScriptEngine->AppendDebugMessage(teamName, false);
  2954. m_teamDelay = 0;
  2955. startTraining( order, team->m_priorityBuild, team->m_team->getName());
  2956. break;
  2957. }
  2958. }
  2959. tTemplate = tTemplate->friend_getNextTemplate();
  2960. }
  2961. // restore canbuildunits.
  2962. m_player->setCanBuildUnits(canBuildUnits);
  2963. }
  2964. //-------------------------------------------------------------------------------------------------
  2965. /** Difficulty level for this player */
  2966. //-------------------------------------------------------------------------------------------------
  2967. enum GameDifficulty AIPlayer::getAIDifficulty(void) const
  2968. {
  2969. return m_difficulty;
  2970. }
  2971. //----------------------------------------------------------------------------------------------------------
  2972. /**
  2973. * Finds a dozer that isn't building or collecting resources.
  2974. */
  2975. Object * AIPlayer::findDozer( const Coord3D *pos )
  2976. {
  2977. // Add any factories placed to the build list.
  2978. Object *obj;
  2979. Object *dozer = NULL;
  2980. Bool needDozer = true;
  2981. Object *closestDozer=NULL;
  2982. Real closestDistSqr = 0;
  2983. for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
  2984. {
  2985. Player *owner = obj->getControllingPlayer();
  2986. if (owner==m_player) {
  2987. // See if it's a dozer.
  2988. if (obj->isKindOf(KINDOF_DOZER)) {
  2989. AIUpdateInterface *ai = obj->getAIUpdateInterface();
  2990. if (ai==NULL) {
  2991. continue;
  2992. }
  2993. DozerAIInterface* dozerAI = ai->getDozerAIInterface();
  2994. if (dozerAI) {
  2995. // Since workers can be dozers, hmmm....
  2996. SupplyTruckAIInterface* supplyTruckAI = ai->getSupplyTruckAIInterface();
  2997. if( !dozerAI->isAnyTaskPending() && supplyTruckAI ) {
  2998. // If it is gathering supplies, don't steal it.
  2999. if (supplyTruckAI->isCurrentlyFerryingSupplies() || supplyTruckAI->isForcedIntoWantingState())
  3000. {
  3001. continue;
  3002. }
  3003. }
  3004. if (obj->getID() == m_repairDozer) {
  3005. continue; // don't steal the repair dozer.
  3006. }
  3007. needDozer = false; // dozer exists, may be busy.
  3008. if (dozerAI->isTaskPending(DOZER_TASK_BUILD)) {
  3009. continue; // already building.
  3010. }
  3011. if (!dozerAI->isAnyTaskPending()) {
  3012. dozer = obj; // prefer an idle dozer
  3013. }
  3014. if (dozer==NULL) {
  3015. dozer = obj; // but we'll take one doing stuff.
  3016. }
  3017. if (dozer && !dozerAI->isAnyTaskPending()) {
  3018. // Got a good one, track closest.
  3019. Real distSqr;
  3020. Real dx, dy;
  3021. dx = pos->x - dozer->getPosition()->x;
  3022. dy = pos->y - dozer->getPosition()->y;
  3023. distSqr = dx*dx+dy*dy;
  3024. if (closestDozer == NULL) {
  3025. closestDozer = dozer;
  3026. closestDistSqr = distSqr;
  3027. } else if (distSqr < closestDistSqr) {
  3028. closestDozer = dozer;
  3029. closestDistSqr = distSqr;
  3030. }
  3031. }
  3032. }
  3033. }
  3034. }
  3035. }
  3036. if (needDozer) {
  3037. queueDozer();
  3038. }
  3039. if (closestDozer) return closestDozer;
  3040. return dozer;
  3041. }
  3042. // ------------------------------------------------------------------------------------------------
  3043. /** CRC */
  3044. // ------------------------------------------------------------------------------------------------
  3045. void AIPlayer::crc( Xfer *xfer )
  3046. {
  3047. } // end crc
  3048. // ------------------------------------------------------------------------------------------------
  3049. /** Xfer method
  3050. * Version Info:
  3051. * 1: Initial version
  3052. * 2: added m_teamSeconds delay.
  3053. * 3: Added m_curWarehouseID.
  3054. * 1: Reset back to 1 with major save file changes.
  3055. */
  3056. // ------------------------------------------------------------------------------------------------
  3057. void AIPlayer::xfer( Xfer *xfer )
  3058. {
  3059. // version
  3060. XferVersion currentVersion = 1;
  3061. XferVersion version = currentVersion;
  3062. xfer->xferVersion( &version, currentVersion );
  3063. // team build queue count
  3064. UnsignedShort teamBuildQueueCount = 0;
  3065. for( DLINK_ITERATOR< TeamInQueue > teamInQueueIt = iterate_TeamBuildQueue();
  3066. teamInQueueIt.done() == FALSE;
  3067. teamInQueueIt.advance() )
  3068. teamBuildQueueCount++;
  3069. xfer->xferUnsignedShort( &teamBuildQueueCount );
  3070. // team build queue data
  3071. TeamInQueue *teamInQueue;
  3072. if( xfer->getXferMode() == XFER_SAVE )
  3073. {
  3074. for( DLINK_ITERATOR< TeamInQueue > teamInQueueIt = iterate_TeamBuildQueue();
  3075. teamInQueueIt.done() == FALSE;
  3076. teamInQueueIt.advance() )
  3077. {
  3078. // get element data
  3079. teamInQueue = teamInQueueIt.cur();
  3080. // xfer it
  3081. xfer->xferSnapshot( teamInQueue );
  3082. } // end for, iterate team build queue
  3083. } // end if, save
  3084. else
  3085. {
  3086. // sanity, the list must be empty
  3087. if( getFirstItemIn_TeamBuildQueue() != NULL )
  3088. {
  3089. DEBUG_CRASH(( "AIPlayer::xfer - TeamBuildQueue head is not NULL, you should delete it or something before loading a new list\n" ));
  3090. throw SC_INVALID_DATA;
  3091. } // end if
  3092. // ready all data
  3093. for( UnsignedShort i = 0; i < teamBuildQueueCount; ++i )
  3094. {
  3095. // allocate new team in queue instance
  3096. teamInQueue = newInstance(TeamInQueue);
  3097. // attach to end of list
  3098. prependTo_TeamBuildQueue( teamInQueue );
  3099. // xfer data
  3100. xfer->xferSnapshot( teamInQueue );
  3101. } // end for, i
  3102. // the list was loaded in reverse order, reverse the list so it's in the same order as before
  3103. reverse_TeamBuildQueue();
  3104. } // end else, load
  3105. // team ready queue count
  3106. UnsignedShort teamReadyQueueCount = 0;
  3107. for( DLINK_ITERATOR< TeamInQueue > teamReadyQueueIt = iterate_TeamReadyQueue();
  3108. teamReadyQueueIt.done() == FALSE;
  3109. teamReadyQueueIt.advance() )
  3110. teamReadyQueueCount++;
  3111. xfer->xferUnsignedShort( &teamReadyQueueCount );
  3112. // team Ready queue data
  3113. TeamInQueue *teamReadyQueue;
  3114. if( xfer->getXferMode() == XFER_SAVE )
  3115. {
  3116. for( DLINK_ITERATOR< TeamInQueue > teamReadyQueueIt = iterate_TeamReadyQueue();
  3117. teamReadyQueueIt.done() == FALSE;
  3118. teamReadyQueueIt.advance() )
  3119. {
  3120. // get element
  3121. teamReadyQueue = teamReadyQueueIt.cur();
  3122. // xfer data
  3123. xfer->xferSnapshot( teamReadyQueue );
  3124. } // end for, iterate team ready queue
  3125. } // end if, save
  3126. else
  3127. {
  3128. // sanity, the list must be empty
  3129. if( getFirstItemIn_TeamReadyQueue() != NULL )
  3130. {
  3131. DEBUG_CRASH(( "AIPlayer::xfer - TeamReadyQueue head is not NULL, you should delete it or something before loading a new list\n" ));
  3132. throw SC_INVALID_DATA;
  3133. } // end if
  3134. // read all data
  3135. for( UnsignedShort i = 0; i < teamReadyQueueCount; ++i )
  3136. {
  3137. // allocate new team in queue instance
  3138. teamInQueue = newInstance(TeamInQueue);
  3139. // attach to end of list
  3140. prependTo_TeamReadyQueue( teamInQueue );
  3141. // xfer data
  3142. xfer->xferSnapshot( teamInQueue );
  3143. } // end for, i
  3144. // reverse the list since it was loaded in reverse order due to the prepend
  3145. reverse_TeamReadyQueue();
  3146. } // end else, load
  3147. // xfer player index ... this is really just for sanity
  3148. PlayerIndex playerIndex = m_player->getPlayerIndex();
  3149. xfer->xferUser( &playerIndex, sizeof( PlayerIndex ) );
  3150. if( playerIndex != m_player->getPlayerIndex() )
  3151. {
  3152. DEBUG_CRASH(( "AIPlayer::xfer - player index mismatch\n" ));
  3153. throw SC_INVALID_DATA;
  3154. } // end if
  3155. // xfer the rest of the ai player data (it's pretty straight forward)
  3156. xfer->xferBool( &m_readyToBuildTeam );
  3157. xfer->xferBool( &m_readyToBuildStructure );
  3158. xfer->xferInt( &m_teamTimer );
  3159. xfer->xferInt( &m_structureTimer );
  3160. xfer->xferInt( &m_buildDelay );
  3161. xfer->xferInt( &m_teamDelay );
  3162. xfer->xferInt(&m_teamSeconds);
  3163. xfer->xferObjectID(&m_curWarehouseID);
  3164. xfer->xferInt( &m_frameLastBuildingBuilt );
  3165. xfer->xferUser( &m_difficulty, sizeof( GameDifficulty ) );
  3166. xfer->xferInt( &m_skillsetSelector );
  3167. xfer->xferCoord3D( &m_baseCenter );
  3168. xfer->xferBool( &m_baseCenterSet );
  3169. xfer->xferReal( &m_baseRadius );
  3170. xfer->xferUser( m_structuresToRepair, sizeof( ObjectID ) * MAX_STRUCTURES_TO_REPAIR );
  3171. xfer->xferObjectID( &m_repairDozer );
  3172. xfer->xferInt( &m_structuresInQueue );
  3173. xfer->xferBool( &m_dozerQueuedForRepair );
  3174. xfer->xferBool( &m_dozerIsRepairing );
  3175. xfer->xferInt( &m_bridgeTimer );
  3176. } // end xfer
  3177. // ------------------------------------------------------------------------------------------------
  3178. /** Load post process */
  3179. // ------------------------------------------------------------------------------------------------
  3180. void AIPlayer::loadPostProcess( void )
  3181. {
  3182. } // end loadPostProcess
  3183. // ------------------------------------------------------------------------------------------------
  3184. // ------------------------------------------------------------------------------------------------
  3185. TeamInQueue::~TeamInQueue()
  3186. {
  3187. WorkOrder *order, *next;
  3188. for( order = m_workOrders; order; order = next )
  3189. {
  3190. next = order->m_next;
  3191. order->deleteInstance();
  3192. }
  3193. // If we have a team, activate it. If it is empty, Team.cpp will remove empty active teams.
  3194. if (m_team) m_team->setActive();
  3195. m_workOrders = NULL;
  3196. }
  3197. // ------------------------------------------------------------------------------------------------
  3198. // ------------------------------------------------------------------------------------------------
  3199. Bool TeamInQueue::isAllBuilt()
  3200. {
  3201. WorkOrder *order;
  3202. Bool stillBuilding = false;
  3203. for( order = m_workOrders; order; order = order->m_next )
  3204. {
  3205. if (order->m_numRequired>order->m_numCompleted)
  3206. {
  3207. stillBuilding = true;
  3208. }
  3209. }
  3210. return !stillBuilding;
  3211. }
  3212. // ------------------------------------------------------------------------------------------------
  3213. // ------------------------------------------------------------------------------------------------
  3214. Bool TeamInQueue::isBuildTimeExpired()
  3215. {
  3216. if (m_team->getPrototype()->getTemplateInfo()->m_initialIdleFrames<1) {
  3217. return false; // Unlimited time.
  3218. }
  3219. if (TheGameLogic->getFrame() > m_frameStarted + m_team->getPrototype()->getTemplateInfo()->m_initialIdleFrames) {
  3220. return true;
  3221. }
  3222. return false;
  3223. }
  3224. // ------------------------------------------------------------------------------------------------
  3225. // ------------------------------------------------------------------------------------------------
  3226. Bool TeamInQueue::isMinimumBuilt()
  3227. {
  3228. WorkOrder *order;
  3229. for( order = m_workOrders; order; order = order->m_next )
  3230. {
  3231. Int count = order->m_numCompleted;
  3232. if (order->m_factoryID != INVALID_ID) {
  3233. count++; // we have one building.
  3234. }
  3235. if (order->m_numRequired>count)
  3236. {
  3237. if (order->m_required) {
  3238. return false; // required units not built.
  3239. }
  3240. }
  3241. }
  3242. return true;
  3243. }
  3244. // ------------------------------------------------------------------------------------------------
  3245. // ------------------------------------------------------------------------------------------------
  3246. Bool TeamInQueue::includesADozer()
  3247. {
  3248. WorkOrder *order;
  3249. for( order = m_workOrders; order; order = order->m_next )
  3250. {
  3251. // GLA dozers (workers) are also resource gatherers, so make sure it isn't a gatherer. jba.
  3252. if (order->m_thing->isKindOf(KINDOF_DOZER) && !order->m_isResourceGatherer) {
  3253. return true;
  3254. }
  3255. }
  3256. return false;
  3257. }
  3258. // ------------------------------------------------------------------------------------------------
  3259. // ------------------------------------------------------------------------------------------------
  3260. Bool TeamInQueue::areBuildsComplete()
  3261. {
  3262. WorkOrder *order;
  3263. for( order = m_workOrders; order; order = order->m_next )
  3264. {
  3265. if (order->m_factoryID != INVALID_ID) {
  3266. return false; // we have one building.
  3267. }
  3268. }
  3269. return true;
  3270. }
  3271. // ------------------------------------------------------------------------------------------------
  3272. // ------------------------------------------------------------------------------------------------
  3273. void TeamInQueue::disband()
  3274. {
  3275. Team *newTeam = m_team->getPrototype()->getControllingPlayer()->getDefaultTeam();
  3276. AsciiString teamName = m_team->getPrototype()->getName();
  3277. teamName.concat(" - team disbanded, build time expired.");
  3278. TheScriptEngine->AppendDebugMessage(teamName, false);
  3279. if (m_team != newTeam) {
  3280. m_team->transferUnitsTo(newTeam);
  3281. if (!m_team->getPrototype()->getIsSingleton()) {
  3282. m_team->deleteInstance();
  3283. }
  3284. m_team = NULL;
  3285. }
  3286. }
  3287. // ------------------------------------------------------------------------------------------------
  3288. /** CRC */
  3289. // ------------------------------------------------------------------------------------------------
  3290. void TeamInQueue::crc( Xfer *xfer )
  3291. {
  3292. } // end crc
  3293. // ------------------------------------------------------------------------------------------------
  3294. /** Xfer method
  3295. * Version Info:
  3296. * 1: Initial version */
  3297. // ------------------------------------------------------------------------------------------------
  3298. void TeamInQueue::xfer( Xfer *xfer )
  3299. {
  3300. // version
  3301. XferVersion currentVersion = 1;
  3302. XferVersion version = currentVersion;;
  3303. xfer->xferVersion( &version, currentVersion );
  3304. // xfer work order count
  3305. UnsignedShort workOrderCount = 0;
  3306. WorkOrder *workOrder;
  3307. for( workOrder = m_workOrders; workOrder; workOrder = workOrder->m_next )
  3308. workOrderCount++;
  3309. xfer->xferUnsignedShort( &workOrderCount );
  3310. // xfer work orders
  3311. if( xfer->getXferMode() == XFER_SAVE )
  3312. {
  3313. // xfer each work order
  3314. for( workOrder = m_workOrders; workOrder; workOrder = workOrder->m_next )
  3315. {
  3316. // xfer work order data
  3317. xfer->xferSnapshot( workOrder );
  3318. } // end for
  3319. } // end if, save
  3320. else
  3321. {
  3322. // sanity
  3323. if( m_workOrders != NULL )
  3324. {
  3325. DEBUG_CRASH(( "TeamInQueue::xfer - m_workOrders should be NULL but isn't. Perhaps you should blow it away before loading\n" ));
  3326. throw SC_INVALID_DATA;
  3327. } // end if
  3328. // load all work orders
  3329. for( UnsignedShort i = 0; i < workOrderCount; ++i )
  3330. {
  3331. // allocate new work order
  3332. workOrder = newInstance(WorkOrder);
  3333. // attach to list at the end
  3334. workOrder->m_next = NULL;
  3335. if( m_workOrders == NULL )
  3336. m_workOrders = workOrder;
  3337. else
  3338. {
  3339. WorkOrder *last = m_workOrders;
  3340. while( last->m_next != NULL )
  3341. last = last->m_next;
  3342. last->m_next = workOrder;
  3343. } // end else
  3344. // load work order data
  3345. xfer->xferSnapshot( workOrder );
  3346. } // end for, i
  3347. } // end else, load
  3348. // xfer the rest of the team in queue data
  3349. xfer->xferBool( &m_priorityBuild );
  3350. TeamID teamID = m_team ? m_team->getID() : TEAM_ID_INVALID;
  3351. xfer->xferUser( &teamID, sizeof( TeamID ) );
  3352. if( xfer->getXferMode() == XFER_LOAD )
  3353. m_team = TheTeamFactory->findTeamByID( teamID );
  3354. xfer->xferInt( &m_frameStarted );
  3355. xfer->xferBool( &m_sentToStartLocation );
  3356. xfer->xferBool( &m_stopQueueing );
  3357. xfer->xferBool( &m_reinforcement );
  3358. xfer->xferObjectID( &m_reinforcementID );
  3359. } // end xfer
  3360. // ------------------------------------------------------------------------------------------------
  3361. /** Load post process */
  3362. // ------------------------------------------------------------------------------------------------
  3363. void TeamInQueue::loadPostProcess( void )
  3364. {
  3365. } // end loadPostProcess
  3366. ///////////////////////////////////////////////////////////////////////////////////////////////////
  3367. ///////////////////////////////////////////////////////////////////////////////////////////////////
  3368. ///////////////////////////////////////////////////////////////////////////////////////////////////
  3369. // ------------------------------------------------------------------------------------------------
  3370. // ------------------------------------------------------------------------------------------------
  3371. WorkOrder::~WorkOrder()
  3372. {
  3373. } // end WorkOrder
  3374. // ------------------------------------------------------------------------------------------------
  3375. /** Verify factoryID still refers to an active object */
  3376. // ------------------------------------------------------------------------------------------------
  3377. void WorkOrder::validateFactory( Player *thisPlayer )
  3378. {
  3379. if (m_factoryID == INVALID_ID)
  3380. return;
  3381. Object *factory = TheGameLogic->findObjectByID( m_factoryID );
  3382. if ( factory == NULL) {
  3383. m_factoryID = INVALID_ID;
  3384. return;
  3385. }
  3386. if (factory->getControllingPlayer()!=thisPlayer) {
  3387. m_factoryID = INVALID_ID;
  3388. }
  3389. } // end validateFactory
  3390. // ------------------------------------------------------------------------------------------------
  3391. /** CRC */
  3392. // ------------------------------------------------------------------------------------------------
  3393. void WorkOrder::crc( Xfer *xfer )
  3394. {
  3395. } // end crc
  3396. // ------------------------------------------------------------------------------------------------
  3397. /** Xfer method
  3398. * Version Info:
  3399. * 1: Initial version */
  3400. // ------------------------------------------------------------------------------------------------
  3401. void WorkOrder::xfer( Xfer *xfer )
  3402. {
  3403. // version
  3404. XferVersion currentVersion = 1;
  3405. XferVersion version = currentVersion;
  3406. xfer->xferVersion( &version, currentVersion );
  3407. // thing template
  3408. AsciiString thingTemplateName = m_thing ? m_thing->getName() : AsciiString::TheEmptyString;
  3409. xfer->xferAsciiString( &thingTemplateName );
  3410. if( xfer->getXferMode() == XFER_LOAD )
  3411. m_thing = TheThingFactory->findTemplate( thingTemplateName );
  3412. // factory id
  3413. xfer->xferObjectID( &m_factoryID );
  3414. // num completed
  3415. xfer->xferInt( &m_numCompleted );
  3416. // num required
  3417. xfer->xferInt( &m_numRequired );
  3418. // is required
  3419. xfer->xferBool( &m_required );
  3420. // is resource gatherer
  3421. xfer->xferBool( &m_isResourceGatherer );
  3422. } // end xfer
  3423. // ------------------------------------------------------------------------------------------------
  3424. /** Load post process */
  3425. // ------------------------------------------------------------------------------------------------
  3426. void WorkOrder::loadPostProcess( void )
  3427. {
  3428. } // end loadPostProcess
  3429. //----------------------------------------------------------------------------------------------------------
  3430. /**
  3431. * Get the bounds for a player's structure.
  3432. */
  3433. void AIPlayer::getPlayerStructureBounds( Region2D *bounds, Int playerNdx, Bool conservative )
  3434. {
  3435. Player::PlayerTeamList::const_iterator it;
  3436. Bool firstObject = true;
  3437. Bool firstStructure = true;
  3438. bounds->hi.x = bounds->lo.x = bounds->hi.y = bounds->lo.y = 0;
  3439. Region2D objBounds;
  3440. objBounds.hi.x = objBounds.lo.x = objBounds.hi.y = objBounds.lo.y = 0;
  3441. Player* pPlayer = ThePlayerList->getNthPlayer(playerNdx);
  3442. if (pPlayer == NULL)
  3443. return;
  3444. for (it = pPlayer->getPlayerTeams()->begin(); it != pPlayer->getPlayerTeams()->end(); ++it)
  3445. {
  3446. for (DLINK_ITERATOR<Team> iter = (*it)->iterate_TeamInstanceList(); !iter.done(); iter.advance())
  3447. {
  3448. Team *team = iter.cur();
  3449. if (!team)
  3450. continue;
  3451. for (DLINK_ITERATOR<Object> iter = team->iterate_TeamMemberList(); !iter.done(); iter.advance())
  3452. {
  3453. Object *pObj = iter.cur();
  3454. if (!pObj)
  3455. continue;
  3456. if( pObj->isKindOf(KINDOF_STRUCTURE) )
  3457. {
  3458. if( conservative && pObj->isKindOf( KINDOF_CONSERVATIVE_BUILDING ) )
  3459. {
  3460. //Kris - Aug 14, 2003
  3461. //Conservative buildings (captured tech buildings, sneak attack buildings are rejected).
  3462. //This was added so base boundaries aren't potentially most of the map. Because sneak
  3463. //attack uses an inverse cost calculation by avoiding defended areas, we need to keep
  3464. //things close to the enemy base.
  3465. continue;
  3466. }
  3467. Coord3D pos = *pObj->getPosition();
  3468. if (firstObject) {
  3469. objBounds.lo.x = objBounds.hi.x = pos.x;
  3470. objBounds.lo.y = objBounds.hi.y = pos.y;
  3471. firstObject = false;
  3472. }
  3473. else
  3474. {
  3475. if (objBounds.lo.x>pos.x) objBounds.lo.x = pos.x;
  3476. if (objBounds.lo.y>pos.y) objBounds.lo.y = pos.y;
  3477. if (objBounds.hi.x<pos.x) objBounds.hi.x = pos.x;
  3478. if (objBounds.hi.y<pos.y) objBounds.hi.y = pos.y;
  3479. }
  3480. if (firstStructure)
  3481. {
  3482. bounds->lo.x = bounds->hi.x = pos.x;
  3483. bounds->lo.y = bounds->hi.y = pos.y;
  3484. firstStructure = false;
  3485. }
  3486. else
  3487. {
  3488. if (bounds->lo.x>pos.x) bounds->lo.x = pos.x;
  3489. if (bounds->lo.y>pos.y) bounds->lo.y = pos.y;
  3490. if (bounds->hi.x<pos.x) bounds->hi.x = pos.x;
  3491. if (bounds->hi.y<pos.y) bounds->hi.y = pos.y;
  3492. }
  3493. }
  3494. }
  3495. }
  3496. }
  3497. if (!firstStructure) {
  3498. // Player had no structures, so use unit bounds.
  3499. *bounds = objBounds;
  3500. }
  3501. }