ControlBarCommand.cpp 46 KB

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