From 5d4595cd30b54ea2166665be81cc79c6fdfc6111 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 02:14:46 +0200 Subject: [PATCH 1/7] Added an empty selection check to 'deselectAllDrawables'. --- .../GameEngine/Include/GameClient/InGameUI.h | 2 +- .../GameEngine/Source/GameClient/InGameUI.cpp | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h index 21a5b2d292d..0f35de9bf4e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h @@ -448,7 +448,7 @@ friend class Drawable; // for selection/deselection transactions // Drawable selection mechanisms virtual void selectDrawable( Drawable *draw ); ///< Mark given Drawable as "selected" virtual void deselectDrawable( Drawable *draw ); ///< Clear "selected" status from Drawable - virtual void deselectAllDrawables( Bool postMsg = true ); ///< Clear the "select" flag from all drawables + virtual void deselectAllDrawables( Bool updateGameLogic = true ); ///< Clear the "select" flag from all drawables virtual Int getSelectCount() { return m_selectCount; } ///< Get count of currently selected drawables virtual Int getMaxSelectCount() { return m_maxSelectCount; } ///< Get the max number of selected drawables virtual UnsignedInt getFrameSelectionChanged() { return m_frameSelectionChanged; } ///< Get the max number of selected drawables diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 50bf693fc67..e33a9a3e64f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -3563,9 +3563,10 @@ void InGameUI::deselectDrawable( Drawable *draw ) //------------------------------------------------------------------------------------------------- /** Clear all drawables' "select" status */ //------------------------------------------------------------------------------------------------- -void InGameUI::deselectAllDrawables( Bool postMsg ) +void InGameUI::deselectAllDrawables( Bool updateGameLogic ) { const DrawableList *selected = getAllSelectedDrawables(); + const Bool hadSelectedDrawables = !selected->empty(); // loop through all the selected drawables for ( DrawableListCIt it = selected->begin(); it != selected->end(); ) @@ -3586,16 +3587,14 @@ void InGameUI::deselectAllDrawables( Bool postMsg ) // our selection can no longer consist of exactly one angry mob m_soloNexusSelectedDrawableID = INVALID_DRAWABLE_ID; - - ///@todo don't we want to not emit this message if there wasn't a group at all? (CBD) - /** @todo also, we probably are sending this message too much, we should come up with - some kind of "selections are dirty" status that we can check once per frame and send - the correct group info over the network ... could be tricky tho (or impossible) given - the order of operations of things happening in the code (CBD) */ - if( postMsg ) + if (updateGameLogic) { - // TheSuperHackers @tweak Originally this message had one boolean argument, but it wasn't used for anything. - TheMessageStream->appendMessage( GameMessage::MSG_DESTROY_SELECTED_GROUP ); + // TheSuperHackers @tweak Only send this message when objects were previously selected. + if (hadSelectedDrawables) + { + // TheSuperHackers @tweak Originally this message had one boolean argument, but it wasn't used for anything. + TheMessageStream->appendMessage(GameMessage::MSG_DESTROY_SELECTED_GROUP); + } } } From cb27fd2820df92a001517b5f0361e6b628471fcc Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 02:17:36 +0200 Subject: [PATCH 2/7] Removed 'deselectAll'. --- .../MessageStream/SelectionXlat.cpp | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index 3067a517414..7f59daf3c52 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -210,18 +210,6 @@ static Bool canSelectWrapper( Drawable *draw, void *userData ) return CanSelectDrawable( draw, dragSelecting ); } -//----------------------------------------------------------------------------- -/** - * Deselect all drawables, and emit a "TEAM_DESTROY" message, since - * the "team" was the group of currently selected units. - */ -static void deselectAll() -{ - - // deselect it all - TheInGameUI->deselectAllDrawables(); -} - //----------------------------------------------------------------------------- /** * Select the given drawable, without playing its sound. @@ -231,7 +219,7 @@ static Bool selectSingleDrawableWithoutSound( Drawable *draw ) { // since we are single selecting a drawable, unselect everything else - deselectAll(); + TheInGameUI->deselectAllDrawables(); // do the drawable selection TheInGameUI->selectDrawable( draw ); @@ -895,7 +883,7 @@ GameMessageDisposition SelectionTranslator::onMouseLeftClick(MAYBE_UNUSED const { if (!addToGroup) { - deselectAll(); + TheInGameUI->deselectAllDrawables(); } GameMessage *newMsg = TheMessageStream->appendMessage(GameMessage::MSG_CREATE_SELECTED_GROUP); @@ -1065,7 +1053,7 @@ GameMessageDisposition SelectionTranslator::onRawMouseLeftButtonUp(MAYBE_UNUSED { if( !TheInGameUI->getPreventLeftClickDeselectionInAlternateMouseModeForOneClick() ) { - deselectAll(); + TheInGameUI->deselectAllDrawables(); m_lastGroupSelGroup = -1; } else @@ -1131,7 +1119,7 @@ GameMessageDisposition SelectionTranslator::onRawMouseRightButtonUp(MAYBE_UNUSED else if (!TheGlobalData->m_useAlternateMouse) { //No GUI command mode, so deselect everyone if we're in regular mouse mode. - deselectAll(); + TheInGameUI->deselectAllDrawables(); } } } From b80ccc4f01a97e7494e34173cb1d1d038a548464 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 02:40:41 +0200 Subject: [PATCH 3/7] Refactored 'selectSingleDrawableWithoutSound'. --- .../GameClient/MessageStream/SelectionXlat.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index 7f59daf3c52..57963049c83 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -218,14 +218,14 @@ static Bool canSelectWrapper( Drawable *draw, void *userData ) static Bool selectSingleDrawableWithoutSound( Drawable *draw ) { - // since we are single selecting a drawable, unselect everything else - TheInGameUI->deselectAllDrawables(); - - // do the drawable selection - TheInGameUI->selectDrawable( draw ); - Object *obj = draw->getObject(); if (obj != nullptr) { + // since we are single selecting a drawable, unselect everything else + TheInGameUI->deselectAllDrawables(); + + // do the drawable selection + TheInGameUI->selectDrawable(draw); + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_CREATE_SELECTED_GROUP_NO_SOUND); msg->appendBooleanArgument(TRUE); msg->appendObjectIDArgument(obj->getID()); From ba252d2ce4913d2c37b7b3e40535deb95c69b52b Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 05:25:14 +0200 Subject: [PATCH 4/7] Refactored 'SelectionTranslator::onMetaAddTeam'. --- .../Source/GameClient/MessageStream/SelectionXlat.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index 57963049c83..356d57ccd1b 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -1258,6 +1258,7 @@ GameMessageDisposition SelectionTranslator::onMetaAddTeam(MAYBE_UNUSED const Gam } else { + Bool deselectedAllDrawables = FALSE; Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); if( draw && draw->isKindOf( KINDOF_STRUCTURE ) ) @@ -1266,6 +1267,7 @@ GameMessageDisposition SelectionTranslator::onMetaAddTeam(MAYBE_UNUSED const Gam //Can't select other units if you have a structure selected. So deselect the structure to prevent //group force attack exploit. TheInGameUI->deselectAllDrawables(); + deselectedAllDrawables = TRUE; } // no need to send two messages for selecting the same group. @@ -1280,7 +1282,7 @@ GameMessageDisposition SelectionTranslator::onMetaAddTeam(MAYBE_UNUSED const Gam Int numObjs = objlist.size(); // TheSuperHackers @bugfix skyaero 22/07/2025 Can't select other units if you have a structure selected. So deselect the structure to prevent group force attack exploit. - if (numObjs > 0 && objlist[0]->getDrawable()->isKindOf(KINDOF_STRUCTURE)) + if (!deselectedAllDrawables && numObjs > 0 && objlist[0]->getDrawable()->isKindOf(KINDOF_STRUCTURE)) { TheInGameUI->deselectAllDrawables(); } From 1ba3f6e8b57e392e59d55750ae391573034d33c4 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 05:39:24 +0200 Subject: [PATCH 5/7] Updated callsites of 'InGameUI::deselectAllDrawables'. --- .../GameClient/MessageStream/CommandXlat.cpp | 14 +++++++------- .../GameClient/MessageStream/SelectionXlat.cpp | 4 ++-- .../Source/GameLogic/System/GameLogicDispatch.cpp | 2 +- .../GUI/ControlBar/ControlBarCommandProcessing.cpp | 2 +- .../Code/GameEngine/Source/GameClient/InGameUI.cpp | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index ac77d3c6017..dc33bf56432 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -1255,7 +1255,7 @@ GameMessage::Type CommandTranslator::issueSpecialPowerCommand( const CommandButt if( spUpdate ) { //Deselect the drawables before posting the selection message. - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); //Because we just launched a special power via shortcut, and the special power accepts input //from the player (particle uplink cannon, spectre gunship), simply select the object now. @@ -2585,7 +2585,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); @@ -2701,8 +2701,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - TheInGameUI->deselectAllDrawables(); // select the unit + TheInGameUI->deselectAllDrawables(FALSE); // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); @@ -2814,7 +2814,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // select the unit // create a new group. @@ -2929,8 +2929,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - TheInGameUI->deselectAllDrawables(); // select the unit + TheInGameUI->deselectAllDrawables(FALSE); // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); @@ -2982,7 +2982,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if ( heroDraw == nullptr ) break; - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); @@ -3067,7 +3067,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage /* - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); // creating a new team so pass in true diff --git a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index 356d57ccd1b..beb5b2ad526 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -221,7 +221,7 @@ static Bool selectSingleDrawableWithoutSound( Drawable *draw ) Object *obj = draw->getObject(); if (obj != nullptr) { // since we are single selecting a drawable, unselect everything else - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // do the drawable selection TheInGameUI->selectDrawable(draw); @@ -883,7 +883,7 @@ GameMessageDisposition SelectionTranslator::onMouseLeftClick(MAYBE_UNUSED const { if (!addToGroup) { - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); } GameMessage *newMsg = TheMessageStream->appendMessage(GameMessage::MSG_CREATE_SELECTED_GROUP); diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index c9c7cbb7602..f8e24a13b32 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -828,7 +828,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) if (currentlySelectedGroup && TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == msgPlayer /*&& !TheRecorder->isMultiplayer()*/) { const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs(); - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it) { const Object *obj = findObjectByID(*it); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp index 5e2c41a3d48..e9f3f5664ce 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp @@ -646,7 +646,7 @@ CBCommandStatus ControlBar::processCommandUI( GameWindow *control, } //deselect other units - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index e33a9a3e64f..3f02f6c5e23 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -5896,7 +5896,7 @@ void InGameUI::selectNextIdleWorker() if(selectThisObject) { DEBUG_ASSERTCRASH(selectThisObject->getContainedBy() == nullptr, ("InGameUI::selectNextIdleWorker Selected idle object should not be contained")); - deselectAllDrawables(); + deselectAllDrawables(FALSE); GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); From 74b9ff4e0a06aea0477636b1ad8871a700114bdd Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 06:20:41 +0200 Subject: [PATCH 6/7] Added logic to avoid double deselection on left mouse click. --- Core/GameEngine/Include/GameClient/SelectionXlat.h | 1 + .../GameClient/MessageStream/SelectionXlat.cpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Include/GameClient/SelectionXlat.h b/Core/GameEngine/Include/GameClient/SelectionXlat.h index efc2a775742..1ba1ee74b09 100644 --- a/Core/GameEngine/Include/GameClient/SelectionXlat.h +++ b/Core/GameEngine/Include/GameClient/SelectionXlat.h @@ -42,6 +42,7 @@ class SelectionTranslator : public GameMessageTranslator friend Bool killThemKillThemAllWrapper( Drawable *draw, void *userData ); private: + Bool m_pendingDeselection; Bool m_leftMouseButtonIsDown; Bool m_dragSelecting; UnsignedInt m_lastGroupSelTime; diff --git a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp index beb5b2ad526..adbcab3c400 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp @@ -243,6 +243,7 @@ SelectionTranslator *TheSelectionTranslator = nullptr; //----------------------------------------------------------------------------- SelectionTranslator::SelectionTranslator() { + m_pendingDeselection = FALSE; m_leftMouseButtonIsDown = FALSE; m_dragSelecting = FALSE; m_lastGroupSelTime = 0; @@ -397,6 +398,15 @@ GameMessageDisposition SelectionTranslator::translateGameMessage(const GameMessa case GameMessage::MSG_MOUSE_LEFT_CLICK: { disp = onMouseLeftClick(msg); + + // TheSuperHackers @tweak Avoid double deselection when selecting a new object with another object selected, + // originally triggered by RAW_MOUSE_LEFT_BUTTON_UP and MOUSE_LEFT_CLICK, respectively. + if (m_pendingDeselection) + { + m_pendingDeselection = FALSE; + TheInGameUI->deselectAllDrawables(); + } + break; } // Note that the raw left messages are only used to draw feedback now when @@ -883,6 +893,7 @@ GameMessageDisposition SelectionTranslator::onMouseLeftClick(MAYBE_UNUSED const { if (!addToGroup) { + m_pendingDeselection = FALSE; TheInGameUI->deselectAllDrawables(FALSE); } @@ -1053,7 +1064,7 @@ GameMessageDisposition SelectionTranslator::onRawMouseLeftButtonUp(MAYBE_UNUSED { if( !TheInGameUI->getPreventLeftClickDeselectionInAlternateMouseModeForOneClick() ) { - TheInGameUI->deselectAllDrawables(); + m_pendingDeselection = TRUE; m_lastGroupSelGroup = -1; } else From 219fa730608596e85da3f882b8c81e2898680ec9 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 30 Jun 2026 06:36:00 +0200 Subject: [PATCH 7/7] Misc fixes. --- .../Source/GameClient/MessageStream/CommandXlat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index dc33bf56432..8ff9751a9ea 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -2701,8 +2701,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - // select the unit TheInGameUI->deselectAllDrawables(FALSE); + // select the unit // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); @@ -2929,8 +2929,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - // select the unit TheInGameUI->deselectAllDrawables(FALSE); + // select the unit // create a new group. GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP );