AcademyStats.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  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. // FILE: AcademyStats.cpp //////////////////////////////////////////////////////
  24. //-----------------------------------------------------------------------------
  25. //
  26. // Electronic Arts Los Angeles
  27. //
  28. // Confidential Information
  29. // Copyright (C) 2003 - All Rights Reserved
  30. //
  31. //-----------------------------------------------------------------------------
  32. //
  33. // Project: RTS3
  34. //
  35. // File name: AcademyStats.cpp
  36. //
  37. // Created: Kris Morness, July 2003
  38. //
  39. // Desc: Keeps track of various statistics in order to provide advice to
  40. // the player about how to improve playing.
  41. //
  42. //-----------------------------------------------------------------------------
  43. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  44. #include "Common/AcademyStats.h"
  45. #include "Common/Energy.h"
  46. #include "Common/Player.h"
  47. #include "Common/PlayerList.h"
  48. #include "Common/PlayerTemplate.h"
  49. #include "Common/SpecialPower.h"
  50. #include "Common/ThingTemplate.h"
  51. #include "Common/Xfer.h"
  52. #include "GameClient/ControlBar.h"
  53. #include "GameClient/GameText.h"
  54. #include "GameLogic/GameLogic.h"
  55. #include "GameLogic/Object.h"
  56. #include "GameLogic/Module/ContainModule.h"
  57. #ifdef _INTERNAL
  58. // for occasional debugging...
  59. //#pragma optimize("", off)
  60. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  61. #endif
  62. const char *TheAcademyClassificationTypeNames[] =
  63. {
  64. "ACT_NONE",
  65. "ACT_UPGRADE_RADAR",
  66. "ACT_SUPERPOWER",
  67. NULL
  68. };
  69. #define FRAMES_BETWEEN_UPDATES 30
  70. //------------------------------------------------------------------------------------------------
  71. AcademyStats::AcademyStats()
  72. {
  73. }
  74. //------------------------------------------------------------------------------------------------
  75. void findDozerCommandSet( Object *object, void *userData )
  76. {
  77. const CommandSet *dozerCommandSet = (const CommandSet*)userData;
  78. if( dozerCommandSet )
  79. {
  80. return;
  81. }
  82. if( object && object->isKindOf( KINDOF_DOZER ) )
  83. {
  84. dozerCommandSet = TheControlBar->findCommandSet( object->getCommandSetString() );
  85. }
  86. }
  87. //------------------------------------------------------------------------------------------------
  88. void AcademyStats::init( const Player *player )
  89. {
  90. if( !TheGameLogic )
  91. {
  92. return; // GUIEdit crashes on this, so bail
  93. }
  94. m_nextUpdateFrame = TheGameLogic->getFrame() + FRAMES_BETWEEN_UPDATES;
  95. m_firstUpdate = TRUE;
  96. m_unknownSide = FALSE;
  97. //Init the player and cache some important information.
  98. m_player = player;
  99. const PlayerTemplate *plyrTemplate = player->getPlayerTemplate();
  100. if( !plyrTemplate ||
  101. plyrTemplate->getBaseSide().compareNoCase( "USA" ) &&
  102. plyrTemplate->getBaseSide().compareNoCase( "China" ) &&
  103. plyrTemplate->getBaseSide().compareNoCase( "GLA" ) )
  104. {
  105. //Admittedly, this is a massive violation of data driven design. Shame on me!
  106. //Unknown side... don't provide ANY advice. Simplicity reasons.
  107. m_unknownSide = TRUE;
  108. }
  109. //Find the command set for our dozer... so we can extract information about things
  110. //we can build.
  111. m_dozerCommandSet = NULL;
  112. player->iterateObjects( findDozerCommandSet, (void*)m_dozerCommandSet );
  113. m_commandCenterTemplate = NULL;
  114. m_supplyCenterTemplate = NULL;
  115. if( m_dozerCommandSet )
  116. {
  117. for( int i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  118. {
  119. const CommandButton *button = m_dozerCommandSet->getCommandButton( i );
  120. if( button )
  121. {
  122. const ThingTemplate *thing = button->getThingTemplate();
  123. if( thing )
  124. {
  125. if( thing->isKindOf( KINDOF_FS_SUPPLY_CENTER ) )
  126. {
  127. m_supplyCenterTemplate = thing;
  128. m_supplyCenterCost = m_supplyCenterTemplate->calcCostToBuild( player );
  129. }
  130. else if( thing->isKindOf( KINDOF_COMMANDCENTER ) )
  131. {
  132. //Wow, this is bogus... but we need this template pointer in order to fire special powers from the
  133. //shortcut... ugh.
  134. m_commandCenterTemplate = thing;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. //+-----------------------+
  141. //| Tier 1 (Basic advice) |
  142. //+-----------------------+
  143. //1) Did player build at least one of each structure type available?
  144. //CUT!!!
  145. //2) Did player run out of money before building a supply center?
  146. m_spentCashBeforeBuildingSupplyCenter = FALSE;
  147. m_supplyCentersBuilt = 0;
  148. if( !m_supplyCenterTemplate )
  149. {
  150. m_supplyCenterCost = 1000;
  151. }
  152. //3) Did player build radar (if applicable)?
  153. m_researchedRadar = FALSE;
  154. //4) Did player build any dozers/workers?
  155. m_peonsBuilt = 0;
  156. //5) Did player ever capture a structure?
  157. m_structuresCaptured = 0;
  158. //6) Did player spend any generals points?
  159. m_generalsPointsSpent = 0;
  160. //7) Did player ever use a generals power or superweapon?
  161. m_specialPowersUsed = 0;
  162. //8) Did player garrison any structures?
  163. m_structuresGarrisoned = 0;
  164. //9) How idle was the player in building military units?
  165. m_idleBuildingUnitsMaxFrames = 0;
  166. m_lastUnitBuiltFrame = 0;
  167. //10) Did player drag select units?
  168. m_dragSelectUnits = 0;
  169. //11) Did player upgrade anything?
  170. m_upgradesPurchased = 0;
  171. //12) Was player out of power for more than 10 minutes?
  172. m_powerOutMaxFrames = 0;
  173. m_oldestPowerOutFrame = 0;
  174. m_hadPowerLastCheck = 0;
  175. //13) Extra gatherers built?
  176. m_gatherersBuilt = 0;
  177. //14) Heros built?
  178. m_heroesBuilt = 0;
  179. //+------------------------------+
  180. //| Tier 2 (Intermediate advice) |
  181. //+------------------------------+
  182. //15) Selected a strategy center battle plan?
  183. m_hadAStrategyCenter = FALSE;
  184. m_choseAStrategyForCenter = FALSE;
  185. //16) Placed units inside tunnel network?
  186. m_hadATunnelNetwork = FALSE;
  187. m_unitsEnteredTunnelNetwork = 0;
  188. //17) Player used control groups?
  189. m_controlGroupsUsed = 0;
  190. //18) Built secondary income building?
  191. m_secondaryIncomeUnitsBuilt = 0;
  192. //19) Cleared out garrisoned buildings?
  193. m_clearedGarrisonedBuildings = 0;
  194. //20) Did the Player pick up salvage (as GLA)?
  195. m_salvageCollected = 0;
  196. //21) Did the player ever use the "Guard" ability?
  197. m_guardAbilityUsedCount = 0;
  198. //22) Did the player build more than one Supply Center (that is, did he expand out)?
  199. //Uses m_supplyCentersBuilt!
  200. //+--------------------------+
  201. //| Tier 3 (Advanced advice) |
  202. //+--------------------------+
  203. //25) Did the player use the new alternate interface in the options?
  204. //Uses TheGlobalData->m_useAlternateMouse
  205. //26) Player did not use the new "double click location attack move/guard"
  206. m_doubleClickAttackMoveOrdersGiven = 0;
  207. //27) Built barracks within 5 minutes?
  208. m_builtBarracksWithinFiveMinutes = FALSE;
  209. //28) Built war factory within 10 minutes?
  210. m_builtWarFactoryWithinTenMinutes = FALSE;
  211. //29) Built tech structure within 15 minutes?
  212. m_builtTechStructureWithinFifteenMinutes = FALSE;
  213. //30) No income for 2 minutes?
  214. m_lastIncomeFrame = 0;
  215. m_maxFramesBetweenIncome = 0;
  216. //31) Did the Player ever use Dozers/Workers to clear out traps/mines/booby traps?
  217. m_mines = 0;
  218. m_minesCleared = 0;
  219. //32) Captured any sniped vehicles?
  220. m_vehiclesRecovered = 0;
  221. m_vehiclesSniped = 0; //Neutral team stat
  222. //33) Did the player ever build a "disguisable" unit and never used the disguise ability?
  223. m_disguisableVehiclesBuilt = 0;
  224. m_vehiclesDisguised = 0;
  225. //34) Did the player never build a "stealth" upgrade?
  226. //CUT!!!
  227. //35) Did the player ever create a "Firestorm" with his MiGs or Inferno Cannons?
  228. m_firestormsCreated = 0;
  229. }
  230. //------------------------------------------------------------------------------------------------
  231. static void updateAcademyStats( Object *obj, void *userData )
  232. {
  233. AcademyStats *academy = (AcademyStats*)userData;
  234. if( !obj || !academy )
  235. {
  236. return;
  237. }
  238. if( academy->isFirstUpdate() )
  239. {
  240. academy->recordProduction( obj, NULL );
  241. }
  242. }
  243. //------------------------------------------------------------------------------------------------
  244. void AcademyStats::update()
  245. {
  246. if( m_unknownSide )
  247. {
  248. return;
  249. }
  250. UnsignedInt now = TheGameLogic->getFrame();
  251. if( m_nextUpdateFrame >= now )
  252. {
  253. m_nextUpdateFrame = now + FRAMES_BETWEEN_UPDATES;
  254. m_player->iterateObjects( updateAcademyStats, (void*)this );
  255. //2) Did player run out of money before building a supply center?
  256. if( !m_supplyCentersBuilt && !m_spentCashBeforeBuildingSupplyCenter )
  257. {
  258. const Money *money = m_player->getMoney();
  259. if( money )
  260. {
  261. UnsignedInt amount = money->countMoney();
  262. if( amount < m_supplyCenterCost )
  263. {
  264. m_spentCashBeforeBuildingSupplyCenter = TRUE;
  265. }
  266. }
  267. }
  268. //12) Was player out of power for more than 10 minutes?
  269. const Energy *power = m_player->getEnergy();
  270. if( power )
  271. {
  272. Bool hasPower = power->hasSufficientPower();
  273. if( hasPower != m_hadPowerLastCheck )
  274. {
  275. if( !hasPower )
  276. {
  277. //We just lost power
  278. m_oldestPowerOutFrame = now;
  279. }
  280. else
  281. {
  282. //We just gained power... see how long we were without.
  283. UnsignedInt frames = now - m_oldestPowerOutFrame;
  284. if( frames > m_powerOutMaxFrames )
  285. {
  286. m_powerOutMaxFrames = frames;
  287. }
  288. }
  289. m_hadPowerLastCheck = hasPower;
  290. }
  291. }
  292. if( isFirstUpdate() )
  293. {
  294. setFirstUpdate( FALSE );
  295. }
  296. }
  297. }
  298. //------------------------------------------------------------------------------------------------
  299. void AcademyStats::recordProduction( const Object *obj, const Object *constructer )
  300. {
  301. UnsignedInt now = TheGameLogic->getFrame();
  302. //2) Did player run out of money before building a supply center?
  303. //22) Did the player build more than one Supply Center (that is, did he expand out)?
  304. if( obj->isKindOf( KINDOF_FS_SUPPLY_CENTER ) )
  305. {
  306. m_supplyCentersBuilt++;
  307. }
  308. //4) Did player build any dozers/workers?
  309. if( obj->isKindOf( KINDOF_DOZER ) )
  310. {
  311. m_peonsBuilt++;
  312. }
  313. //9) How idle was the player in building military units?
  314. if( obj->isKindOf( KINDOF_INFANTRY ) || obj->isKindOf( KINDOF_VEHICLE ) )
  315. {
  316. if( !obj->isKindOf( KINDOF_DOZER ) && !obj->isKindOf( KINDOF_HARVESTER ) )
  317. {
  318. //How long has it been since we built our last unit?
  319. UnsignedInt idleFrames = now - m_lastUnitBuiltFrame;
  320. //If it was longer than our max time... then record it.
  321. if( idleFrames > m_idleBuildingUnitsMaxFrames )
  322. {
  323. m_idleBuildingUnitsMaxFrames = idleFrames;
  324. }
  325. //Record the frame we built our unit.
  326. m_lastUnitBuiltFrame = now;
  327. }
  328. }
  329. //13) Extra gatherers built?
  330. if( obj->isKindOf( KINDOF_HARVESTER ) )
  331. {
  332. m_gatherersBuilt++;
  333. }
  334. //14) Heros built?
  335. if( obj->isKindOf( KINDOF_HERO ) )
  336. {
  337. m_heroesBuilt++;
  338. }
  339. //15) Selected a strategy center battle plan?
  340. if( obj->isKindOf( KINDOF_FS_STRATEGY_CENTER ) )
  341. {
  342. m_hadAStrategyCenter = TRUE;
  343. }
  344. //16) Placed units inside tunnel network?
  345. if( obj->getContain() && obj->getContain()->isTunnelContain() )
  346. {
  347. m_hadATunnelNetwork = TRUE;
  348. }
  349. //18) Built secondary income building?
  350. if( obj->isKindOf( KINDOF_MONEY_HACKER ) || obj->isKindOf( KINDOF_FS_BLACK_MARKET ) || obj->isKindOf( KINDOF_FS_SUPPLY_DROPZONE ) )
  351. {
  352. m_secondaryIncomeUnitsBuilt++;
  353. }
  354. //27) Built tech structure within 15 minutes?
  355. if( obj->isKindOf( KINDOF_FS_BARRACKS ) )
  356. {
  357. if( TheGameLogic->getFrame() <= 300 * LOGICFRAMES_PER_SECOND )
  358. {
  359. m_builtBarracksWithinFiveMinutes = TRUE;
  360. }
  361. }
  362. //28) Built tech structure within 15 minutes?
  363. if( obj->isKindOf( KINDOF_FS_WARFACTORY ) )
  364. {
  365. if( TheGameLogic->getFrame() <= 600 * LOGICFRAMES_PER_SECOND )
  366. {
  367. m_builtWarFactoryWithinTenMinutes = TRUE;
  368. }
  369. }
  370. //29) Built tech structure within 15 minutes?
  371. if( obj->isKindOf( KINDOF_FS_ADVANCED_TECH ) )
  372. {
  373. if( TheGameLogic->getFrame() <= 900 * LOGICFRAMES_PER_SECOND )
  374. {
  375. m_builtTechStructureWithinFifteenMinutes = TRUE;
  376. }
  377. }
  378. //33) Did the player ever build a "disguisable" unit and never used the disguise ability?
  379. if( obj->isKindOf( KINDOF_DISGUISER ) )
  380. {
  381. m_disguisableVehiclesBuilt++;
  382. }
  383. }
  384. //------------------------------------------------------------------------------------------------
  385. void AcademyStats::recordUpgrade( const UpgradeTemplate *upgrade, Bool granted )
  386. {
  387. //3) Did player build radar (if applicable)?
  388. if( upgrade->getAcademyClassificationType() == ACT_UPGRADE_RADAR )
  389. {
  390. m_researchedRadar = TRUE;
  391. }
  392. //11) Did player upgrade anything?
  393. if( !granted )
  394. {
  395. m_upgradesPurchased++;
  396. }
  397. }
  398. //------------------------------------------------------------------------------------------------
  399. void AcademyStats::recordSpecialPowerUsed( const SpecialPowerTemplate *spTemplate )
  400. {
  401. if( spTemplate->getAcademyClassificationType() == ACT_SUPERPOWER )
  402. {
  403. m_specialPowersUsed++;
  404. }
  405. }
  406. //------------------------------------------------------------------------------------------------
  407. void AcademyStats::recordIncome()
  408. {
  409. UnsignedInt now = TheGameLogic->getFrame();
  410. UnsignedInt delta = m_lastIncomeFrame - now;
  411. if( delta > m_maxFramesBetweenIncome )
  412. {
  413. m_maxFramesBetweenIncome = delta;
  414. }
  415. m_lastIncomeFrame = now;
  416. }
  417. //------------------------------------------------------------------------------------------------
  418. void AcademyStats::evaluateTier1Advice( AcademyAdviceInfo *info, Int numAvailableTips )
  419. {
  420. UnsignedInt maxAdviceTips = MAX_ADVICE_TIPS;
  421. //-allAdvice feature
  422. //if( !TheGlobalData->m_allAdvice )
  423. //{
  424. // maxAdviceTips = 1;
  425. //}
  426. UnsignedInt now = TheGameLogic->getFrame();
  427. //numAvailableTips is used to determine if we are going to randomly choose a tip
  428. //or determine if a tip is available (if -1)
  429. Bool choosing = numAvailableTips != -1;
  430. Int availableTips = 0;
  431. //1) Did player build at least one of each structure type available?
  432. //CUT!!!
  433. //2) Did player run out of money before building a supply center?
  434. if( m_spentCashBeforeBuildingSupplyCenter )
  435. {
  436. availableTips++;
  437. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  438. Int limit = maxAdviceTips - info->numTips;
  439. if( choosing && rand < limit )
  440. {
  441. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildSupplyCenterEarlier" ) );
  442. info->numTips++;
  443. }
  444. numAvailableTips--;
  445. }
  446. //3) Did player build radar (if applicable)?
  447. if( !m_researchedRadar )
  448. {
  449. availableTips++;
  450. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  451. Int limit = maxAdviceTips - info->numTips;
  452. if( choosing && rand < limit )
  453. {
  454. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:TryBuildingRadar" ) );
  455. info->numTips++;
  456. }
  457. numAvailableTips--;
  458. }
  459. //4) Did player build any dozers/workers?
  460. if( m_peonsBuilt < 2 )
  461. {
  462. availableTips++;
  463. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  464. Int limit = maxAdviceTips - info->numTips;
  465. if( choosing && rand < limit )
  466. {
  467. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildMorePeons" ) );
  468. info->numTips++;
  469. }
  470. numAvailableTips--;
  471. }
  472. //5) Did player ever capture a structure?
  473. if( !m_structuresCaptured )
  474. {
  475. availableTips++;
  476. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  477. Int limit = maxAdviceTips - info->numTips;
  478. if( choosing && rand < limit )
  479. {
  480. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:TryCapturingStructures" ) );
  481. info->numTips++;
  482. }
  483. numAvailableTips--;
  484. }
  485. //6) Did player spend any generals points?
  486. if( !m_generalsPointsSpent )
  487. {
  488. availableTips++;
  489. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  490. Int limit = maxAdviceTips - info->numTips;
  491. if( choosing && rand < limit )
  492. {
  493. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:SpendGeneralsPoints" ) );
  494. info->numTips++;
  495. }
  496. numAvailableTips--;
  497. }
  498. //7) Did player ever use a generals power or superweapon?
  499. if( !m_specialPowersUsed )
  500. {
  501. availableTips++;
  502. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  503. Int limit = maxAdviceTips - info->numTips;
  504. if( choosing && rand < limit )
  505. {
  506. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:TryUsingSuperweapons" ) );
  507. info->numTips++;
  508. }
  509. numAvailableTips--;
  510. }
  511. //8) Did player garrison any structures?
  512. if( !m_structuresGarrisoned )
  513. {
  514. availableTips++;
  515. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  516. Int limit = maxAdviceTips - info->numTips;
  517. if( choosing && rand < limit )
  518. {
  519. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:TryGarrisoningAStructure" ) );
  520. info->numTips++;
  521. }
  522. numAvailableTips--;
  523. }
  524. //9) How idle was the player in building military units?
  525. UnsignedInt idleFrames = now - m_lastUnitBuiltFrame;
  526. if( idleFrames > m_idleBuildingUnitsMaxFrames )
  527. {
  528. m_idleBuildingUnitsMaxFrames = idleFrames;
  529. }
  530. if( m_idleBuildingUnitsMaxFrames > 300 * LOGICFRAMES_PER_SECOND || !m_lastUnitBuiltFrame )
  531. {
  532. availableTips++;
  533. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  534. Int limit = maxAdviceTips - info->numTips;
  535. if( choosing && rand < limit )
  536. {
  537. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:IdleBuildingUnits" ) );
  538. info->numTips++;
  539. }
  540. numAvailableTips--;
  541. }
  542. //10) Did player drag select units?
  543. if( !m_dragSelectUnits )
  544. {
  545. availableTips++;
  546. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  547. Int limit = maxAdviceTips - info->numTips;
  548. if( choosing && rand < limit )
  549. {
  550. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:TryDragSelectingUnits" ) );
  551. info->numTips++;
  552. }
  553. numAvailableTips--;
  554. }
  555. //11) Did player upgrade anything?
  556. if( !m_upgradesPurchased )
  557. {
  558. availableTips++;
  559. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  560. Int limit = maxAdviceTips - info->numTips;
  561. if( choosing && rand < limit )
  562. {
  563. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:ResearchUpgrades" ) );
  564. info->numTips++;
  565. }
  566. numAvailableTips--;
  567. }
  568. //12) Was player out of power for more than 10 minutes?
  569. if( !m_hadPowerLastCheck )
  570. {
  571. UnsignedInt frames = now - m_oldestPowerOutFrame;
  572. if( frames > m_powerOutMaxFrames )
  573. {
  574. m_powerOutMaxFrames = frames;
  575. }
  576. }
  577. if( m_powerOutMaxFrames > 600 * LOGICFRAMES_PER_SECOND )
  578. {
  579. availableTips++;
  580. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  581. Int limit = maxAdviceTips - info->numTips;
  582. if( choosing && rand < limit )
  583. {
  584. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:RanOutOfPower" ) );
  585. info->numTips++;
  586. }
  587. numAvailableTips--;
  588. }
  589. //13) Extra gatherers built?
  590. if( !m_gatherersBuilt )
  591. {
  592. //Don't count free ones that come with supply centers!
  593. availableTips++;
  594. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  595. Int limit = maxAdviceTips - info->numTips;
  596. if( choosing && rand < limit )
  597. {
  598. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildMoreGatherers" ) );
  599. info->numTips++;
  600. }
  601. numAvailableTips--;
  602. }
  603. //14) Heros built?
  604. if( !m_heroesBuilt )
  605. {
  606. availableTips++;
  607. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  608. Int limit = maxAdviceTips - info->numTips;
  609. if( choosing && rand < limit )
  610. {
  611. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildAHero" ) );
  612. info->numTips++;
  613. }
  614. numAvailableTips--;
  615. }
  616. if( !choosing && availableTips > 0 )
  617. {
  618. //We're done counting available tips, so now that we know the
  619. //number, we can randomly choose.
  620. evaluateTier1Advice( info, availableTips );
  621. }
  622. }
  623. //------------------------------------------------------------------------------------------------
  624. void AcademyStats::evaluateTier2Advice( AcademyAdviceInfo *info, Int numAvailableTips )
  625. {
  626. UnsignedInt maxAdviceTips = MAX_ADVICE_TIPS;
  627. //-allAdvice feature
  628. //if( !TheGlobalData->m_allAdvice )
  629. //{
  630. // maxAdviceTips = 1;
  631. //}
  632. //numAvailableTips is used to determine if we are going to randomly choose a tip
  633. //or determine if a tip is available (if -1)
  634. Bool choosing = numAvailableTips != -1;
  635. Int availableTips = 0;
  636. //15) Selected a strategy center battle plan?
  637. if( m_hadAStrategyCenter && !m_choseAStrategyForCenter )
  638. {
  639. availableTips++;
  640. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  641. Int limit = maxAdviceTips - info->numTips;
  642. if( choosing && rand < limit )
  643. {
  644. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:PickStrategyCenterPlan" ) );
  645. info->numTips++;
  646. }
  647. numAvailableTips--;
  648. }
  649. //16) Placed units inside tunnel network?
  650. if( m_hadATunnelNetwork && !m_unitsEnteredTunnelNetwork )
  651. {
  652. availableTips++;
  653. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  654. Int limit = maxAdviceTips - info->numTips;
  655. if( choosing && rand < limit )
  656. {
  657. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:UseTunnelNetwork" ) );
  658. info->numTips++;
  659. }
  660. numAvailableTips--;
  661. }
  662. //17) Player used control groups?
  663. if( !m_controlGroupsUsed )
  664. {
  665. availableTips++;
  666. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  667. Int limit = maxAdviceTips - info->numTips;
  668. if( choosing && rand < limit )
  669. {
  670. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:UseControlGroups" ) );
  671. info->numTips++;
  672. }
  673. numAvailableTips--;
  674. }
  675. //18) Built secondary income building?
  676. if( !m_secondaryIncomeUnitsBuilt )
  677. {
  678. availableTips++;
  679. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  680. Int limit = maxAdviceTips - info->numTips;
  681. if( choosing && rand < limit )
  682. {
  683. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:UseSecondaryIncomeMethods" ) );
  684. info->numTips++;
  685. }
  686. numAvailableTips--;
  687. }
  688. //19) Cleared out garrisoned buildings?
  689. if( !m_clearedGarrisonedBuildings )
  690. {
  691. availableTips++;
  692. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  693. Int limit = maxAdviceTips - info->numTips;
  694. if( choosing && rand < limit )
  695. {
  696. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:ClearBuildings" ) );
  697. info->numTips++;
  698. }
  699. numAvailableTips--;
  700. }
  701. //20) Did the Player pick up salvage (as GLA)?
  702. if( m_player->getPlayerTemplate() && !m_player->getPlayerTemplate()->getBaseSide().compareNoCase( "GLA" ) )
  703. {
  704. if( !m_salvageCollected )
  705. {
  706. availableTips++;
  707. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  708. Int limit = maxAdviceTips - info->numTips;
  709. if( choosing && rand < limit )
  710. {
  711. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:PickUpSalvage" ) );
  712. info->numTips++;
  713. }
  714. numAvailableTips--;
  715. }
  716. }
  717. //21) Did the player ever use the "Guard" ability?
  718. if( !m_guardAbilityUsedCount )
  719. {
  720. availableTips++;
  721. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  722. Int limit = maxAdviceTips - info->numTips;
  723. if( choosing && rand < limit )
  724. {
  725. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:UseGuardAbility" ) );
  726. info->numTips++;
  727. }
  728. numAvailableTips--;
  729. }
  730. //22) Did the player build more than one Supply Center (that is, did he expand out)?
  731. if( m_supplyCentersBuilt < 2 )
  732. {
  733. availableTips++;
  734. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  735. Int limit = maxAdviceTips - info->numTips;
  736. if( choosing && rand < limit )
  737. {
  738. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:MultipleSupplyCenters" ) );
  739. info->numTips++;
  740. }
  741. numAvailableTips--;
  742. }
  743. //23) Did the player ever garrison a vehicle?
  744. //CUT!!!
  745. //24) Did the player ever use the hotkey to grab all of one unit type (change to use any hotkeys)?
  746. //CUT!!!
  747. if( !choosing && availableTips > 0 )
  748. {
  749. //We're done counting available tips, so now that we know the
  750. //number, we can randomly choose.
  751. evaluateTier2Advice( info, availableTips );
  752. }
  753. }
  754. //------------------------------------------------------------------------------------------------
  755. void AcademyStats::evaluateTier3Advice( AcademyAdviceInfo *info, Int numAvailableTips )
  756. {
  757. UnsignedInt maxAdviceTips = MAX_ADVICE_TIPS;
  758. //-allAdvice feature
  759. //if( !TheGlobalData->m_allAdvice )
  760. //{
  761. // maxAdviceTips = 1;
  762. //}
  763. UnsignedInt now = TheGameLogic->getFrame();
  764. //numAvailableTips is used to determine if we are going to randomly choose a tip
  765. //or determine if a tip is available (if -1)
  766. Bool choosing = numAvailableTips != -1;
  767. Int availableTips = 0;
  768. //25) Did the player use the new alternate interface in the options?
  769. if( !TheGlobalData->m_useAlternateMouse )
  770. {
  771. availableTips++;
  772. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  773. Int limit = maxAdviceTips - info->numTips;
  774. if( choosing && rand < limit )
  775. {
  776. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:AlternateMouseInterface" ) );
  777. info->numTips++;
  778. }
  779. numAvailableTips--;
  780. }
  781. //26) Player did not use the new "double click location attack move/guard"
  782. if( !m_doubleClickAttackMoveOrdersGiven )
  783. {
  784. availableTips++;
  785. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  786. Int limit = maxAdviceTips - info->numTips;
  787. if( choosing && rand < limit )
  788. {
  789. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:DoubleClickAttackMoveGuard" ) );
  790. info->numTips++;
  791. }
  792. numAvailableTips--;
  793. }
  794. //27) Built barracks within 5 minutes?
  795. if( !m_builtBarracksWithinFiveMinutes )
  796. {
  797. availableTips++;
  798. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  799. Int limit = maxAdviceTips - info->numTips;
  800. if( choosing && rand < limit )
  801. {
  802. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildBarracksSooner" ) );
  803. info->numTips++;
  804. }
  805. numAvailableTips--;
  806. }
  807. //28) Built warfactory within 10 minutes?
  808. if( !m_builtWarFactoryWithinTenMinutes )
  809. {
  810. availableTips++;
  811. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  812. Int limit = maxAdviceTips - info->numTips;
  813. if( choosing && rand < limit )
  814. {
  815. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildWarFactorySooner" ) );
  816. info->numTips++;
  817. }
  818. numAvailableTips--;
  819. }
  820. //29) Built tech structure within 15 minutes?
  821. if( !m_builtTechStructureWithinFifteenMinutes )
  822. {
  823. availableTips++;
  824. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  825. Int limit = maxAdviceTips - info->numTips;
  826. if( choosing && rand < limit )
  827. {
  828. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:BuildTechStructureSooner" ) );
  829. info->numTips++;
  830. }
  831. numAvailableTips--;
  832. }
  833. //30) No income for 2 minutes?
  834. UnsignedInt delta = m_lastIncomeFrame - now;
  835. if( delta > m_maxFramesBetweenIncome )
  836. {
  837. m_maxFramesBetweenIncome = delta;
  838. }
  839. if( m_maxFramesBetweenIncome > LOGICFRAMES_PER_SECOND * 120 || !m_lastIncomeFrame )
  840. {
  841. availableTips++;
  842. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  843. Int limit = maxAdviceTips - info->numTips;
  844. if( choosing && rand < limit )
  845. {
  846. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:NoIncome" ) );
  847. info->numTips++;
  848. }
  849. numAvailableTips--;
  850. }
  851. //31) Did the Player ever use Dozers/Workers to clear out traps/mines/booby traps?
  852. if( ThePlayerList->getLocalPlayer()->getAcademyStats()->getMines() > 0 && !m_minesCleared )
  853. {
  854. availableTips++;
  855. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  856. Int limit = maxAdviceTips - info->numTips;
  857. if( choosing && rand < limit )
  858. {
  859. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:NoIncome" ) );
  860. info->numTips++;
  861. }
  862. numAvailableTips--;
  863. }
  864. //32) Captured any sniped vehicles?
  865. if( ThePlayerList->getNeutralPlayer()->getAcademyStats()->getVehiclesSniped() > 0 )
  866. {
  867. if( !m_vehiclesRecovered )
  868. {
  869. availableTips++;
  870. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  871. Int limit = maxAdviceTips - info->numTips;
  872. if( choosing && rand < limit )
  873. {
  874. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:UnmannedVehicles" ) );
  875. info->numTips++;
  876. }
  877. numAvailableTips--;
  878. }
  879. }
  880. //33) Did the player ever build a "disguisable" unit and never used the disguise ability?
  881. if( m_disguisableVehiclesBuilt )
  882. {
  883. if( !m_vehiclesDisguised )
  884. {
  885. availableTips++;
  886. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  887. Int limit = maxAdviceTips - info->numTips;
  888. if( choosing && rand < limit )
  889. {
  890. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:DisguisedUnits" ) );
  891. info->numTips++;
  892. }
  893. numAvailableTips--;
  894. }
  895. }
  896. //35) Did the player ever create a "Firestorm" with his MiGs or Inferno Cannons?
  897. if( m_player->getPlayerTemplate() && !m_player->getPlayerTemplate()->getBaseSide().compareNoCase( "China" ) )
  898. {
  899. if( !m_firestormsCreated )
  900. {
  901. availableTips++;
  902. Int rand = GameClientRandomValue( 0, numAvailableTips - 1 );
  903. Int limit = maxAdviceTips - info->numTips;
  904. if( choosing && rand < limit )
  905. {
  906. info->advice[ info->numTips ].concat( TheGameText->fetch( "ACADEMY:Firestorm" ) );
  907. info->numTips++;
  908. }
  909. numAvailableTips--;
  910. }
  911. }
  912. if( !choosing && availableTips > 0 )
  913. {
  914. //We're done counting available tips, so now that we know the
  915. //number, we can randomly choose.
  916. evaluateTier3Advice( info, availableTips );
  917. }
  918. }
  919. //------------------------------------------------------------------------------------------------
  920. Bool AcademyStats::calculateAcademyAdvice( AcademyAdviceInfo *info )
  921. {
  922. UnsignedInt maxAdviceTips = MAX_ADVICE_TIPS;
  923. //-allAdvice feature
  924. //if( !TheGlobalData->m_allAdvice )
  925. //{
  926. // maxAdviceTips = 1;
  927. //}
  928. if( m_unknownSide )
  929. {
  930. return FALSE;
  931. }
  932. //Sanity
  933. if( !info )
  934. {
  935. DEBUG_CRASH( ("AcademyStats::calculateAcademyAdvice() was passed in NULL AcademyAdviceInfo.") );
  936. return FALSE;
  937. }
  938. //It's possible to play a perfect game and not need any advice.
  939. info->numTips = 0;
  940. //Build the header for each string.
  941. for( Int i = 0; i < maxAdviceTips; i++ )
  942. {
  943. info->advice[ i ].format( UnicodeString( L"\n\n" ) );
  944. }
  945. //First look at tier 1 basic advice and pick any advice we could benefit from.
  946. evaluateTier1Advice( info );
  947. //Now look at tier 2 intermediate advice.
  948. if( info->numTips < maxAdviceTips )
  949. {
  950. evaluateTier2Advice( info );
  951. if( info->numTips < maxAdviceTips )
  952. {
  953. //Finally look at tier 3 advanced advice.
  954. evaluateTier3Advice( info );
  955. }
  956. }
  957. //Do we have any advice to give?
  958. return info->numTips != 0;
  959. }
  960. //------------------------------------------------------------------------------------------------
  961. // CRC
  962. //------------------------------------------------------------------------------------------------
  963. void AcademyStats::crc( Xfer *xfer )
  964. {
  965. } // end crc
  966. //------------------------------------------------------------------------------------------------
  967. /** Xfer method
  968. * Version Info:
  969. * 1: Initial version */
  970. //------------------------------------------------------------------------------------------------
  971. void AcademyStats::xfer( Xfer *xfer )
  972. {
  973. // version
  974. XferVersion currentVersion = 1;
  975. XferVersion version = currentVersion;
  976. xfer->xferVersion( &version, currentVersion );
  977. xfer->xferUnsignedInt( &m_nextUpdateFrame );
  978. xfer->xferBool( &m_firstUpdate );
  979. xfer->xferBool( &m_unknownSide );
  980. //+-----------------------+
  981. //| Tier 1 (Basic advice) |
  982. //+-----------------------+
  983. //1) Did player build at least one of each structure type available?
  984. //CUT!!!
  985. //2) Did player run out of money before building a supply center?
  986. xfer->xferBool( &m_spentCashBeforeBuildingSupplyCenter );
  987. xfer->xferUnsignedInt( &m_supplyCentersBuilt );
  988. xfer->xferUnsignedInt( &m_supplyCenterCost );
  989. //3) Did player build radar (if applicable)?
  990. xfer->xferBool( &m_researchedRadar );
  991. //4) Did player build any dozers/workers?
  992. xfer->xferUnsignedInt( &m_peonsBuilt );
  993. //5) Did player ever capture a structure?
  994. xfer->xferUnsignedInt( &m_structuresCaptured );
  995. //6) Did player spend any generals points?
  996. xfer->xferUnsignedInt( &m_generalsPointsSpent );
  997. //7) Did player ever use a generals power or superweapon?
  998. xfer->xferUnsignedInt( &m_specialPowersUsed );
  999. //8) Did player garrison any structures?
  1000. xfer->xferUnsignedInt( &m_structuresGarrisoned );
  1001. //9) How idle was the player in building military units?
  1002. xfer->xferUnsignedInt( &m_idleBuildingUnitsMaxFrames );
  1003. xfer->xferUnsignedInt( &m_lastUnitBuiltFrame );
  1004. //10) Did player drag select units?
  1005. xfer->xferUnsignedInt( &m_dragSelectUnits );
  1006. //11) Did player upgrade anything?
  1007. xfer->xferUnsignedInt( &m_upgradesPurchased );
  1008. //12) Was player out of power for more than 10 minutes?
  1009. xfer->xferUnsignedInt( &m_powerOutMaxFrames );
  1010. xfer->xferUnsignedInt( &m_oldestPowerOutFrame );
  1011. xfer->xferBool ( &m_hadPowerLastCheck );
  1012. //13) Extra gathers built?
  1013. xfer->xferUnsignedInt( &m_gatherersBuilt );
  1014. //14) Heros built?
  1015. xfer->xferUnsignedInt( &m_heroesBuilt );
  1016. //+------------------------------+
  1017. //| Tier 2 (Intermediate advice) |
  1018. //+------------------------------+
  1019. //15) Selected a strategy center battle plan?
  1020. xfer->xferBool( &m_hadAStrategyCenter );
  1021. xfer->xferBool( &m_choseAStrategyForCenter );
  1022. //16) Placed units inside tunnel network?
  1023. xfer->xferUnsignedInt( &m_unitsEnteredTunnelNetwork );
  1024. xfer->xferBool( &m_hadATunnelNetwork );
  1025. //17) Player used control groups?
  1026. xfer->xferUnsignedInt( &m_controlGroupsUsed );
  1027. //18) Built secondary income unit (hacker, dropzone, blackmarket)?
  1028. xfer->xferUnsignedInt( &m_secondaryIncomeUnitsBuilt );
  1029. //19) Cleared out garrisoned buildings?
  1030. xfer->xferUnsignedInt( &m_clearedGarrisonedBuildings );
  1031. //20) Did the Player pick up salvage (as GLA)?
  1032. xfer->xferUnsignedInt( &m_salvageCollected );
  1033. //21) Did the player ever use the "Guard" ability?
  1034. xfer->xferUnsignedInt( &m_guardAbilityUsedCount );
  1035. //22) Did the player build more than one Supply Center (that is, did he expand out)?
  1036. //Uses m_supplyCentersBuilt!
  1037. //23) Did the player ever garrison a vehicle?
  1038. //CUT!!!
  1039. //24) Did the player ever use the hotkey to grab all of one unit type (change to use any hotkeys)?
  1040. //CUT!!!
  1041. //+--------------------------+
  1042. //| Tier 3 (Advanced advice) |
  1043. //+--------------------------+
  1044. //25) Did the player use the new alternate interface in the options?
  1045. //Uses TheGlobalData->m_useAlternateMouse
  1046. //26) Player did not use the new "double click location attack move/guard"
  1047. xfer->xferUnsignedInt( &m_doubleClickAttackMoveOrdersGiven );
  1048. //27) Built barracks within 5 minutes?
  1049. xfer->xferBool( &m_builtBarracksWithinFiveMinutes );
  1050. //28) Built war factory within 10 minutes?
  1051. xfer->xferBool( &m_builtWarFactoryWithinTenMinutes );
  1052. //29) Built tech structure within 15 minutes?
  1053. xfer->xferBool( &m_builtTechStructureWithinFifteenMinutes );
  1054. //30) No income for 2 minutes?
  1055. xfer->xferUnsignedInt( &m_lastIncomeFrame );
  1056. xfer->xferUnsignedInt( &m_maxFramesBetweenIncome );
  1057. //31) Did the Player ever use Dozers/Workers to clear out traps/mines/booby traps?
  1058. xfer->xferUnsignedInt( &m_mines ); //Neutral player stat
  1059. xfer->xferUnsignedInt( &m_minesCleared );
  1060. //32) Captured any sniped vehicles?
  1061. xfer->xferUnsignedInt( &m_vehiclesRecovered );
  1062. xfer->xferUnsignedInt( &m_vehiclesSniped );
  1063. //33) Did the player ever build a "disguisable" unit and never used the disguise ability?
  1064. xfer->xferUnsignedInt( &m_disguisableVehiclesBuilt );
  1065. xfer->xferUnsignedInt( &m_vehiclesDisguised );
  1066. //34) Did the player never build a "stealth" upgrade?
  1067. //CUT!!!
  1068. //35) Did the player ever create a "Firestorm" with his MiGs or Inferno Cannons?
  1069. xfer->xferUnsignedInt( &m_firestormsCreated );
  1070. } // end xfer
  1071. //------------------------------------------------------------------------------------------------
  1072. // Load post process
  1073. //------------------------------------------------------------------------------------------------
  1074. void AcademyStats::loadPostProcess( void )
  1075. {
  1076. } // end loadPostProcess