ControlBarCommand.cpp 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541
  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: ControlBarCommand.cpp ////////////////////////////////////////////////////////////////////
  24. // Author: Colin Day, March 2002
  25. // Desc: Methods specific to the control bar unit commands
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  29. #include "Common/NameKeyGenerator.h"
  30. #include "Common/ThingTemplate.h"
  31. #include "Common/ThingFactory.h"
  32. #include "Common/Player.h"
  33. #include "Common/PlayerList.h"
  34. #include "Common/PlayerTemplate.h"
  35. #include "Common/SpecialPower.h"
  36. #include "Common/Upgrade.h"
  37. #include "Common/BuildAssistant.h"
  38. #include "GameLogic/GameLogic.h"
  39. #include "GameLogic/Module/BattlePlanUpdate.h"
  40. #include "GameLogic/Module/DozerAIUpdate.h"
  41. #include "GameLogic/Module/OverchargeBehavior.h"
  42. #include "GameLogic/Module/ProductionUpdate.h"
  43. #include "GameLogic/Module/SpecialPowerModule.h"
  44. #include "GameLogic/Module/TransportContain.h"
  45. #include "GameLogic/Module/MobNexusContain.h"
  46. #include "GameLogic/Module/SpecialAbilityUpdate.h"
  47. #include "GameLogic/Module/BattlePlanUpdate.h"
  48. #include "GameLogic/Module/VeterancyGainCreate.h"
  49. #include "GameLogic/Module/HackInternetAIUpdate.h"
  50. #include "GameLogic/Weapon.h"
  51. #include "GameClient/InGameUI.h"
  52. #include "GameClient/Drawable.h"
  53. #include "GameClient/ControlBar.h"
  54. #include "GameClient/GameWindow.h"
  55. #include "GameClient/GameWindowManager.h"
  56. #include "GameClient/GadgetPushButton.h"
  57. #ifdef _INTERNAL
  58. // for occasional debugging...
  59. //#pragma optimize("", off)
  60. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  61. #endif
  62. // PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
  63. static GameWindow *commandWindows[ MAX_COMMANDS_PER_SET ];
  64. Bool commandWindowsInitialized = FALSE;
  65. static Color BuildClockColor = GameMakeColor(0,0,0,100);
  66. // STATIC DATA STORAGE ////////////////////////////////////////////////////////////////////////////
  67. ControlBar::ContainEntry ControlBar::m_containData[ MAX_COMMANDS_PER_SET ];
  68. //-------------------------------------------------------------------------------------------------
  69. /** Note, this iterate callback assumes that the inventory exit buttons appear in a
  70. * continuous order in the layout of the command set */
  71. //-------------------------------------------------------------------------------------------------
  72. struct PopulateInvButtonData
  73. {
  74. Int currIndex; ///< index that represents the control we're talking about
  75. Int maxIndex; ///< this is the last valid control we can use
  76. GameWindow **controls; ///< the controls
  77. Object *transport; ///< the transport
  78. };
  79. //-------------------------------------------------------------------------------------------------
  80. /** Used for the callback iterator on transport contents to do the actual GUI fill */
  81. //-------------------------------------------------------------------------------------------------
  82. void ControlBar::populateInvDataCallback( Object *obj, void *userData )
  83. {
  84. PopulateInvButtonData *data = (PopulateInvButtonData *)userData;
  85. //
  86. // if we're beyond the max the GUI can support, design needs to change the parameters
  87. // of the transport object to carry less things
  88. //
  89. if( data->currIndex > data->maxIndex )
  90. {
  91. DEBUG_ASSERTCRASH( 0, ("There is not enough GUI slots to hold the # of items inside a '%s'\n",
  92. data->transport->getTemplate()->getName().str()) );
  93. return;
  94. } // end if
  95. // get the window control that we're going to put our smiling faces in
  96. GameWindow *control = data->controls[ data->currIndex ];
  97. DEBUG_ASSERTCRASH( control, ("populateInvDataCallback: Control not found\n") );
  98. // assign our control and object id to the transport data
  99. m_containData[ data->currIndex ].control = control;
  100. m_containData[ data->currIndex ].objectID = obj->getID();
  101. data->currIndex++;
  102. // fill out the control enabled, hilite, and pushed images
  103. const Image *image;
  104. image = obj->getTemplate()->getButtonImage();
  105. GadgetButtonSetEnabledImage( control, image );
  106. //No longer used
  107. //image = TheMappedImageCollection->findImageByName( obj->getTemplate()->getInventoryImageName( INV_IMAGE_HILITE ) );
  108. //GadgetButtonSetHiliteImage( control, image );
  109. //image = TheMappedImageCollection->findImageByName( obj->getTemplate()->getInventoryImageName( INV_IMAGE_PUSHED ) );
  110. //GadgetButtonSetHiliteSelectedImage( control, image );
  111. //Show the contained object's veterancy symbol!
  112. image = calculateVeterancyOverlayForObject( obj );
  113. GadgetButtonDrawOverlayImage( control, image );
  114. // enable the control
  115. control->winEnable( TRUE );
  116. } // end populateInvDataCallback
  117. //-------------------------------------------------------------------------------------------------
  118. /** Transports have an extra special manipulation of the user interface. They get to look
  119. * at the available command set, and any of the commands that are TransportExit commands *AND*
  120. * there is actually an object to represent that slot contained in the transport, the
  121. * inventory picture of the contained object will be displayed in the window control for
  122. * that TransportExit command. Also, transports will HIDE any TransportExit controls found
  123. * in the command set that represent slots that *DO NOT EXIST* for the transport (that is,
  124. * the transport can only hold 4 things, but the GUI has buttons for 8 things). For slots
  125. * that are empty but present in the transport the UI will show a disabled button to show
  126. * the user that there is an open "slot" */
  127. //-------------------------------------------------------------------------------------------------
  128. void ControlBar::doTransportInventoryUI( Object *transport, const CommandSet *commandSet )
  129. {
  130. /// @todo srj -- remove hard-coding here, please
  131. //static const CommandButton *exitCommand = findCommandButton( "Command_TransportExit" );
  132. // sanity
  133. if( transport == NULL || commandSet == NULL )
  134. return;
  135. // get the transport contain module
  136. ContainModuleInterface *contain = transport->getContain();
  137. // sanity
  138. if( contain == NULL )
  139. return;
  140. // how many slots do we have inside the transport
  141. Int transportMax = contain->getContainMax();
  142. //
  143. // first, hide any windows in 'm_commandWindows' that correspond to TransportExit commands
  144. // within the 'commandSet' that are overflow slots (the ui could be showing more inventory
  145. // exit button slots than there are slots in the transport)
  146. //
  147. // The extra slots bit means that a tank that takes up three slots will make two transport
  148. // buttons disappear off the end to show he takes up more room.
  149. transportMax = transportMax - contain->getExtraSlotsInUse();
  150. Int firstInventoryIndex = -1;
  151. Int lastInventoryIndex = -1;
  152. Int inventoryCommandCount = 0;
  153. const CommandButton *commandButton;
  154. for( Int i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  155. {
  156. // our implementation doesn't necessarily make use of the max possible command buttons
  157. if (! m_commandWindows[ i ]) continue;
  158. // get command button
  159. commandButton = commandSet->getCommandButton(i);
  160. // is this an inventory exit command
  161. if( commandButton && commandButton->getCommandType() == GUI_COMMAND_EXIT_CONTAINER )
  162. {
  163. // record the index of the control for the first inventory exit command we found
  164. if( firstInventoryIndex == -1 )
  165. firstInventoryIndex = i;
  166. //
  167. // since we're assuming all inventory exit commands appear in a continuous order,
  168. // we need to also need to keep track of what is the last valid inventory commadn index
  169. //
  170. lastInventoryIndex = i;
  171. // increment our count of available inventory exit commands found for the set
  172. inventoryCommandCount++;
  173. // show the window, but disable by default unless something is actually loaded in there
  174. m_commandWindows[ i ]->winHide( FALSE );
  175. m_commandWindows[ i ]->winEnable( FALSE );
  176. ///////// poopy
  177. //Clear any potential veterancy rank, or else we'll see it when it's empty!
  178. GadgetButtonDrawOverlayImage( m_commandWindows[ i ], NULL );
  179. //Unmanned vehicles don't have any commands available -- in fact they are hidden!
  180. if( transport->isDisabledByType( DISABLED_UNMANNED ) )
  181. {
  182. m_commandWindows[ i ]->winHide( TRUE );
  183. }
  184. // is this where we set the cameos disabled when container is subdued?
  185. // if we've counted more UI spots than the transport can hold, hide this command window
  186. if( inventoryCommandCount > transportMax )
  187. m_commandWindows[ i ]->winHide( TRUE );
  188. //
  189. // set the inventory exit command into the window (even if it's one of the hidden ones
  190. // it's OK cause we'll never see it to click it
  191. //
  192. setControlCommand( m_commandWindows[ i ], commandButton );
  193. } // end if
  194. } // end for i
  195. // After Every change to the m_commandWIndows, we need to show fill in the missing blanks with the images
  196. // removed from multiplayer branch
  197. //showCommandMarkers();
  198. //
  199. // now, iterate the contained items of the transport and for each one we find we will
  200. // populate a user interface button with its inventory picture and store the inventory
  201. // data inside the control bar so we can respond to the button when its clicked
  202. //
  203. if( lastInventoryIndex >= 0 ) // just for sanity
  204. {
  205. PopulateInvButtonData data;
  206. data.controls = m_commandWindows;
  207. data.currIndex = firstInventoryIndex;
  208. data.maxIndex = lastInventoryIndex;
  209. data.transport = transport;
  210. contain->iterateContained( populateInvDataCallback, &data, FALSE );
  211. } // end if
  212. //
  213. // save the last recorded inventory count so we know when we have to redo the gui when
  214. // something exits or enters
  215. //
  216. m_lastRecordedInventoryCount = contain->getContainCount();
  217. } // end doTransportInventoryUI
  218. //-------------------------------------------------------------------------------------------------
  219. //-------------------------------------------------------------------------------------------------
  220. void ControlBar::populateCommand( Object *obj )
  221. {
  222. const CommandSet *commandSet;
  223. Int i;
  224. Player *player = obj->getControllingPlayer();
  225. // reset contain data
  226. resetContainData();
  227. // reset the build queue data
  228. resetBuildQueueData();
  229. // get command set
  230. commandSet = TheControlBar->findCommandSet( obj->getCommandSetString() );
  231. // if no command set match is found hide all the buttons
  232. if( commandSet == NULL )
  233. {
  234. // hide all the buttons
  235. for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  236. if (m_commandWindows[ i ])
  237. {
  238. m_commandWindows[ i ]->winHide( TRUE );
  239. }
  240. // nothing left to do
  241. return;
  242. } // end if
  243. // transports do extra special things with the user interface buttons
  244. if( obj->getContain() && obj->getContain()->isDisplayedOnControlBar() )
  245. doTransportInventoryUI( obj, commandSet );
  246. // populate the button with commands defined
  247. const CommandButton *commandButton;
  248. for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  249. {
  250. // our implementation doesn't necessarily make use of the max possible command buttons
  251. if (! m_commandWindows[ i ]) continue;
  252. // get command button
  253. commandButton = commandSet->getCommandButton(i);
  254. // if button is not present, just hide the window
  255. if( commandButton == NULL )
  256. {
  257. // hide window on interface
  258. m_commandWindows[ i ]->winHide( TRUE );
  259. } // end if
  260. else
  261. {
  262. //Script only command -- don't show it in the UI.
  263. if( BitTest( commandButton->getOptions(), SCRIPT_ONLY ) )
  264. {
  265. m_commandWindows[ i ]->winHide( TRUE );
  266. continue;
  267. }
  268. //
  269. // inventory exit commands were a special case already taken care above ... we needed
  270. // to iterage through the containment for transport objects and fill out any available
  271. // inventory exit buttons on the UI
  272. //
  273. if( commandButton->getCommandType() != GUI_COMMAND_EXIT_CONTAINER )
  274. {
  275. // make sure the window is not hidden
  276. m_commandWindows[ i ]->winHide( FALSE );
  277. // enable by default
  278. m_commandWindows[ i ]->winEnable( TRUE );
  279. // populate the visible button with data from the command button
  280. setControlCommand( m_commandWindows[ i ], commandButton );
  281. //
  282. // commands that require sciences we don't have are hidden so they never show up
  283. // cause we can never pick "another" general technology throughout the game
  284. //
  285. if( BitTest( commandButton->getOptions(), NEED_SPECIAL_POWER_SCIENCE ) )
  286. {
  287. const SpecialPowerTemplate *power = commandButton->getSpecialPowerTemplate();
  288. if( power && power->getRequiredScience() != SCIENCE_INVALID )
  289. {
  290. if( commandButton->getCommandType() != GUI_COMMAND_PURCHASE_SCIENCE &&
  291. commandButton->getCommandType() != GUI_COMMAND_PLAYER_UPGRADE &&
  292. commandButton->getCommandType() != GUI_COMMAND_OBJECT_UPGRADE )
  293. {
  294. if( player->hasScience( power->getRequiredScience() ) == FALSE )
  295. {
  296. //Hide the power
  297. m_commandWindows[ i ]->winHide( TRUE );
  298. }
  299. else
  300. {
  301. //The player does have the special power! Now determine if the images require
  302. //enhancement based on upgraded versions. This is determined by the command
  303. //button specifying a vector of sciences in the command button.
  304. Int bestIndex = -1;
  305. ScienceType science;
  306. for( Int scienceIndex = 0; scienceIndex < commandButton->getScienceVec().size(); ++scienceIndex )
  307. {
  308. science = commandButton->getScienceVec()[ scienceIndex ];
  309. //Keep going until we reach the end or don't have the required science!
  310. if( player->hasScience( science ) )
  311. {
  312. bestIndex = scienceIndex;
  313. }
  314. else
  315. {
  316. break;
  317. }
  318. }
  319. if( bestIndex != -1 )
  320. {
  321. //Now get the best sciencetype.
  322. science = commandButton->getScienceVec()[ bestIndex ];
  323. const CommandSet *commandSet1;
  324. const CommandSet *commandSet3;
  325. const CommandSet *commandSet8;
  326. Int i;
  327. // get command set
  328. if( !player || !player->getPlayerTemplate()
  329. || player->getPlayerTemplate()->getPurchaseScienceCommandSetRank1().isEmpty()
  330. || player->getPlayerTemplate()->getPurchaseScienceCommandSetRank3().isEmpty()
  331. || player->getPlayerTemplate()->getPurchaseScienceCommandSetRank8().isEmpty() )
  332. {
  333. continue;
  334. }
  335. commandSet1 = TheControlBar->findCommandSet( player->getPlayerTemplate()->getPurchaseScienceCommandSetRank1() );
  336. commandSet3 = TheControlBar->findCommandSet( player->getPlayerTemplate()->getPurchaseScienceCommandSetRank3() );
  337. commandSet8 = TheControlBar->findCommandSet( player->getPlayerTemplate()->getPurchaseScienceCommandSetRank8() );
  338. if( !commandSet1 || !commandSet3 || !commandSet8 )
  339. {
  340. continue;
  341. }
  342. Bool found = FALSE;
  343. for( i = 0; !found && i < MAX_PURCHASE_SCIENCE_RANK_1; i++ )
  344. {
  345. const CommandButton *command = commandSet1->getCommandButton( i );
  346. if( command && command->getCommandType() == GUI_COMMAND_PURCHASE_SCIENCE )
  347. {
  348. //All purchase sciences specify a single science.
  349. if( command->getScienceVec().empty() )
  350. {
  351. DEBUG_CRASH( ("Commandbutton %s is a purchase science button without any science! Please add it.", command->getName().str() ) );
  352. }
  353. else if( command->getScienceVec()[0] == science )
  354. {
  355. commandButton->copyImagesFrom( command, TRUE );
  356. commandButton->copyButtonTextFrom( command, FALSE, TRUE );
  357. found = TRUE;
  358. break;
  359. }
  360. }
  361. }
  362. for( i = 0; !found && i < MAX_PURCHASE_SCIENCE_RANK_3; i++ )
  363. {
  364. const CommandButton *command = commandSet3->getCommandButton( i );
  365. if( command && command->getCommandType() == GUI_COMMAND_PURCHASE_SCIENCE )
  366. {
  367. //All purchase sciences specify a single science.
  368. if( command->getScienceVec().empty() )
  369. {
  370. DEBUG_CRASH( ("Commandbutton %s is a purchase science button without any science! Please add it.", command->getName().str() ) );
  371. }
  372. else if( command->getScienceVec()[0] == science )
  373. {
  374. commandButton->copyImagesFrom( command, TRUE );
  375. commandButton->copyButtonTextFrom( command, FALSE, TRUE );
  376. found = TRUE;
  377. break;
  378. }
  379. }
  380. }
  381. for( i = 0; !found && i < MAX_PURCHASE_SCIENCE_RANK_8; i++ )
  382. {
  383. const CommandButton *command = commandSet8->getCommandButton( i );
  384. if( command && command->getCommandType() == GUI_COMMAND_PURCHASE_SCIENCE )
  385. {
  386. //All purchase sciences specify a single science.
  387. if( command->getScienceVec().empty() )
  388. {
  389. DEBUG_CRASH( ("Commandbutton %s is a purchase science button without any science! Please add it.", command->getName().str() ) );
  390. }
  391. else if( command->getScienceVec()[0] == science )
  392. {
  393. commandButton->copyImagesFrom( command, TRUE );
  394. commandButton->copyButtonTextFrom( command, FALSE, TRUE );
  395. found = TRUE;
  396. break;
  397. }
  398. }
  399. }
  400. }
  401. }
  402. }
  403. }
  404. } // end if
  405. } // end else
  406. } // end else
  407. } // end for i
  408. // After Every change to the m_commandWIndows, we need to show fill in the missing blanks with the images
  409. // removed from multiplayer branch
  410. //showCommandMarkers();
  411. //
  412. // for objects that have a production exit interface, we may have a rally point set.
  413. // if we do, we want to show that rally point in the world
  414. //
  415. ExitInterface *exit = obj->getObjectExitInterface();
  416. if( exit )
  417. {
  418. //
  419. // if a rally point is set, show the rally point, if we don't have it set hide any rally
  420. // point we might have visible
  421. //
  422. showRallyPoint( exit->getRallyPoint() );
  423. } // end if
  424. //
  425. // to avoid a one frame delay where windows may become enabled/disabled, run the update
  426. // at once to get it all in the correct state immediately
  427. //
  428. updateContextCommand();
  429. } // end populateCommand
  430. //-------------------------------------------------------------------------------------------------
  431. /** reset transport data */
  432. //-------------------------------------------------------------------------------------------------
  433. void ControlBar::resetContainData( void )
  434. {
  435. Int i;
  436. for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  437. {
  438. m_containData[ i ].control = NULL;
  439. m_containData[ i ].objectID = INVALID_ID;
  440. } // end for i
  441. } // end resetTransportData
  442. //-------------------------------------------------------------------------------------------------
  443. /** reset the build queue data we use to die queue entires to control */
  444. //-------------------------------------------------------------------------------------------------
  445. void ControlBar::resetBuildQueueData( void )
  446. {
  447. Int i;
  448. for( i = 0; i < MAX_BUILD_QUEUE_BUTTONS; i++ )
  449. {
  450. m_queueData[ i ].control = NULL;
  451. m_queueData[ i ].type = PRODUCTION_INVALID;
  452. m_queueData[ i ].productionID = PRODUCTIONID_INVALID;
  453. m_queueData[ i ].upgradeToResearch = NULL;
  454. } // end for i
  455. } // end resetBuildQueue
  456. //-------------------------------------------------------------------------------------------------
  457. //-------------------------------------------------------------------------------------------------
  458. void ControlBar::populateBuildQueue( Object *producer )
  459. {
  460. /// @todo srj -- remove hard-coding here, please
  461. static const CommandButton *cancelUnitCommand = findCommandButton( "Command_CancelUnitCreate" );
  462. /// @todo srj -- remove hard-coding here, please
  463. static const CommandButton *cancelUpgradeCommand = findCommandButton( "Command_CancelUpgradeCreate" );
  464. static NameKeyType buildQueueIDs[ MAX_BUILD_QUEUE_BUTTONS ];
  465. static Bool idsInitialized = FALSE;
  466. Int i;
  467. // reset the build queue data
  468. resetBuildQueueData();
  469. // get name key ids for the build queue buttons
  470. if( idsInitialized == FALSE )
  471. {
  472. AsciiString buttonName;
  473. for( i = 0; i < MAX_BUILD_QUEUE_BUTTONS; i++ )
  474. {
  475. buttonName.format( "ControlBar.wnd:ButtonQueue%02d", i + 1 );
  476. buildQueueIDs[ i ] = TheNameKeyGenerator->nameToKey( buttonName );
  477. } // end for i
  478. idsInitialized = TRUE;
  479. } // end if
  480. // get window pointers to all the buttons for the build queue
  481. for( i = 0; i < MAX_BUILD_QUEUE_BUTTONS; i++ )
  482. {
  483. // get window commented out cause I believe we already set this. We'll see in a few minutes
  484. m_queueData[ i ].control = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_BUILD_QUEUE ],
  485. buildQueueIDs[ i ] );
  486. // disable window by default
  487. m_queueData[ i ].control->winEnable( FALSE );
  488. //Clear the status because this button doesn't use it -- and if it's set, it'll
  489. //become invisible meaning the image that was there will be showed.
  490. m_queueData[ i ].control->winClearStatus( WIN_STATUS_USE_OVERLAY_STATES );
  491. // set the text of the window to nothing by default
  492. GadgetButtonSetText( m_queueData[ i ].control, UnicodeString( L"" ) );
  493. //Clear any potential veterancy rank, or else we'll see it when it's empty!
  494. GadgetButtonDrawOverlayImage( m_queueData[ i ].control, NULL );
  495. } // end for i
  496. // step through each object being built and set the image data for the buttons
  497. ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
  498. if( pu == NULL )
  499. return; // sanity
  500. const ProductionEntry *production;
  501. Int windowIndex = 0;
  502. const Image *image;
  503. for( production = pu->firstProduction();
  504. production;
  505. production = pu->nextProduction( production ) )
  506. {
  507. // don't go above how many queue windows we have
  508. if( windowIndex >= MAX_BUILD_QUEUE_BUTTONS )
  509. break; // exit for
  510. // set the command into the queue button
  511. if( production->getProductionType() == PRODUCTION_UNIT )
  512. {
  513. // set the control command
  514. setControlCommand( m_queueData[ windowIndex ].control, cancelUnitCommand );
  515. m_queueData[ windowIndex ].type = PRODUCTION_UNIT;
  516. m_queueData[ windowIndex ].productionID = production->getProductionID();
  517. // set the images
  518. m_queueData[ windowIndex ].control->winEnable( TRUE );
  519. m_queueData[ windowIndex ].control->winSetStatus( WIN_STATUS_USE_OVERLAY_STATES );
  520. image = production->getProductionObject()->getButtonImage();
  521. GadgetButtonSetEnabledImage( m_queueData[ windowIndex ].control, image );
  522. //No longer used.
  523. //image = TheMappedImageCollection->findImageByName( production->getProductionObject()->getInventoryImageName( INV_IMAGE_HILITE ) );
  524. //GadgetButtonSetHiliteSelectedImage( m_queueData[ windowIndex ].control, image );
  525. //image = TheMappedImageCollection->findImageByName( production->getProductionObject()->getInventoryImageName( INV_IMAGE_PUSHED ) );
  526. //GadgetButtonSetHiliteImage( m_queueData[ windowIndex ].control, image );
  527. //Show the veterancy rank of the object being constructed in the queue
  528. const Image *image = calculateVeterancyOverlayForThing( production->getProductionObject() );
  529. GadgetButtonDrawOverlayImage( m_queueData[ windowIndex ].control, image );
  530. //
  531. // note we're not setting a disabled image into the queue button ... when there is
  532. // nothing in the queue we set the button to disabled, we want to leave the disabled
  533. // queue button graphic we already have in place
  534. //
  535. // image = TheMappedImageCollection->findImageByName( production->getProductionObject()->getInventoryImageName( INV_IMAGE_DISABLED ) );
  536. // GadgetButtonSetDisabledImage( m_queueData[ windowIndex ].control, image );
  537. } // end if
  538. else
  539. {
  540. const UpgradeTemplate *ut = production->getProductionUpgrade();
  541. // set the control command
  542. setControlCommand( m_queueData[ windowIndex ].control, cancelUpgradeCommand );
  543. m_queueData[ windowIndex ].type = PRODUCTION_UPGRADE;
  544. m_queueData[ windowIndex ].upgradeToResearch = production->getProductionUpgrade();
  545. // set the images
  546. m_queueData[ windowIndex ].control->winEnable( TRUE );
  547. m_queueData[ windowIndex ].control->winSetStatus( WIN_STATUS_USE_OVERLAY_STATES );
  548. image = ut->getButtonImage();
  549. GadgetButtonSetEnabledImage( m_queueData[ windowIndex ].control, image );
  550. //No longer used
  551. //image = TheMappedImageCollection->findImageByName( ut->getQueueImageName( UpgradeTemplate::UPGRADE_HILITE ) );
  552. //GadgetButtonSetHiliteSelectedImage( m_queueData[ windowIndex ].control, image );
  553. //image = TheMappedImageCollection->findImageByName( ut->getQueueImageName( UpgradeTemplate::UPGRADE_PUSHED ) );
  554. //GadgetButtonSetHiliteImage( m_queueData[ windowIndex ].control, image );
  555. //
  556. // note we're not setting a disabled image into the queue button ... when there is
  557. // nothing in the queue we set the button to disabled, we want to leave the disabled
  558. // queue button graphic we already have in place
  559. //
  560. // image = TheMappedImageCollection->findImageByName( ut->getQueueImageName( UpgradeTemplate::UPGRADE_DISABLED ) );
  561. // GadgetButtonSetDisabledImage( m_queueData[ windowIndex ].control, image );
  562. } // end else
  563. // we have filled up this window now
  564. windowIndex++;
  565. } // end for
  566. //
  567. // save the count of things being produced in the build queue, when it changes we will
  568. // repopulate the queue to visually show the change
  569. //
  570. m_displayedQueueCount = pu->getProductionCount();
  571. } // end populateBuildQueue
  572. //-------------------------------------------------------------------------------------------------
  573. //-------------------------------------------------------------------------------------------------
  574. void ControlBar::updateContextCommand( void )
  575. {
  576. Object *obj = NULL;
  577. Int i;
  578. // get object
  579. if( m_currentSelectedDrawable )
  580. obj = m_currentSelectedDrawable->getObject();
  581. //
  582. // the contents of objects are ususally showed on the UI, when those contents change
  583. // we always to update the UI
  584. //
  585. ContainModuleInterface *contain = obj ? obj->getContain() : NULL;
  586. if( contain && contain->getContainMax() > 0 &&
  587. m_lastRecordedInventoryCount != contain->getContainCount() )
  588. {
  589. // record this ast the last known number
  590. m_lastRecordedInventoryCount = contain->getContainCount();
  591. // re-evaluate the UI because something has changed
  592. evaluateContextUI();
  593. } // end if, transport
  594. // get production update for those objects that have one
  595. ProductionUpdateInterface *pu = obj ? obj->getProductionUpdateInterface() : NULL;
  596. //
  597. // when we have a production update, we show the build queue when there is actually
  598. // something in the queue, otherwise we show the selection portrait for the object ... so if
  599. // the queue is visible we need to check to see if we should hide it and show the portrait,
  600. // and if the queue is hidden, we need to check and see if it should become shown
  601. //
  602. if( m_contextParent[ CP_BUILD_QUEUE ]->winIsHidden() == TRUE )
  603. {
  604. if( pu && pu->firstProduction() != NULL )
  605. {
  606. // don't show the portrait image
  607. setPortraitByObject( NULL );
  608. // show the build queue
  609. m_contextParent[ CP_BUILD_QUEUE ]->winHide( FALSE );
  610. populateBuildQueue( obj );
  611. } // end if
  612. } // end if
  613. else
  614. {
  615. if( pu && pu->firstProduction() == NULL )
  616. {
  617. // hide the build queue
  618. m_contextParent[ CP_BUILD_QUEUE ]->winHide( TRUE );
  619. // show the portrait image
  620. setPortraitByObject( obj );
  621. } // end if
  622. } // end else
  623. // update a visible production queue
  624. if( m_contextParent[ CP_BUILD_QUEUE ]->winIsHidden() == FALSE )
  625. {
  626. // when the build queue is enabled, the selected portrait cannot be shown
  627. setPortraitByObject( NULL );
  628. //
  629. // when showing a production queue, when the production count changes of the producer
  630. // object (the thing we have selected for the control bar) we will repopulate the
  631. // windows to visually show the new production linup
  632. //
  633. if( pu )
  634. {
  635. // update the whole queue as necessary
  636. if( pu->getProductionCount() != m_displayedQueueCount )
  637. populateBuildQueue( obj );
  638. //
  639. // update the build percentage on the first thing (the thing that's being built)
  640. // in the queue
  641. //
  642. const ProductionEntry *produce = pu->firstProduction();
  643. if( produce )
  644. {
  645. static NameKeyType winID = TheNameKeyGenerator->nameToKey( "ControlBar.wnd:ButtonQueue01" );
  646. GameWindow *win = TheWindowManager->winGetWindowFromId( m_contextParent[ CP_BUILD_QUEUE ], winID );
  647. DEBUG_ASSERTCRASH( win, ("updateContextCommand: Unable to find first build queue button\n") );
  648. // UnicodeString text;
  649. //
  650. // text.format( L"%.0f%%", produce->getPercentComplete() );
  651. // GadgetButtonSetText( win, text );
  652. GadgetButtonDrawInverseClock(win,produce->getPercentComplete(), m_buildUpClockColor);
  653. } // end if
  654. } // end if
  655. } // end if
  656. // evaluate each command on whether or not it should be enabled
  657. for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  658. {
  659. GameWindow *win;
  660. const CommandButton *command;
  661. // our implementation doesn't necessarily make use of the max possible command buttons
  662. if (! m_commandWindows[ i ]) continue;
  663. // get the window
  664. win = m_commandWindows[ i ];
  665. // only consider commands for windows that are actually shown
  666. //`tbd: fix the bug here, that is that if we don't change the unit, we won't attempt to show
  667. // these.
  668. if( win->winIsHidden() == TRUE )
  669. continue;
  670. // get the command from the control
  671. command = (const CommandButton *)GadgetButtonGetData(win);
  672. //command = (const CommandButton *)win->winGetUserData();
  673. if( command == NULL )
  674. continue;
  675. // LORENZEN COMMENTED THIS OUT 8/11
  676. // Reason: ExitCameos can be greyed out when the container object gets subdued
  677. // // ignore transport/structure inventory commands, they are handled elsewhere
  678. // if( command->getCommandType() == GUI_COMMAND_EXIT_CONTAINER )
  679. // {
  680. // win->winSetStatus( WIN_STATUS_ALWAYS_COLOR ); //Don't let these buttons render in grayscale ever!
  681. // continue;
  682. // }
  683. // else
  684. {
  685. win->winClearStatus( WIN_STATUS_NOT_READY );
  686. win->winClearStatus( WIN_STATUS_ALWAYS_COLOR );
  687. }
  688. // is the command available
  689. CommandAvailability availability = getCommandAvailability( command, obj, win );
  690. // enable/disable the window control
  691. switch( availability )
  692. {
  693. case COMMAND_HIDDEN:
  694. win->winHide( TRUE );
  695. break;
  696. case COMMAND_RESTRICTED:
  697. win->winEnable( FALSE );
  698. break;
  699. case COMMAND_NOT_READY:
  700. win->winEnable( FALSE );
  701. win->winSetStatus( WIN_STATUS_NOT_READY );
  702. break;
  703. case COMMAND_CANT_AFFORD:
  704. win->winEnable( FALSE );
  705. win->winSetStatus( WIN_STATUS_ALWAYS_COLOR );
  706. break;
  707. default:
  708. win->winEnable( TRUE );
  709. break;
  710. }
  711. //Determine by the production type of this button, whether or not the created object
  712. //will have a veterancy rank
  713. if( command->getCommandType() != GUI_COMMAND_EXIT_CONTAINER )
  714. {
  715. //Already handled for contained members -- see ControlBar::populateButtonProc()
  716. const Image *image = calculateVeterancyOverlayForThing( command->getThingTemplate() );
  717. GadgetButtonDrawOverlayImage( win, image );
  718. }
  719. //
  720. // for check-like commands we will keep the push button "pushed" or "unpushed" depending
  721. // on the current running status of the command
  722. //
  723. if( BitTest( command->getOptions(), CHECK_LIKE ))
  724. {
  725. // sanity, check like commands should have windows that are check like as well
  726. DEBUG_ASSERTCRASH( BitTest( win->winGetStatus(), WIN_STATUS_CHECK_LIKE ),
  727. ("updateContextCommand: Error, gadget window for command '%s' is not check-like!\n",
  728. command->getName().str()) );
  729. if( availability == COMMAND_ACTIVE )
  730. GadgetCheckLikeButtonSetVisualCheck( win, TRUE );
  731. else
  732. GadgetCheckLikeButtonSetVisualCheck( win, FALSE );
  733. } // end if
  734. } // end for i
  735. // After Every change to the m_commandWIndows, we need to show fill in the missing blanks with the images
  736. // removed from multiplayer branch
  737. //showCommandMarkers();
  738. // // if we have a build tooltip layout, update it with the new data.
  739. // repopulateBuildTooltipLayout();
  740. } // end updatecontextCommand
  741. //-------------------------------------------------------------------------------------------------
  742. const Image* ControlBar::calculateVeterancyOverlayForThing( const ThingTemplate *thingTemplate )
  743. {
  744. VeterancyLevel level = LEVEL_REGULAR;
  745. if( !thingTemplate )
  746. {
  747. return NULL;
  748. }
  749. Player *player = ThePlayerList->getLocalPlayer();
  750. if( !player )
  751. {
  752. return NULL;
  753. }
  754. //See if the thingTemplate has a VeterancyGainCreate
  755. //This is HORROR CODE and needs to be optimized!
  756. const VeterancyGainCreateModuleData *data = NULL;
  757. AsciiString modName;
  758. const ModuleInfo& mi = thingTemplate->getBehaviorModuleInfo();
  759. for( Int modIdx = 0; modIdx < mi.getCount(); ++modIdx )
  760. {
  761. modName = mi.getNthName(modIdx);
  762. if( !modName.compare( "VeterancyGainCreate" ) )
  763. {
  764. data = (const VeterancyGainCreateModuleData*)mi.getNthData( modIdx );
  765. //It does, so see if the player has that upgrade
  766. if( data )
  767. {
  768. //If no science is specified, he gets it automatically (or check the science).
  769. if( data->m_scienceRequired == SCIENCE_INVALID || player->hasScience( data->m_scienceRequired ) )
  770. {
  771. //We do! So now check to see what the veterancy level would be.
  772. if( data->m_startingLevel > level )
  773. {
  774. level = data->m_startingLevel;
  775. }
  776. }
  777. }
  778. }
  779. }
  780. //Return the appropriate image (including NULL if no veterancy levels)
  781. switch( level )
  782. {
  783. case LEVEL_VETERAN:
  784. return m_rankVeteranIcon;
  785. case LEVEL_ELITE:
  786. return m_rankEliteIcon;
  787. case LEVEL_HEROIC:
  788. return m_rankHeroicIcon;
  789. }
  790. return NULL;;
  791. }
  792. //-------------------------------------------------------------------------------------------------
  793. const Image* ControlBar::calculateVeterancyOverlayForObject( const Object *obj )
  794. {
  795. if( !obj )
  796. {
  797. return NULL;
  798. }
  799. VeterancyLevel level = obj->getVeterancyLevel();
  800. //Return the appropriate image (including NULL if no veterancy levels)
  801. switch( level )
  802. {
  803. case LEVEL_VETERAN:
  804. return m_rankVeteranIcon;
  805. case LEVEL_ELITE:
  806. return m_rankEliteIcon;
  807. case LEVEL_HEROIC:
  808. return m_rankHeroicIcon;
  809. }
  810. return NULL;;
  811. }
  812. //-------------------------------------------------------------------------------------------------
  813. static Int getRappellerCount(Object* obj)
  814. {
  815. Int num = 0;
  816. const ContainedItemsList* items = obj->getContain() ? obj->getContain()->getContainedItemsList() : NULL;
  817. if (items)
  818. {
  819. for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it )
  820. {
  821. if ((*it)->isKindOf(KINDOF_CAN_RAPPEL))
  822. {
  823. ++num;
  824. }
  825. }
  826. }
  827. return num;
  828. }
  829. //-------------------------------------------------------------------------------------------------
  830. /** What's the status between 'obj' and the 'command' at present. Can we do it? Are
  831. * we already doing it? Can ya dig it? */
  832. //-------------------------------------------------------------------------------------------------
  833. CommandAvailability ControlBar::getCommandAvailability( const CommandButton *command,
  834. Object *obj,
  835. GameWindow *win,
  836. GameWindow *applyToWin,
  837. Bool forceDisabledEvaluation ) const
  838. {
  839. if( command->getCommandType() == GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT
  840. || command->getCommandType() == GUI_COMMAND_SPECIAL_POWER_CONSTRUCT_FROM_SHORTCUT )
  841. {
  842. if (ThePlayerList && ThePlayerList->getLocalPlayer())
  843. obj = ThePlayerList->getLocalPlayer()->findMostReadyShortcutSpecialPowerOfType( command->getSpecialPowerTemplate()->getSpecialPowerType() );
  844. else
  845. obj = NULL;
  846. }
  847. //If we modify the button (like a gadget clock overlay), then sometimes we may wish to apply it to a specific different button.
  848. //But if we don't specify anything (default), then make them the same.
  849. if( !applyToWin )
  850. {
  851. applyToWin = win;
  852. }
  853. if (obj == NULL)
  854. return COMMAND_HIDDEN; // probably better than crashing....
  855. Player *player = obj->getControllingPlayer();
  856. if (obj->testScriptStatusBit(OBJECT_STATUS_SCRIPT_DISABLED) || obj->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNPOWERED))
  857. {
  858. // if the object status is disabled or unpowered, you cannot do anything to it.
  859. return COMMAND_HIDDEN;
  860. }
  861. //Unmanned vehicles don't have any commands available -- in fact they are hidden!
  862. if( obj->isDisabledByType( DISABLED_UNMANNED ) )
  863. {
  864. return COMMAND_HIDDEN;
  865. }
  866. //It's possible for command buttons to be a single use only type of a button -- like detonating a nuke from a convoy truck.
  867. if( obj->hasSingleUseCommandBeenUsed() )
  868. {
  869. return COMMAND_RESTRICTED;
  870. }
  871. if( BitTest( command->getOptions(), MUST_BE_STOPPED ) )
  872. {
  873. //This button can only be activated when the unit isn't moving!
  874. AIUpdateInterface *ai = obj->getAI();
  875. if( ai && ai->isMoving() )
  876. {
  877. return COMMAND_RESTRICTED;
  878. }
  879. }
  880. //Other disabled objects are unable to use buttons -- so gray them out.
  881. Bool disabled = obj->isDisabled();
  882. // if we are only disabled by being underpowered, and this button doesn't care, well, fix it
  883. if (disabled
  884. && BitTest(command->getOptions(), IGNORES_UNDERPOWERED)
  885. && obj->getDisabledFlags().test(DISABLED_UNDERPOWERED)
  886. && obj->getDisabledFlags().count() == 1)
  887. {
  888. disabled = false;
  889. }
  890. if (disabled && !forceDisabledEvaluation)
  891. {
  892. GUICommandType commandType = command->getCommandType();
  893. if( commandType != GUI_COMMAND_SELL &&
  894. commandType != GUI_COMMAND_EVACUATE &&
  895. commandType != GUI_COMMAND_EXIT_CONTAINER &&
  896. commandType != GUI_COMMAND_BEACON_DELETE &&
  897. commandType != GUI_COMMAND_SET_RALLY_POINT &&
  898. commandType != GUI_COMMAND_STOP &&
  899. commandType != GUI_COMMAND_SWITCH_WEAPON )
  900. {
  901. if( getCommandAvailability( command, obj, win, applyToWin, TRUE ) == COMMAND_HIDDEN )
  902. {
  903. return COMMAND_HIDDEN;
  904. }
  905. return COMMAND_RESTRICTED;
  906. }
  907. }
  908. // if the command requires an upgrade and we don't have it we can't do it
  909. if( BitTest( command->getOptions(), NEED_UPGRADE ) )
  910. {
  911. const UpgradeTemplate *upgradeT = command->getUpgradeTemplate();
  912. if (upgradeT)
  913. {
  914. // upgrades come in the form of player upgrades and object upgrades
  915. if( upgradeT->getUpgradeType() == UPGRADE_TYPE_PLAYER )
  916. {
  917. if( player->hasUpgradeComplete( upgradeT ) == FALSE )
  918. return COMMAND_RESTRICTED;
  919. }
  920. else if( upgradeT->getUpgradeType() == UPGRADE_TYPE_OBJECT &&
  921. obj->hasUpgrade( upgradeT ) == FALSE )
  922. {
  923. return COMMAND_RESTRICTED;
  924. }
  925. }
  926. }
  927. ProductionUpdateInterface *pu = obj->getProductionUpdateInterface();
  928. if( pu && pu->firstProduction() && BitTest( command->getOptions(), NOT_QUEUEABLE ) )
  929. {
  930. //This button is designated so that it is incapable of building this upgrade/object
  931. //when anything is in the production queue.
  932. return COMMAND_RESTRICTED;
  933. }
  934. Bool queueMaxed = pu ? ( pu->getProductionCount() == MAX_BUILD_QUEUE_BUTTONS ) : FALSE;
  935. switch( command->getCommandType() )
  936. {
  937. case GUI_COMMAND_DOZER_CONSTRUCT:
  938. {
  939. const ThingTemplate * whatToBuild = command->getThingTemplate();
  940. // if the command is a dozer construct task and the object dozer is building anything
  941. // this command is not available
  942. if(whatToBuild)
  943. {
  944. BuildableStatus bStatus = whatToBuild->getBuildable();
  945. if (bStatus == BSTATUS_NO || (bStatus == BSTATUS_ONLY_BY_AI && obj->getControllingPlayer()->getPlayerType() != PLAYER_COMPUTER))
  946. return COMMAND_HIDDEN;
  947. }
  948. // sanity, non dozer object
  949. if( obj->isKindOf( KINDOF_DOZER ) == FALSE )
  950. return COMMAND_RESTRICTED;
  951. // get the dozer ai update interface
  952. DozerAIInterface* dozerAI = NULL;
  953. if( obj->getAIUpdateInterface() == NULL )
  954. return COMMAND_RESTRICTED;
  955. dozerAI = obj->getAIUpdateInterface()->getDozerAIInterface();
  956. DEBUG_ASSERTCRASH( dozerAI != NULL, ("Something KINDOF_DOZER must have a Dozer-like AIUpdate") );
  957. if( dozerAI == NULL )
  958. return COMMAND_RESTRICTED;
  959. // if building anything at all right now we can't build another
  960. if( dozerAI->isTaskPending( DOZER_TASK_BUILD ) == TRUE )
  961. return COMMAND_RESTRICTED;
  962. // return whether or not the player can build this thing
  963. if( player->canBuild( whatToBuild ) == FALSE )
  964. return COMMAND_RESTRICTED;
  965. if( !player->canAffordBuild( whatToBuild ) )
  966. {
  967. return COMMAND_RESTRICTED;//COMMAND_CANT_AFFORD;
  968. }
  969. break;
  970. }
  971. case GUI_COMMAND_SELL:
  972. {
  973. // if this is a sell command, is the object marked as "This cannot be sold?"
  974. // if so, remove the button, otherwise, its available
  975. if (obj->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNSELLABLE))
  976. return COMMAND_HIDDEN;
  977. //since the container can be subdued, , M Lorenzen 8/11
  978. if ( obj->isDisabledByType( DISABLED_SUBDUED ) )
  979. return COMMAND_RESTRICTED;
  980. break;
  981. }
  982. case GUI_COMMAND_UNIT_BUILD:
  983. {
  984. // command is a unit build
  985. if(command->getThingTemplate())
  986. {
  987. BuildableStatus bStatus = command->getThingTemplate()->getBuildable();
  988. if (bStatus == BSTATUS_NO || (bStatus == BSTATUS_ONLY_BY_AI && obj->getControllingPlayer()->getPlayerType() != PLAYER_COMPUTER))
  989. return COMMAND_HIDDEN;
  990. }
  991. if( queueMaxed )
  992. {
  993. return COMMAND_RESTRICTED;
  994. }
  995. // return whether or not the player can build this thing
  996. //NOTE: Player::canBuild() only checks prerequisites!
  997. if( player->canBuild( command->getThingTemplate() ) == FALSE )
  998. return COMMAND_RESTRICTED;
  999. CanMakeType makeType = TheBuildAssistant->canMakeUnit( obj, command->getThingTemplate() );
  1000. if( makeType == CANMAKE_MAXED_OUT_FOR_PLAYER || makeType == CANMAKE_PARKING_PLACES_FULL )
  1001. {
  1002. //Disable the button if the player has a max amount of these units in build queue or existance.
  1003. return COMMAND_RESTRICTED;
  1004. }
  1005. if( makeType == CANMAKE_NO_MONEY )
  1006. {
  1007. return COMMAND_RESTRICTED; //COMMAND_CANT_AFFORD;
  1008. }
  1009. break;
  1010. }
  1011. case GUI_COMMAND_PLAYER_UPGRADE:
  1012. {
  1013. if( queueMaxed )
  1014. {
  1015. return COMMAND_RESTRICTED;
  1016. }
  1017. // if we can build it, we must also NOT already have it or be building it
  1018. if( player->hasUpgradeComplete( command->getUpgradeTemplate() ) == TRUE ||
  1019. player->hasUpgradeInProduction( command->getUpgradeTemplate() ) == TRUE )
  1020. return COMMAND_CANT_AFFORD;//COMMAND_RESTRICTED;
  1021. // if this is an upgrade create we must be able to build it.
  1022. if( TheUpgradeCenter->canAffordUpgrade( player, command->getUpgradeTemplate() ) == FALSE )
  1023. return COMMAND_RESTRICTED;//COMMAND_CANT_AFFORD;
  1024. for( Int i = 0; i < command->getScienceVec().size(); i++ )
  1025. {
  1026. ScienceType st = command->getScienceVec()[ i ];
  1027. if( !player->hasScience( st ) )
  1028. {
  1029. return COMMAND_RESTRICTED;
  1030. }
  1031. }
  1032. break;
  1033. }
  1034. case GUI_COMMAND_OBJECT_UPGRADE:
  1035. {
  1036. if( queueMaxed )
  1037. {
  1038. return COMMAND_RESTRICTED;
  1039. }
  1040. // no production update, can't possibly do this command
  1041. if( pu == NULL )
  1042. {
  1043. DEBUG_CRASH(("Objects that have Object-Level Upgrades must also have ProductionUpdate. Just cuz."));
  1044. return COMMAND_RESTRICTED;
  1045. }
  1046. //
  1047. // if this object already has this upgrade, or is researching it already in the queue
  1048. // we will disable the button so you can't build another one
  1049. //
  1050. if( obj->hasUpgrade( command->getUpgradeTemplate() ) == TRUE ||
  1051. pu->isUpgradeInQueue( command->getUpgradeTemplate() ) == TRUE ||
  1052. obj->affectedByUpgrade( command->getUpgradeTemplate() ) == FALSE )
  1053. return COMMAND_CANT_AFFORD;//COMMAND_RESTRICTED;
  1054. if( TheUpgradeCenter->canAffordUpgrade( player, command->getUpgradeTemplate() ) == FALSE )
  1055. return COMMAND_RESTRICTED;//COMMAND_CANT_AFFORD;
  1056. for( Int i = 0; i < command->getScienceVec().size(); i++ )
  1057. {
  1058. ScienceType st = command->getScienceVec()[ i ];
  1059. if( !player->hasScience( st ) )
  1060. {
  1061. return COMMAND_RESTRICTED;
  1062. }
  1063. }
  1064. break;
  1065. }
  1066. case GUI_COMMAND_FIRE_WEAPON:
  1067. {
  1068. AIUpdateInterface *ai = obj->getAIUpdateInterface();
  1069. // no ai, can't possibly fire weapon
  1070. if( ai == NULL )
  1071. return COMMAND_RESTRICTED;
  1072. // ask the ai if the weapon is ready to fire
  1073. const Weapon* w = obj->getWeaponInWeaponSlot( command->getWeaponSlot() );
  1074. // changed this to Log rather than Crash, because this can legitimately happen now for
  1075. // dozers and workers with mine-clearing stuff... (srj)
  1076. //DEBUG_ASSERTLOG( w, ("Unit %s's CommandButton %s is trying to access weaponslot %d, but doesn't have a weapon there in its FactionUnit ini entry.\n",
  1077. // obj->getTemplate()->getName().str(), command->getName().str(), (Int)command->getWeaponSlot() ) );
  1078. UnsignedInt now = TheGameLogic->getFrame();
  1079. /// @Kris -- We need to show the button as always available for anything with a 0 clip reload time.
  1080. if( w && w->getClipReloadTime( obj ) == 0 )
  1081. {
  1082. return COMMAND_AVAILABLE;
  1083. }
  1084. if( w == NULL // No weapon
  1085. || w->getStatus() != READY_TO_FIRE // Weapon not ready
  1086. || w->getPossibleNextShotFrame() == now // Weapon ready, but could fire this exact frame (handle button flicker since it may be going to fire anyway)
  1087. /// @todo srj -- not sure why this next check is necessary, but the Comanche missile buttons will flicker without it. figure out someday.
  1088. /// @todo ml -- and note: that the "now-1" below causes zero-clip-reload weapons to never be ready, so I added this
  1089. /// If you make changes to this code, make sure that the DragonTank's firewall weapon can be retargeted while active,
  1090. /// that is, while the tank is squirting out flames all over the floor, you can click the firewall button (or "F"),
  1091. /// and re-target the firewall without having to stop or move in-betwen.. Thanks for reading
  1092. || (w->getPossibleNextShotFrame()==now-1)
  1093. )
  1094. {
  1095. if ( w != NULL )
  1096. {
  1097. // only draw the clock when reloading a clip, not when merely between shots, since that's usually a tiny amount of time
  1098. if ( w->getStatus() == RELOADING_CLIP)
  1099. {
  1100. Int percent = w->getPercentReadyToFire() * 100;
  1101. GadgetButtonDrawInverseClock( applyToWin, percent, m_buildUpClockColor );
  1102. }
  1103. return COMMAND_NOT_READY;
  1104. }
  1105. else
  1106. {
  1107. // if this is a mine-clearing button but we don't have the right weaponset,
  1108. // just declare it available... we'll switch weaponsets when the time comes
  1109. if (
  1110. (command->getOptions() & USES_MINE_CLEARING_WEAPONSET) != 0
  1111. && !obj->testWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL)
  1112. )
  1113. {
  1114. return COMMAND_AVAILABLE;
  1115. }
  1116. // no weapon in the slot means "gray me out"
  1117. return COMMAND_RESTRICTED;
  1118. }
  1119. }
  1120. break;
  1121. }
  1122. case GUI_COMMAND_GUARD:
  1123. case GUI_COMMAND_GUARD_WITHOUT_PURSUIT:
  1124. case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY:
  1125. // always available
  1126. break;
  1127. case GUI_COMMAND_COMBATDROP:
  1128. {
  1129. if( getRappellerCount(obj) <= 0 )
  1130. return COMMAND_RESTRICTED;
  1131. break;
  1132. }
  1133. case GUI_COMMAND_EXIT_CONTAINER:
  1134. {
  1135. //
  1136. // this method is really used as a per frame update to see if we should enable
  1137. // disable a control ... inventory of objects shows as buttons have that enable
  1138. // disable logic handled elsewhere, where if the contained count of the entire
  1139. // container changes the UI is completely repopulated
  1140. //
  1141. //since the container can be subdued, the above is no longer true, M Lorenzen 8/11
  1142. if ( obj->isDisabledByType( DISABLED_SUBDUED ) )
  1143. return COMMAND_RESTRICTED;
  1144. break;
  1145. }
  1146. case GUI_COMMAND_EVACUATE:
  1147. {
  1148. // if we have no contained objects we can't evacuate anything
  1149. if( !obj->getContain() || obj->getContain()->getContainCount() <= 0 )
  1150. return COMMAND_RESTRICTED;
  1151. if ( obj->isDisabledByType( DISABLED_SUBDUED ) )
  1152. return COMMAND_RESTRICTED;
  1153. break;
  1154. }
  1155. case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT:
  1156. {
  1157. DockUpdateInterface *dui = obj->getDockUpdateInterface();
  1158. // if the dock is closed or not present this command is invalid
  1159. if( dui == NULL || dui->isDockOpen() == FALSE )
  1160. return COMMAND_RESTRICTED;
  1161. break;
  1162. }
  1163. case GUI_COMMAND_SPECIAL_POWER:
  1164. case GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT:
  1165. case GUI_COMMAND_SPECIAL_POWER_CONSTRUCT:
  1166. case GUI_COMMAND_SPECIAL_POWER_CONSTRUCT_FROM_SHORTCUT:
  1167. {
  1168. // sanity
  1169. DEBUG_ASSERTCRASH( command->getSpecialPowerTemplate() != NULL,
  1170. ("The special power in the command '%s' is NULL\n", command->getName().str()) );
  1171. // get special power module from the object to execute it
  1172. SpecialPowerModuleInterface *mod = obj->getSpecialPowerModule( command->getSpecialPowerTemplate() );
  1173. if( mod == NULL )
  1174. {
  1175. // sanity ... we must have a module for the special power, if we don't somebody probably
  1176. // forgot to put it in the object
  1177. DEBUG_CRASH(( "Object %s does not contain special power module (%s) to execute. Did you forget to add it to the object INI?\n",
  1178. obj->getTemplate()->getName().str(), command->getSpecialPowerTemplate()->getName().str() ));
  1179. }
  1180. else if( mod->isReady() == FALSE )
  1181. {
  1182. Int percent = mod->getPercentReady() * 100;
  1183. GadgetButtonDrawInverseClock( applyToWin, percent, m_buildUpClockColor );
  1184. return COMMAND_NOT_READY;
  1185. }
  1186. else if( SpecialAbilityUpdate *spUpdate = obj->findSpecialAbilityUpdate( command->getSpecialPowerTemplate()->getSpecialPowerType() ) )
  1187. {
  1188. if( spUpdate && spUpdate->isPowerCurrentlyInUse( command ) )
  1189. {
  1190. return COMMAND_RESTRICTED;
  1191. }
  1192. }
  1193. else if( mod->getSpecialPowerTemplate()->getSpecialPowerType() == SPECIAL_CHANGE_BATTLE_PLANS )
  1194. {
  1195. static NameKeyType key_BattlePlanUpdate = NAMEKEY( "BattlePlanUpdate" );
  1196. BattlePlanUpdate *update = (BattlePlanUpdate*)obj->findUpdateModule( key_BattlePlanUpdate );
  1197. if( update && update->getCommandOption() & command->getOptions() )
  1198. {
  1199. return COMMAND_ACTIVE;
  1200. }
  1201. }
  1202. break;
  1203. }
  1204. case GUI_COMMAND_TOGGLE_OVERCHARGE:
  1205. {
  1206. OverchargeBehaviorInterface *obi;
  1207. // search object behavior mdoules
  1208. for( BehaviorModule **bmi = obj->getBehaviorModules(); *bmi; ++bmi )
  1209. {
  1210. // we're looking for the overcharge interface
  1211. obi = (*bmi)->getOverchargeBehaviorInterface();
  1212. if( obi )
  1213. {
  1214. if( obi->isOverchargeActive() )
  1215. return COMMAND_ACTIVE;
  1216. }
  1217. }
  1218. break;
  1219. }
  1220. // switch weapon command
  1221. case GUI_COMMAND_SWITCH_WEAPON:
  1222. {
  1223. // ask the ai which weapon is in the current slot
  1224. const Weapon* w = obj->getWeaponInWeaponSlot( command->getWeaponSlot() );
  1225. DEBUG_ASSERTCRASH( w, ("Unit %s's CommandButton %s is trying to access weaponslot %d, but doesn't have a weapon there in its FactionUnit ini entry.",
  1226. obj->getTemplate()->getName().str(), command->getName().str(), (Int)command->getWeaponSlot() ) );
  1227. if( w == NULL)
  1228. return COMMAND_RESTRICTED;
  1229. const DrawableList *selected = TheInGameUI->getAllSelectedDrawables();
  1230. for( DrawableListCIt it = selected->begin(); it != selected->end(); ++it )
  1231. {
  1232. Drawable *draw = *it;
  1233. if( draw && draw->getObject() && draw->getObject()->isLocallyControlled() && draw->getObject()->getCurrentWeapon())
  1234. {
  1235. WeaponSlotType wslot = draw->getObject()->getCurrentWeapon()->getWeaponSlot();
  1236. if (wslot != command->getWeaponSlot())
  1237. return COMMAND_AVAILABLE;
  1238. }
  1239. }
  1240. return COMMAND_ACTIVE;
  1241. }
  1242. case GUI_COMMAND_HACK_INTERNET:
  1243. {
  1244. AIUpdateInterface *ai = obj->getAI();
  1245. if( ai )
  1246. {
  1247. HackInternetAIInterface *hackAI = ai->getHackInternetAIInterface();
  1248. if( hackAI && hackAI->isHackingPackingOrUnpacking() )
  1249. {
  1250. return COMMAND_RESTRICTED;
  1251. }
  1252. }
  1253. return COMMAND_AVAILABLE;
  1254. }
  1255. case GUI_COMMAND_STOP:
  1256. {
  1257. if( !BitTest( command->getOptions(), OPTION_ONE ) )
  1258. {
  1259. return COMMAND_AVAILABLE;
  1260. }
  1261. //We're dealing with a strategy center stop button. Only show the button
  1262. //if we're in bombardment mode (to stop the artillery cannon).
  1263. static NameKeyType key_BattlePlanUpdate = NAMEKEY( "BattlePlanUpdate" );
  1264. BattlePlanUpdate *bpUpdate = (BattlePlanUpdate*)obj->findUpdateModule( key_BattlePlanUpdate );
  1265. if( bpUpdate && bpUpdate->getActiveBattlePlan() != PLANSTATUS_BOMBARDMENT )
  1266. {
  1267. return COMMAND_RESTRICTED;
  1268. }
  1269. return COMMAND_AVAILABLE;
  1270. }
  1271. case GUI_COMMAND_SELECT_ALL_UNITS_OF_TYPE:
  1272. {
  1273. //We can *always* select a unit :)
  1274. return COMMAND_AVAILABLE;
  1275. }
  1276. }
  1277. // all is well with the command
  1278. return COMMAND_AVAILABLE;
  1279. } // end getCommandAvailability