From 29796659834437e06549d788f39adfd19e643f25 Mon Sep 17 00:00:00 2001 From: triatomic <32312517+triatomic@users.noreply.github.com> Date: Sun, 23 Aug 2026 01:29:01 +0300 Subject: [PATCH 1/2] feat: Shift click to queue or cancel five units at once Shift clicking a unit cameo queues five copies instead of one, and shift clicking a build queue slot cancels five instead of one. Queueing re-runs canMakeUnit before each additional unit rather than firing five messages blindly. That check covers money, queue space, parking places and per player unit caps, all of which move as the batch is queued, so a shift click on the last affordable unit queues what it can and stops quietly. Only the first unit reports a failure, so the player is not spammed with five identical "not enough money" messages. Cancelling walks the queue forwards from the clicked slot, which takes the most recently queued copies first and leaves the item currently building alone for as long as possible. It only cancels entries of the same production type, so a shift click cannot silently eat unrelated queued items. Each unit still gets its own production id and its own MSG_QUEUE_UNIT_CREATE, so the logic side sees exactly what it would from five separate clicks. No new message type, and nothing about the batch is resolved client side beyond reading the modifier key. --- .../ControlBarCommandProcessing.cpp | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp index 5e2c41a3d48..3b3ca1f83cd 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp @@ -50,8 +50,12 @@ #include "GameClient/GameWindow.h" #include "GameClient/GameWindowManager.h" #include "GameClient/InGameUI.h" +#include "GameClient/Keyboard.h" #include "GameClient/AnimateWindowManager.h" +// TheSuperHackers @feature How many units a shift click queues or cancels at once. +static const Int SHIFT_CLICK_BATCH_SIZE = 5; + #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/Module/ProductionUpdate.h" @@ -440,14 +444,28 @@ CBCommandStatus ControlBar::processCommandUI( GameWindow *control, } - // get a new production id to assign to this - ProductionID productionID = pu->requestUniqueUnitID(); + // TheSuperHackers @feature Shift queues a batch instead of a single unit. + Int unitsToQueue = 1; + if( TheKeyboard && TheKeyboard->isShift() ) + unitsToQueue = SHIFT_CLICK_BATCH_SIZE; + + for( Int queued = 0; queued < unitsToQueue; ++queued ) + { + // Re-check every time round. canMakeUnit covers money, queue space, parking and + // per player unit caps, and each unit we just queued moves those. Stop quietly + // once we can no longer build -- the first unit already reported any problem. + if( queued > 0 && TheBuildAssistant->canMakeUnit( factory, whatToBuild ) != CANMAKE_OK ) + break; + + // get a new production id to assign to this + ProductionID productionID = pu->requestUniqueUnitID(); - // create a message to build this thing + // create a message to build this thing - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_QUEUE_UNIT_CREATE ); - msg->appendIntegerArgument( whatToBuild->getTemplateID() ); - msg->appendIntegerArgument( productionID ); + GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_QUEUE_UNIT_CREATE ); + msg->appendIntegerArgument( whatToBuild->getTemplateID() ); + msg->appendIntegerArgument( productionID ); + } break; @@ -492,6 +510,26 @@ CBCommandStatus ControlBar::processCommandUI( GameWindow *control, GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_CANCEL_UNIT_CREATE ); msg->appendIntegerArgument( productionIDToCancel ); + // TheSuperHackers @feature Shift cancels a batch. Walk backwards from the clicked + // slot so we take the most recently queued copies first, leaving the item that is + // actually being built alone for as long as possible. Only cancels entries of the + // same type, so a shift click does not silently eat unrelated queued units. + if( TheKeyboard && TheKeyboard->isShift() ) + { + Int cancelled = 1; + for( Int j = i + 1; j < MAX_BUILD_QUEUE_BUTTONS && cancelled < SHIFT_CLICK_BATCH_SIZE; ++j ) + { + if( m_queueData[ j ].control == nullptr ) + continue; + if( m_queueData[ j ].type != PRODUCTION_UNIT ) + continue; + + msg = TheMessageStream->appendMessage( GameMessage::MSG_CANCEL_UNIT_CREATE ); + msg->appendIntegerArgument( m_queueData[ j ].productionID ); + ++cancelled; + } + } + break; } From b2b70caec664562bebb94c6a313b99d3c0d9e99f Mon Sep 17 00:00:00 2001 From: triatomic <32312517+triatomic@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:53:40 +0300 Subject: [PATCH 2/2] fix(client): Cancel the newest queued entries first in a shift click batch The extra cancels scanned upward from the clicked slot, and the queue is displayed oldest to newest - so the batch took the oldest entries after the click, removing units close to production while newer copies survived at the tail. Walk from the tail towards the clicked slot instead, so the most recently queued entries go first. The batch deliberately stays unfiltered by template: its point is to clear what was just queued, whatever it was. Only unit entries are taken, so an upgrade in the queue is never swept up - now stated in the comment. --- .../GUI/ControlBar/ControlBarCommandProcessing.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp index 3b3ca1f83cd..810fbaa32f4 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp @@ -510,14 +510,17 @@ CBCommandStatus ControlBar::processCommandUI( GameWindow *control, GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_CANCEL_UNIT_CREATE ); msg->appendIntegerArgument( productionIDToCancel ); - // TheSuperHackers @feature Shift cancels a batch. Walk backwards from the clicked - // slot so we take the most recently queued copies first, leaving the item that is - // actually being built alone for as long as possible. Only cancels entries of the - // same type, so a shift click does not silently eat unrelated queued units. + // TheSuperHackers @feature Shift cancels a batch: the clicked entry plus the newest + // queued units. The queue is displayed oldest to newest, so the extra cancels walk + // from the tail towards the clicked slot - taking the most recently queued entries + // first and leaving whatever is closest to completion alone for as long as + // possible. Deliberately not filtered by template: the point of the batch is to + // clear what was just queued, whatever it was. Only unit entries are taken, so an + // upgrade sitting in the queue is never swept up. if( TheKeyboard && TheKeyboard->isShift() ) { Int cancelled = 1; - for( Int j = i + 1; j < MAX_BUILD_QUEUE_BUTTONS && cancelled < SHIFT_CLICK_BATCH_SIZE; ++j ) + for( Int j = MAX_BUILD_QUEUE_BUTTONS - 1; j > i && cancelled < SHIFT_CLICK_BATCH_SIZE; --j ) { if( m_queueData[ j ].control == nullptr ) continue;