GUICommandTranslator.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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: GUICommandTranslator.cpp /////////////////////////////////////////////////////////////////
  24. // Author: Colin Day, March 2002
  25. // Desc: Translator for commands activated from the selection GUI, such as special unit
  26. // actions, that require additional clicks in the world like selecting a target
  27. // object or location
  28. ///////////////////////////////////////////////////////////////////////////////////////////////////
  29. // USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
  30. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  31. #include "Common/ActionManager.h"
  32. #include "Common/GameCommon.h"
  33. #include "Common/GameAudio.h"
  34. #include "Common/NameKeyGenerator.h"
  35. #include "Common/Player.h"
  36. #include "Common/PlayerList.h"
  37. #include "Common/SpecialPower.h"
  38. #include "Common/ThingTemplate.h"
  39. #include "GameClient/ControlBar.h"
  40. #include "GameClient/Drawable.h"
  41. #include "GameClient/GameText.h"
  42. #include "Common/Geometry.h"
  43. #include "GameClient/GUICommandTranslator.h"
  44. #include "GameClient/CommandXlat.h"
  45. #ifdef _INTERNAL
  46. // for occasional debugging...
  47. //#pragma optimize("", off)
  48. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  49. #endif
  50. // PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
  51. static enum CommandStatus
  52. {
  53. COMMAND_INCOMPLETE = 0,
  54. COMMAND_COMPLETE
  55. };
  56. // PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
  57. PickAndPlayInfo::PickAndPlayInfo()
  58. {
  59. m_air = FALSE;
  60. m_drawTarget = NULL;
  61. m_weaponSlot = NULL;
  62. m_specialPowerType = SPECIAL_INVALID;
  63. }
  64. //-------------------------------------------------------------------------------------------------
  65. //-------------------------------------------------------------------------------------------------
  66. GUICommandTranslator::GUICommandTranslator()
  67. {
  68. }
  69. //-------------------------------------------------------------------------------------------------
  70. //-------------------------------------------------------------------------------------------------
  71. GUICommandTranslator::~GUICommandTranslator()
  72. {
  73. }
  74. //-------------------------------------------------------------------------------------------------
  75. /** Is the object under the mouse position a valid target for the command */
  76. //-------------------------------------------------------------------------------------------------
  77. static Object *validUnderCursor( const ICoord2D *mouse, const CommandButton *command, PickType pickType )
  78. {
  79. Object *pickObj = NULL;
  80. // pick a drawable at the mouse location
  81. Drawable *pick = TheTacticalView->pickDrawable( mouse, FALSE, pickType );
  82. // only continue if there is something there
  83. if( pick && pick->getObject() )
  84. {
  85. Player *player = ThePlayerList->getLocalPlayer();
  86. // get object we picked
  87. pickObj = pick->getObject();
  88. if (!command->isValidObjectTarget(player, pickObj))
  89. pickObj = NULL;
  90. } // end if
  91. return pickObj;
  92. } // end validUnderCursor
  93. //-------------------------------------------------------------------------------------------------
  94. //-------------------------------------------------------------------------------------------------
  95. static CommandStatus doFireWeaponCommand( const CommandButton *command, const ICoord2D *mouse )
  96. {
  97. // sanity
  98. if( command == NULL || mouse == NULL )
  99. return COMMAND_COMPLETE;
  100. //
  101. // for single object selections get the source ID and sanity check for illegal object and
  102. // bail along the way
  103. //
  104. ObjectID sourceID = INVALID_ID;
  105. if( TheInGameUI->getSelectCount() == 1 )
  106. {
  107. Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
  108. // sanity
  109. if( draw == NULL || draw->getObject() == NULL )
  110. return COMMAND_COMPLETE;
  111. // get object id
  112. sourceID = draw->getObject()->getID();
  113. } // end if
  114. // create message and send to the logic
  115. GameMessage *msg;
  116. if( BitTest( command->getOptions(), NEED_TARGET_POS ) )
  117. {
  118. Coord3D world;
  119. // translate the mouse location into world coords
  120. TheTacticalView->screenToTerrain( mouse, &world );
  121. // create the message and append arguments
  122. msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_WEAPON_AT_LOCATION );
  123. msg->appendIntegerArgument( command->getWeaponSlot() );
  124. msg->appendLocationArgument( world );
  125. msg->appendIntegerArgument( command->getMaxShotsToFire() );
  126. //Also append the object ID (incase weapon doesn't like obstacles on land).
  127. Object *target = validUnderCursor( mouse, command, PICK_TYPE_SELECTABLE );
  128. ObjectID targetID = target ? target->getID() : INVALID_ID;
  129. msg->appendObjectIDArgument( targetID );
  130. } // end if
  131. else if( BitTest( command->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) )
  132. {
  133. // setup the pick type ... some commands allow us to target shrubbery
  134. PickType pickType = PICK_TYPE_SELECTABLE;
  135. if( BitTest( command->getOptions(), ALLOW_SHRUBBERY_TARGET ) == TRUE )
  136. pickType = (PickType)((Int)pickType | (Int)PICK_TYPE_SHRUBBERY);
  137. if( BitTest( command->getOptions(), ALLOW_MINE_TARGET ) == TRUE )
  138. pickType = (PickType)((Int)pickType | (Int)PICK_TYPE_MINES);
  139. // get the target object under the cursor
  140. Object *target = validUnderCursor( mouse, command, pickType );
  141. // only continue if the object meets all the command criteria
  142. if( target )
  143. {
  144. msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_WEAPON_AT_OBJECT );
  145. msg->appendIntegerArgument( command->getWeaponSlot() );
  146. msg->appendObjectIDArgument( target->getID() );
  147. msg->appendIntegerArgument( command->getMaxShotsToFire() );
  148. } // end if
  149. } // end else
  150. else
  151. {
  152. msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_WEAPON );
  153. msg->appendIntegerArgument( command->getWeaponSlot() );
  154. msg->appendIntegerArgument( command->getMaxShotsToFire() );
  155. //This could be legit now -- think of firing a self destruct weapon
  156. //-----------------------------------------------------------------
  157. //DEBUG_ASSERTCRASH( 0, ("doFireWeaponCommand: Command options say it doesn't need additional user input '%s'\n",
  158. // command->m_name.str()) );
  159. //return COMMAND_COMPLETE;
  160. } // end else
  161. return COMMAND_COMPLETE;
  162. } // end fire weapon
  163. //-------------------------------------------------------------------------------------------------
  164. //-------------------------------------------------------------------------------------------------
  165. static CommandStatus doGuardCommand( const CommandButton *command, GuardMode guardMode, const ICoord2D *mouse )
  166. {
  167. // sanity
  168. if( command == NULL || mouse == NULL )
  169. return COMMAND_COMPLETE;
  170. if( TheInGameUI->getSelectCount() == 0 )
  171. return COMMAND_COMPLETE;
  172. GameMessage *msg = NULL;
  173. if ( msg == NULL && BitTest( command->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) )
  174. {
  175. // get the target object under the cursor
  176. Object* target = validUnderCursor( mouse, command, PICK_TYPE_SELECTABLE );
  177. if( target )
  178. {
  179. msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_GUARD_OBJECT );
  180. msg->appendObjectIDArgument( target->getID() );
  181. msg->appendIntegerArgument(guardMode);
  182. pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_GUARD_OBJECT);
  183. }
  184. }
  185. if( msg == NULL )
  186. {
  187. Coord3D world;
  188. if (BitTest( command->getOptions(), NEED_TARGET_POS ))
  189. {
  190. // translate the mouse location into world coords
  191. TheTacticalView->screenToTerrain( mouse, &world );
  192. }
  193. else
  194. {
  195. Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
  196. if( draw == NULL || draw->getObject() == NULL )
  197. return COMMAND_COMPLETE;
  198. world = *draw->getObject()->getPosition();
  199. }
  200. // create the message and append arguments
  201. msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_GUARD_POSITION );
  202. msg->appendLocationArgument(world);
  203. msg->appendIntegerArgument(guardMode);
  204. pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_GUARD_POSITION);
  205. }
  206. return COMMAND_COMPLETE;
  207. }
  208. //-------------------------------------------------------------------------------------------------
  209. /** Do the set rally point command */
  210. //-------------------------------------------------------------------------------------------------
  211. static CommandStatus doAttackMoveCommand( const CommandButton *command, const ICoord2D *mouse )
  212. {
  213. // sanity
  214. if( command == NULL || mouse == NULL )
  215. return COMMAND_COMPLETE;
  216. //
  217. // we can only set rally points for structures ... and we never multiple select structures
  218. // so we must be sure there is only one thing selected (that thing we will set the point on)
  219. //
  220. Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
  221. DEBUG_ASSERTCRASH( draw, ("doAttackMoveCommand: No selected object(s)\n") );
  222. // sanity
  223. if( draw == NULL || draw->getObject() == NULL )
  224. return COMMAND_COMPLETE;
  225. // convert mouse point to world coords
  226. Coord3D world;
  227. TheTacticalView->screenToTerrain( mouse, &world );
  228. // send the message to set the rally point
  229. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_DO_ATTACKMOVETO );
  230. msg->appendLocationArgument( world );
  231. // Play the unit voice response
  232. pickAndPlayUnitVoiceResponse(TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_ATTACKMOVETO);
  233. return COMMAND_COMPLETE;
  234. }
  235. //-------------------------------------------------------------------------------------------------
  236. /** Do the set rally point command */
  237. //-------------------------------------------------------------------------------------------------
  238. static CommandStatus doSetRallyPointCommand( const CommandButton *command, const ICoord2D *mouse )
  239. {
  240. // sanity
  241. if( command == NULL || mouse == NULL )
  242. return COMMAND_COMPLETE;
  243. //
  244. // we can only set rally points for structures ... and we never multiple select structures
  245. // so we must be sure there is only one thing selected (that thing we will set the point on)
  246. //
  247. DEBUG_ASSERTCRASH( TheInGameUI->getSelectCount() == 1,
  248. ("doSetRallyPointCommand: The selected count is not 1, we can only set a rally point on a *SINGLE* building\n") );
  249. Drawable *draw = TheInGameUI->getFirstSelectedDrawable();
  250. DEBUG_ASSERTCRASH( draw, ("doSetRallyPointCommand: No selected object\n") );
  251. // sanity
  252. if( draw == NULL || draw->getObject() == NULL )
  253. return COMMAND_COMPLETE;
  254. // convert mouse point to world coords
  255. Coord3D world;
  256. TheTacticalView->screenToTerrain( mouse, &world );
  257. // send the message to set the rally point
  258. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_SET_RALLY_POINT );
  259. msg->appendObjectIDArgument( draw->getObject()->getID() );
  260. msg->appendLocationArgument( world );
  261. return COMMAND_COMPLETE;
  262. } // end doSetRallyPointCommand
  263. //-------------------------------------------------------------------------------------------------
  264. /** Do the beacon placement command */
  265. //-------------------------------------------------------------------------------------------------
  266. static CommandStatus doPlaceBeacon( const CommandButton *command, const ICoord2D *mouse )
  267. {
  268. // sanity
  269. if( command == NULL || mouse == NULL )
  270. return COMMAND_COMPLETE;
  271. // convert mouse point to world coords
  272. Coord3D world;
  273. TheTacticalView->screenToTerrain( mouse, &world );
  274. // send the message to set the rally point
  275. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_PLACE_BEACON );
  276. msg->appendLocationArgument( world );
  277. return COMMAND_COMPLETE;
  278. } // end doPlaceBeacon
  279. //-------------------------------------------------------------------------------------------------
  280. //-------------------------------------------------------------------------------------------------
  281. GameMessageDisposition GUICommandTranslator::translateGameMessage(const GameMessage *msg)
  282. {
  283. GameMessageDisposition disp = KEEP_MESSAGE;
  284. // only pay attention to clicks in this translator if there is a pending GUI command
  285. const CommandButton *command = TheInGameUI->getGUICommand();
  286. if( command == NULL )
  287. return disp;
  288. switch( msg->getType() )
  289. {
  290. //---------------------------------------------------------------------------------------------
  291. case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
  292. {
  293. //
  294. //
  295. // it is necessary to use this input when there is a pending gui command, we don't wan't
  296. // it to fall through to the rest of the system when we're in pending gui command "mode"
  297. // because things like selection rectangles will start when we want to stay totally
  298. // within the gui command "mode" here
  299. //
  300. disp = DESTROY_MESSAGE;
  301. break;
  302. } // end left mouse down
  303. //---------------------------------------------------------------------------------------------
  304. case GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK:
  305. case GameMessage::MSG_MOUSE_LEFT_CLICK:
  306. {
  307. CommandStatus commandStatus = COMMAND_COMPLETE;
  308. ICoord2D mouse = msg->getArgument(0)->pixelRegion.hi;
  309. // do the command action
  310. if( command && !command->isContextCommand() )
  311. {
  312. switch( command->getCommandType() )
  313. {
  314. //---------------------------------------------------------------------------------------
  315. case GUI_COMMAND_FIRE_WEAPON:
  316. {
  317. commandStatus = doFireWeaponCommand( command, &mouse );
  318. PickAndPlayInfo info;
  319. WeaponSlotType slot = command->getWeaponSlot();
  320. info.m_weaponSlot = &slot;
  321. pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_DO_WEAPON_AT_LOCATION, &info );
  322. break;
  323. } // end fire weapon command
  324. //---------------------------------------------------------------------------------------
  325. case GUI_COMMAND_EVACUATE:
  326. {
  327. if (BitTest(command->getOptions(), NEED_TARGET_POS)) {
  328. Coord3D worldPos;
  329. TheTacticalView->screenToTerrain(&mouse, &worldPos);
  330. GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_EVACUATE);
  331. msg->appendLocationArgument(worldPos);
  332. pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_EVACUATE );
  333. commandStatus = COMMAND_COMPLETE;
  334. }
  335. break;
  336. }
  337. //---------------------------------------------------------------------------------------
  338. case GUI_COMMAND_GUARD:
  339. {
  340. commandStatus = doGuardCommand( command, GUARDMODE_NORMAL, &mouse );
  341. break;
  342. }
  343. //---------------------------------------------------------------------------------------
  344. case GUI_COMMAND_GUARD_WITHOUT_PURSUIT:
  345. {
  346. commandStatus = doGuardCommand( command, GUARDMODE_GUARD_WITHOUT_PURSUIT, &mouse );
  347. break;
  348. }
  349. //---------------------------------------------------------------------------------------
  350. case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY:
  351. {
  352. commandStatus = doGuardCommand( command, GUARDMODE_GUARD_FLYING_UNITS_ONLY, &mouse );
  353. break;
  354. }
  355. //Special weapons are now always context commands...
  356. //---------------------------------------------------------------------------------------
  357. case GUI_COMMAND_SPECIAL_POWER:
  358. case GUI_COMMAND_SPECIAL_POWER_FROM_SHORTCUT:
  359. {
  360. return KEEP_MESSAGE;
  361. break;
  362. } // end special power
  363. case GUI_COMMAND_ATTACK_MOVE:
  364. {
  365. commandStatus = doAttackMoveCommand( command, &mouse );
  366. break;
  367. }
  368. //---------------------------------------------------------------------------------------
  369. case GUI_COMMAND_SET_RALLY_POINT:
  370. {
  371. commandStatus = doSetRallyPointCommand( command, &mouse );
  372. break;
  373. } // end set rally point
  374. //---------------------------------------------------------------------------------------
  375. case GUICOMMANDMODE_PLACE_BEACON:
  376. {
  377. commandStatus = doPlaceBeacon( command, &mouse );
  378. break;
  379. } // end set rally point
  380. } // end switch
  381. // used the input
  382. disp = DESTROY_MESSAGE;
  383. // get out of GUI command mode if we completed the command one way or another
  384. if( commandStatus == COMMAND_COMPLETE )
  385. {
  386. TheInGameUI->setPreventLeftClickDeselectionInAlternateMouseModeForOneClick( TRUE );
  387. TheInGameUI->setGUICommand( NULL );
  388. }
  389. } // end if
  390. break;
  391. } // end left mouse up
  392. } // end switch
  393. // If we're destroying the message, it means we used it. Therefore, destroy the current
  394. // attack move instruction as well.
  395. if (disp == DESTROY_MESSAGE)
  396. TheInGameUI->clearAttackMoveToMode();
  397. return disp;
  398. } // end translateMessage