PlaceEventTranslator.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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: PlaceEventTranslator.cpp ///////////////////////////////////////////////////////////
  24. // Author: Steven Johnson, Dec 2001
  25. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  26. #include "Common/BuildAssistant.h"
  27. #include "Common/GameAudio.h"
  28. #include "Common/Player.h"
  29. #include "Common/PlayerList.h"
  30. #include "Common/SpecialPower.h"
  31. #include "Common/ThingTemplate.h"
  32. #include "GameClient/CommandXlat.h"
  33. #include "GameClient/ControlBar.h"
  34. #include "GameClient/Drawable.h"
  35. #include "GameClient/Eva.h"
  36. #include "GameClient/PlaceEventTranslator.h"
  37. #include "GameLogic/GameLogic.h"
  38. #include "GameLogic/Object.h"
  39. #include "GameLogic/Module/ProductionUpdate.h"
  40. #ifdef _INTERNAL
  41. // for occasional debugging...
  42. //#pragma optimize("", off)
  43. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  44. #endif
  45. //-------------------------------------------------------------------------------------------------
  46. PlaceEventTranslator::PlaceEventTranslator() : m_frameOfUpButton(-1)
  47. {
  48. }
  49. //-------------------------------------------------------------------------------------------------
  50. PlaceEventTranslator::~PlaceEventTranslator()
  51. {
  52. }
  53. //-------------------------------------------------------------------------------------------------
  54. /** Translator to process raw input messages into the "place something" message(s) */
  55. //-------------------------------------------------------------------------------------------------
  56. GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMessage *msg)
  57. {
  58. GameMessageDisposition disp = KEEP_MESSAGE;
  59. switch(msg->getType())
  60. {
  61. //---------------------------------------------------------------------------------------------
  62. case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN:
  63. {
  64. // if we're in a building placement mode, do the place and send to all players
  65. const ThingTemplate *build = TheInGameUI->getPendingPlaceType();
  66. if( build && TheInGameUI->isPlacementAnchored() == FALSE )
  67. {
  68. ICoord2D mouse = msg->getArgument(0)->pixel;
  69. Coord3D world;
  70. // translate mouse position to world position
  71. TheTacticalView->screenToTerrain( &mouse, &world );
  72. //
  73. // placing things causes a dozer to go over and build it ... get the dozer in question
  74. // from the in game UI
  75. //
  76. Object *builderObject = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() );
  77. // if our source object is gone cancel this whole placement process
  78. if( builderObject == NULL )
  79. {
  80. TheInGameUI->placeBuildAvailable( NULL, NULL );
  81. break;
  82. } // end if
  83. // set this location as the placement anchor
  84. TheInGameUI->setPlacementStart( &mouse );
  85. /*
  86. //
  87. // This block of code checks for valid placement on a down mouse click, but since we can
  88. // rotate a building into a valid location, this check prevents us from placing things
  89. // down in some legal locations
  90. //
  91. // get the type of thing we want to build
  92. const ThingTemplate *whatToBuild = TheInGameUI->getPendingPlaceType();
  93. //
  94. // if the spot at which they choose to place this thing is illegal we won't start
  95. // the placement anchor, instead we play a "can't do that" sound
  96. //
  97. LegalBuildCode lbc;
  98. lbc = TheBuildAssistant->isLocationLegalToBuild( &world,
  99. whatToBuild,
  100. TheInGameUI->getPlacementAngle(),
  101. BuildAssistant::USE_QUICK_PATHFIND |
  102. BuildAssistant::TERRAIN_RESTRICTIONS |
  103. BuildAssistant::CLEAR_PATH |
  104. BuildAssistant::NO_OBJECT_OVERLAP,
  105. builderObject );
  106. if( lbc != LBC_OK )
  107. {
  108. static const Sound *noCanDoSound = TheAudio->Sounds->getSound( "NoCanDoSound" );
  109. // play a can't do that sound
  110. TheAudio->Sounds->playSound( noCanDoSound );
  111. // display a message to the user as to why you can't build there
  112. TheInGameUI->displayCantBuildMessage( lbc );
  113. } // end if
  114. else
  115. {
  116. // start placement anchor
  117. TheInGameUI->setPlacementStart(&mouse);
  118. } // end else
  119. */
  120. // used the input
  121. disp = DESTROY_MESSAGE;
  122. }
  123. break;
  124. }
  125. //---------------------------------------------------------------------------------------------
  126. case GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK:
  127. case GameMessage::MSG_MOUSE_LEFT_CLICK:
  128. {
  129. // if we're in a building placement mode, do the place and send to all players
  130. const ThingTemplate *build = TheInGameUI->getPendingPlaceType();
  131. // ... and also remove any radius cursor that is active.
  132. // (srj sez: not sure if this is always necessary... more of a failsafe to make it go away.)
  133. TheInGameUI->setRadiusCursorNone();
  134. if (build && TheInGameUI->isPlacementAnchored())
  135. {
  136. GameMessage *placeMsg;
  137. // Player *player = ThePlayerList->getLocalPlayer();
  138. Coord3D world;
  139. Real angle;
  140. ICoord2D anchorStart, anchorEnd;
  141. Bool isLineBuild = TheBuildAssistant->isLineBuildTemplate( build );
  142. // get the angle of the drawable at the cursor to use as the initial angle
  143. angle = TheInGameUI->getPlacementAngle();
  144. // get start point from the anchor arrow used to place and select angles
  145. TheInGameUI->getPlacementPoints( &anchorStart, &anchorEnd );
  146. // translate the screen position of start to world target location
  147. TheTacticalView->screenToTerrain( &anchorStart, &world );
  148. // get the source object ID of the thing that is "building" the object
  149. ObjectID builderID = INVALID_ID;
  150. Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() );
  151. if( builderObj )
  152. builderID = builderObj->getID();
  153. //Kris: September 27, 2002
  154. //Make sure we have enough CASH to build it! It's possible that between the
  155. //time we initiated it and the time we confirm it, a hacker has stolen some of
  156. //our cash!
  157. CanMakeType cmt = TheBuildAssistant->canMakeUnit( builderObj, build );
  158. if( cmt != CANMAKE_OK )
  159. {
  160. if (cmt == CANMAKE_NO_MONEY)
  161. {
  162. TheEva->setShouldPlay(EVA_InsufficientFunds);
  163. TheInGameUI->message( "GUI:NotEnoughMoneyToBuild" );
  164. break;
  165. }
  166. else if (cmt == CANMAKE_QUEUE_FULL)
  167. {
  168. TheInGameUI->message( "GUI:ProductionQueueFull" );
  169. break;
  170. }
  171. else if (cmt == CANMAKE_PARKING_PLACES_FULL)
  172. {
  173. TheInGameUI->message( "GUI:ParkingPlacesFull" );
  174. break;
  175. }
  176. else if( cmt == CANMAKE_MAXED_OUT_FOR_PLAYER )
  177. {
  178. TheInGameUI->message( "GUI:UnitMaxedOut" );
  179. break;
  180. }
  181. // get out of pending placement mode, this will also clear the arrow anchor status
  182. TheInGameUI->placeBuildAvailable( NULL, NULL );
  183. break;
  184. }
  185. // check to see if this is a legal location to build something at
  186. LegalBuildCode lbc;
  187. lbc = TheBuildAssistant->isLocationLegalToBuild( &world,
  188. build,
  189. angle,
  190. BuildAssistant::USE_QUICK_PATHFIND |
  191. BuildAssistant::TERRAIN_RESTRICTIONS |
  192. BuildAssistant::CLEAR_PATH |
  193. BuildAssistant::NO_OBJECT_OVERLAP |
  194. BuildAssistant::SHROUD_REVEALED |
  195. BuildAssistant::IGNORE_STEALTHED |
  196. BuildAssistant::FAIL_STEALTHED_WITHOUT_FEEDBACK,
  197. builderObj, NULL );
  198. if( lbc == LBC_OK )
  199. {
  200. //Are we building this structure via the special power system? (special case for sneak attack)
  201. if( builderObj )
  202. {
  203. ProductionUpdateInterface *puInterface = builderObj->getProductionUpdateInterface();
  204. if( puInterface )
  205. {
  206. const CommandButton *commandButton = puInterface->getSpecialPowerConstructionCommandButton();
  207. if( commandButton )
  208. {
  209. //If we get this far, then we aren't going to really build the object using the production update
  210. //interface. Instead, we're going to trigger the special power to create it magically without a
  211. //dozer/worker.
  212. placeMsg = TheMessageStream->appendMessage( GameMessage::MSG_DO_SPECIAL_POWER_AT_LOCATION );
  213. placeMsg->appendIntegerArgument( commandButton->getSpecialPowerTemplate()->getID() ); //The ID of the special power template.
  214. placeMsg->appendLocationArgument( world ); //Position of special to be fired.
  215. placeMsg->appendRealArgument( angle ); //Angle of special to be fired.
  216. placeMsg->appendObjectIDArgument( INVALID_ID ); //There is no object in the way.
  217. placeMsg->appendIntegerArgument( commandButton->getOptions() ); //Command button options.
  218. placeMsg->appendObjectIDArgument( builderObj->getID() ); //The source object responsible for firing the special.
  219. // get out of pending placement mode, this will also clear the arrow anchor status
  220. TheInGameUI->placeBuildAvailable( NULL, NULL );
  221. // used the input
  222. disp = DESTROY_MESSAGE;
  223. m_frameOfUpButton = TheGameLogic->getFrame();
  224. break;
  225. }
  226. }
  227. }
  228. // create the right kind of message
  229. if( isLineBuild )
  230. placeMsg = TheMessageStream->appendMessage( GameMessage::MSG_DOZER_CONSTRUCT_LINE );
  231. else
  232. placeMsg = TheMessageStream->appendMessage( GameMessage::MSG_DOZER_CONSTRUCT );
  233. placeMsg->appendIntegerArgument(build->getTemplateID());
  234. placeMsg->appendLocationArgument(world);
  235. placeMsg->appendRealArgument(angle);
  236. if( isLineBuild )
  237. {
  238. Coord3D worldEnd;
  239. TheTacticalView->screenToTerrain( &anchorEnd, &worldEnd );
  240. placeMsg->appendLocationArgument( worldEnd );
  241. } // end if
  242. pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), placeMsg->getType() );
  243. // get out of pending placement mode, this will also clear the arrow anchor status
  244. TheInGameUI->placeBuildAvailable( NULL, NULL );
  245. } // end if, location legal to build at
  246. else
  247. {
  248. // can't place, display why
  249. TheInGameUI->displayCantBuildMessage( lbc );
  250. //Cannot build here -- play the voice sound from the dozer
  251. AudioEventRTS sound = *builderObj->getTemplate()->getPerUnitSound( "VoiceNoBuild" );
  252. sound.setObjectID( builderObj->getID() );
  253. TheAudio->addAudioEvent( &sound );
  254. // play a can't do that sound (UI beep type sound)
  255. static AudioEventRTS noCanDoSound( "NoCanDoSound" );
  256. TheAudio->addAudioEvent( &noCanDoSound );
  257. // unhook the anchor so they can try again
  258. TheInGameUI->setPlacementStart( NULL );
  259. } // end else
  260. // used the input
  261. disp = DESTROY_MESSAGE;
  262. m_frameOfUpButton = TheGameLogic->getFrame();
  263. }
  264. if (disp == DESTROY_MESSAGE)
  265. TheInGameUI->clearAttackMoveToMode();
  266. break;
  267. }
  268. //---------------------------------------------------------------------------------------------
  269. case GameMessage::MSG_RAW_MOUSE_POSITION:
  270. {
  271. // if a building placement is in progress update the destination position
  272. if (TheInGameUI->isPlacementAnchored())
  273. {
  274. const Int PLACEMENT_DRAG_THRESHOLD_DIST = 5; // in pixels away from anchor point
  275. ICoord2D mouse = msg->getArgument(0)->pixel;
  276. //
  277. // we will only process placement end point sets (clicking, and dragging to set angles)
  278. // if we have moved far enough away from the start point
  279. //
  280. ICoord2D start;
  281. TheInGameUI->getPlacementPoints( &start, NULL );
  282. Int x, y;
  283. x = mouse.x - start.x;
  284. y = mouse.y - start.y;
  285. if( sqrt( (x * x) + (y * y) ) >= PLACEMENT_DRAG_THRESHOLD_DIST )
  286. {
  287. TheInGameUI->setPlacementEnd(&mouse);
  288. disp = DESTROY_MESSAGE;
  289. } // end if
  290. }
  291. break;
  292. }
  293. }
  294. return disp;
  295. }