SelectionInfo.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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. #include "PreRTS.h"
  24. #include "GameLogic/Damage.h"
  25. #include "GameLogic/Module/ContainModule.h"
  26. #include "Common/ActionManager.h"
  27. #include "Common/ThingTemplate.h"
  28. #include "Common/PlayerList.h"
  29. #include "Common/Player.h"
  30. #include "GameClient/SelectionInfo.h"
  31. #include "GameClient/CommandXlat.h"
  32. #include "GameClient/ControlBar.h"
  33. #include "GameClient/GameClient.h"
  34. #include "GameClient/Drawable.h"
  35. #include "GameClient/KeyDefs.h"
  36. #ifdef _INTERNAL
  37. // for occasional debugging...
  38. //#pragma optimize("", off)
  39. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  40. #endif
  41. //-------------------------------------------------------------------------------------------------
  42. SelectionInfo::SelectionInfo() :
  43. currentCountEnemies(0),
  44. currentCountCivilians(0),
  45. currentCountMine(0),
  46. currentCountMineInfantry(0),
  47. currentCountMineBuildings(0),
  48. currentCountFriends(0),
  49. newCountEnemies(0),
  50. newCountCivilians(0),
  51. newCountCrates(0),
  52. newCountMine(0),
  53. newCountMineBuildings(0),
  54. newCountFriends(0),
  55. newCountGarrisonableBuildings(0),
  56. selectEnemies(FALSE),
  57. selectCivilians(FALSE),
  58. selectMine(FALSE),
  59. selectMineBuildings(FALSE),
  60. selectFriends(FALSE)
  61. { }
  62. //-------------------------------------------------------------------------------------------------
  63. PickDrawableStruct::PickDrawableStruct() : drawableListToFill(NULL)
  64. {
  65. //Added By Sadullah Nader
  66. //Initializations inserted
  67. drawableListToFill = FALSE;
  68. //
  69. forceAttackMode = TheInGameUI->isInForceAttackMode();
  70. UnsignedInt pickType = getPickTypesForContext(forceAttackMode);
  71. translatePickTypesToKindof(pickType, kindofsToMatch);
  72. if (!forceAttackMode)
  73. {
  74. kindofsToMatch.set(KINDOF_ALWAYS_SELECTABLE);
  75. }
  76. }
  77. //-------------------------------------------------------------------------------------------------
  78. /**
  79. * Given a list of currently selected things and a list of things that are currently under
  80. * the selection (pointer or drag), generate some useful information about each.
  81. */
  82. extern Bool contextCommandForNewSelection(const DrawableList *currentlySelectedDrawables,
  83. const DrawableList *newlySelectedDrawables,
  84. SelectionInfo *outSelectionInfo,
  85. Bool selectionIsPoint)
  86. {
  87. if (!(currentlySelectedDrawables && newlySelectedDrawables && outSelectionInfo))
  88. return FALSE;
  89. Bool forceFire = TheInGameUI->isInForceAttackMode();
  90. Bool forceMove = TheInGameUI->isInForceMoveToMode();
  91. if (forceFire || forceMove) {
  92. return FALSE;
  93. }
  94. Player *localPlayer = ThePlayerList->getLocalPlayer();
  95. DrawableListCIt it;
  96. for (it = currentlySelectedDrawables->begin(); it != currentlySelectedDrawables->end(); ++it) {
  97. if (!(*it)) {
  98. continue;
  99. }
  100. Object *obj = (*it)->getObject();
  101. if (!obj) {
  102. continue;
  103. }
  104. if (obj->isLocallyControlled()) {
  105. ++outSelectionInfo->currentCountMine;
  106. if (obj->isKindOf(KINDOF_INFANTRY)) {
  107. ++outSelectionInfo->currentCountMineInfantry;
  108. } else if (obj->isKindOf(KINDOF_STRUCTURE)) {
  109. ++outSelectionInfo->currentCountMineBuildings;
  110. }
  111. } else {
  112. Relationship rel = localPlayer->getRelationship(obj->getTeam());
  113. if (rel == ALLIES) {
  114. ++outSelectionInfo->currentCountFriends;
  115. } else if (rel == ENEMIES) {
  116. ++outSelectionInfo->currentCountEnemies;
  117. } else if (rel == NEUTRAL) {
  118. ++outSelectionInfo->currentCountCivilians;
  119. }
  120. }
  121. }
  122. Drawable *newMine = NULL;
  123. Drawable *newFriendly = NULL;
  124. Drawable *newEnemy = NULL;
  125. Drawable *newCivilian = NULL;
  126. for (it = newlySelectedDrawables->begin(); it != newlySelectedDrawables->end(); ++it) {
  127. if (!(*it)) {
  128. continue;
  129. }
  130. Object *obj = (*it)->getObject();
  131. if (!obj) {
  132. continue;
  133. }
  134. if (TheActionManager->canPlayerGarrison(localPlayer, obj, CMD_FROM_PLAYER)) {
  135. ++outSelectionInfo->newCountGarrisonableBuildings;
  136. }
  137. if (obj->isKindOf(KINDOF_CRATE)) {
  138. ++outSelectionInfo->newCountCrates;
  139. }
  140. if (obj->isLocallyControlled()) {
  141. ++outSelectionInfo->newCountMine;
  142. newMine = *it;
  143. if (obj->isKindOf(KINDOF_STRUCTURE)) {
  144. ++outSelectionInfo->newCountMineBuildings;
  145. }
  146. } else {
  147. Relationship rel = localPlayer->getRelationship(obj->getTeam());
  148. if (rel == ALLIES) {
  149. newFriendly = *it;
  150. ++outSelectionInfo->newCountFriends;
  151. } else if (rel == ENEMIES) {
  152. newEnemy = *it;
  153. ++outSelectionInfo->newCountEnemies;
  154. } else if (rel == NEUTRAL) {
  155. newCivilian = *it;
  156. ++outSelectionInfo->newCountCivilians;
  157. }
  158. }
  159. }
  160. DEBUG_ASSERTCRASH(outSelectionInfo->currentCountEnemies <= 1, ("Selection bug. jkmcd"));
  161. DEBUG_ASSERTCRASH(outSelectionInfo->currentCountFriends <= 1, ("Selection bug. jkmcd"));
  162. DEBUG_ASSERTCRASH(outSelectionInfo->currentCountCivilians <= 1, ("Selection bug. jkmcd"));
  163. if (outSelectionInfo->currentCountEnemies > 0) {
  164. // If we have an enemy selected, there are no context sensitive commands
  165. return FALSE;
  166. }
  167. if (outSelectionInfo->currentCountFriends > 0) {
  168. return FALSE;
  169. }
  170. if (outSelectionInfo->currentCountCivilians > 0) {
  171. return FALSE;
  172. }
  173. if (TheGlobalData->m_useAlternateMouse) {
  174. // context sensitive commands never apply when selecting in alternate mouse mode
  175. return FALSE;
  176. }
  177. if (outSelectionInfo->currentCountMine > 0) {
  178. if (outSelectionInfo->newCountEnemies > 0) {
  179. if (outSelectionInfo->newCountEnemies == 1 && selectionIsPoint) {
  180. return TheGameClient->evaluateContextCommand(newEnemy, newEnemy->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
  181. }
  182. return selectionIsPoint;
  183. }
  184. if (outSelectionInfo->newCountMine > 0) {
  185. if (outSelectionInfo->newCountMine == 1 && selectionIsPoint && !TheInGameUI->isInPreferSelectionMode()) {
  186. return TheGameClient->evaluateContextCommand(newMine, newMine->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
  187. }
  188. return FALSE;
  189. }
  190. if (outSelectionInfo->newCountFriends > 0) {
  191. if (outSelectionInfo->newCountFriends == 1 && selectionIsPoint) {
  192. return TheGameClient->evaluateContextCommand(newFriendly, newFriendly->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
  193. }
  194. return FALSE;
  195. }
  196. if (outSelectionInfo->currentCountMineInfantry > 0 && outSelectionInfo->newCountGarrisonableBuildings == 1) {
  197. return TRUE;
  198. }
  199. if (outSelectionInfo->newCountCivilians > 0) {
  200. if (outSelectionInfo->newCountCivilians == 1 && selectionIsPoint) {
  201. return TheGameClient->evaluateContextCommand(newCivilian, newCivilian->getPosition(), CommandTranslator::EVALUATE_ONLY) != GameMessage::MSG_INVALID;
  202. }
  203. return FALSE;
  204. }
  205. if (outSelectionInfo->newCountCrates > 0) {
  206. return (outSelectionInfo->newCountCrates == 1 && selectionIsPoint);
  207. }
  208. }
  209. if (outSelectionInfo->currentCountMine == 0) {
  210. return FALSE;
  211. }
  212. return selectionIsPoint;
  213. }
  214. //-------------------------------------------------------------------------------------------------
  215. UnsignedInt getPickTypesForContext( Bool forceAttackMode )
  216. {
  217. UnsignedInt types = PICK_TYPE_SELECTABLE;
  218. if (forceAttackMode)
  219. types |= PICK_TYPE_FORCEATTACKABLE;
  220. //
  221. // if we have a gui context command that allows for a shrubbery target then we want to
  222. // pick that type too (generally shrubbery aren't pickable cause it would get in
  223. // the way with movement and general selection)
  224. //
  225. const CommandButton *command = TheInGameUI->getGUICommand();
  226. if (command != NULL) {
  227. if (BitTest( command->getOptions(), ALLOW_MINE_TARGET)) {
  228. types |= PICK_TYPE_MINES;
  229. }
  230. if (BitTest( command->getOptions(), ALLOW_SHRUBBERY_TARGET ) ) {
  231. types |= PICK_TYPE_SHRUBBERY;
  232. }
  233. } else {
  234. types |= getPickTypesForCurrentSelection(forceAttackMode);
  235. }
  236. return types;
  237. } // end getPickTypesForContext
  238. //-------------------------------------------------------------------------------------------------
  239. UnsignedInt getPickTypesForCurrentSelection( Bool forceAttackMode )
  240. {
  241. UnsignedInt retVal = 0;
  242. if (!TheInGameUI->areSelectedObjectsControllable()) {
  243. return retVal;
  244. }
  245. const DrawableList *allSelectedDrawables = TheInGameUI->getAllSelectedDrawables();
  246. for (DrawableListCIt cit = allSelectedDrawables->begin(); cit != allSelectedDrawables->end(); ++cit) {
  247. Drawable *draw = *cit;
  248. if (!draw) {
  249. continue;
  250. }
  251. Object *obj = draw->getObject();
  252. if (!obj) {
  253. continue;
  254. }
  255. // srj sez: thanks to new, area-effect disarming, we NO LONGER want to do this...
  256. // if (obj->hasWeaponToDealDamageType(DAMAGE_DISARM)) {
  257. // retVal |= PICK_TYPE_MINES;
  258. // }
  259. if (obj->hasWeaponToDealDamageType(DAMAGE_FLAME) && forceAttackMode ) {
  260. retVal |= PICK_TYPE_SHRUBBERY;
  261. }
  262. // For efficiency.
  263. if (BitTest(retVal, PICK_TYPE_MINES | PICK_TYPE_SHRUBBERY)) {
  264. break;
  265. }
  266. }
  267. return retVal;
  268. }
  269. //-------------------------------------------------------------------------------------------------
  270. void translatePickTypesToKindof(UnsignedInt pickTypes, KindOfMaskType& outMask)
  271. {
  272. if (BitTest(pickTypes, PICK_TYPE_SELECTABLE)) {
  273. outMask.set(KINDOF_SELECTABLE);
  274. }
  275. if (BitTest(pickTypes, PICK_TYPE_SHRUBBERY)) {
  276. outMask.set(KINDOF_SHRUBBERY);
  277. }
  278. if (BitTest(pickTypes, PICK_TYPE_MINES)) {
  279. outMask.set(KINDOF_MINE);
  280. }
  281. if (BitTest(pickTypes, PICK_TYPE_FORCEATTACKABLE)) {
  282. outMask.set(KINDOF_FORCEATTACKABLE);
  283. }
  284. }
  285. //-------------------------------------------------------------------------------------------------
  286. // Given a drawable, add it to an stl list specified by userData.
  287. // Useful for iterateDrawablesInRegion.
  288. Bool addDrawableToList( Drawable *draw, void *userData )
  289. {
  290. PickDrawableStruct *pds = (PickDrawableStruct *) userData;
  291. #if defined(_DEBUG) || defined(_INTERNAL)
  292. if (TheGlobalData->m_allowUnselectableSelection) {
  293. pds->drawableListToFill->push_back(draw);
  294. return TRUE;
  295. }
  296. #endif
  297. if (!pds->drawableListToFill)
  298. return FALSE;
  299. if (!draw->getTemplate()->isAnyKindOf(pds->kindofsToMatch))
  300. return FALSE;
  301. if (!draw->isSelectable())
  302. {
  303. const Object *obj = draw->getObject();
  304. if ( obj && obj->getContainedBy() )//hmm, interesting... he is not selectable but he is contained
  305. {// What we are after here is to propagate the selection the selection ti the container
  306. // if the cobtainer is non-enclosing... see also selectionxlat, in the left_click case
  307. ContainModuleInterface *contain = obj->getContainedBy()->getContain();
  308. Drawable *containDraw = obj->getContainedBy()->getDrawable();
  309. if (contain && ! contain->isEnclosingContainerFor( obj ) && containDraw )
  310. return addDrawableToList( containDraw, userData );
  311. }
  312. else
  313. return FALSE;
  314. }
  315. //Kris: Aug 9, 2003!!! Wow, this bug has been around a LONG time!!
  316. //Basically, it was possible to drag select a single enemy/neutral unit even if you couldn't see it
  317. //including stealthed units.
  318. const Object *obj = draw->getObject();
  319. if( obj )
  320. {
  321. const Player *player = ThePlayerList->getLocalPlayer();
  322. Relationship rel = player->getRelationship( obj->getTeam() );
  323. if( rel == NEUTRAL || rel == ENEMIES )
  324. {
  325. if( obj->getShroudedStatus( player->getPlayerIndex() ) >= OBJECTSHROUD_FOGGED )
  326. {
  327. return FALSE;
  328. }
  329. //If stealthed, no way!
  330. if( obj->testStatus( OBJECT_STATUS_STEALTHED ) && !obj->testStatus( OBJECT_STATUS_DETECTED ) )
  331. {
  332. return FALSE;
  333. }
  334. }
  335. }
  336. pds->drawableListToFill->push_back(draw);
  337. return TRUE;
  338. }