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/CommandXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index ac77d3c6017..8ff9751a9ea 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,7 +2701,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // select the unit // create a new 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,7 +2929,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage if(newDrawable != nullptr ) { //deselect other units - TheInGameUI->deselectAllDrawables(); + TheInGameUI->deselectAllDrawables(FALSE); // select the unit // create a new 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 3067a517414..adbcab3c400 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. @@ -230,14 +218,14 @@ static void deselectAll() static Bool selectSingleDrawableWithoutSound( Drawable *draw ) { - // since we are single selecting a drawable, unselect everything else - deselectAll(); - - // 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(FALSE); + + // 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()); @@ -255,6 +243,7 @@ SelectionTranslator *TheSelectionTranslator = nullptr; //----------------------------------------------------------------------------- SelectionTranslator::SelectionTranslator() { + m_pendingDeselection = FALSE; m_leftMouseButtonIsDown = FALSE; m_dragSelecting = FALSE; m_lastGroupSelTime = 0; @@ -409,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 @@ -895,7 +893,8 @@ GameMessageDisposition SelectionTranslator::onMouseLeftClick(MAYBE_UNUSED const { if (!addToGroup) { - deselectAll(); + m_pendingDeselection = FALSE; + TheInGameUI->deselectAllDrawables(FALSE); } GameMessage *newMsg = TheMessageStream->appendMessage(GameMessage::MSG_CREATE_SELECTED_GROUP); @@ -1065,7 +1064,7 @@ GameMessageDisposition SelectionTranslator::onRawMouseLeftButtonUp(MAYBE_UNUSED { if( !TheInGameUI->getPreventLeftClickDeselectionInAlternateMouseModeForOneClick() ) { - deselectAll(); + m_pendingDeselection = TRUE; m_lastGroupSelGroup = -1; } else @@ -1131,7 +1130,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(); } } } @@ -1270,6 +1269,7 @@ GameMessageDisposition SelectionTranslator::onMetaAddTeam(MAYBE_UNUSED const Gam } else { + Bool deselectedAllDrawables = FALSE; Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); if( draw && draw->isKindOf( KINDOF_STRUCTURE ) ) @@ -1278,6 +1278,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. @@ -1292,7 +1293,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(); } 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/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/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 50bf693fc67..3f02f6c5e23 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); + } } } @@ -5897,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 );