Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Core/GameEngine/Include/Common/OptionPreferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class OptionPreferences : public UserPreferences
Real getScrollFactor();
Bool getDrawScrollAnchor();
Bool getMoveScrollAnchor();
Bool getEasyMilitaryDragEnabled() const;
Bool getCursorCaptureEnabledInWindowedGame() const;
Bool getCursorCaptureEnabledInWindowedMenu() const;
Bool getCursorCaptureEnabledInFullscreenGame() const;
Expand Down
17 changes: 17 additions & 0 deletions Core/GameEngine/Include/GameClient/SelectionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,29 @@ struct PickDrawableStruct
Bool isPointSelection;
Bool forceAttackMode;

// TheSuperHackers @feature EasyMilitaryDrag leaves builders out of a drag selection. Cached here
// rather than read per drawable, because the constructor runs once per selection while the
// callback runs for every drawable in the region.
Bool easyMilitaryDrag;
// Set instead of easyMilitaryDrag when Ctrl is held: select only the builders the option
// normally skips, so they can still be boxed deliberately.
Bool easyMilitaryDragInverted;
// Set for a second pass when the first found nothing, which suspends the filter entirely so a
// box holding only builders still selects them rather than coming back empty.
Bool easyMilitaryDragDisabled;

// Note, this is OR'd with the things we are attempting to select.
KindOfMaskType kindofsToMatch;

PickDrawableStruct();
};

//-------------------------------------------------------------------------------------------------
// TheSuperHackers @feature TRUE while a Ctrl held drag should be treated as an inverted
// EasyMilitaryDrag selection rather than as force attack targeting. Ctrl drives both, so every
// place that asks "are we force attacking?" during a drag has to agree on the answer.
extern Bool isEasyMilitaryDragInvertedActive( Bool selectionIsPoint );

//-------------------------------------------------------------------------------------------------
extern Bool contextCommandForNewSelection(const DrawableList *currentlySelectedDrawables,
const DrawableList *newlySelectedDrawables,
Expand Down
15 changes: 15 additions & 0 deletions Core/GameEngine/Source/Common/OptionPreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,21 @@ Bool OptionPreferences::getRightMouseScrollWithAlternateMouseEnabled() const
return FALSE;
}

// TheSuperHackers @feature Options.ini: EasyMilitaryDrag = Yes leaves builders out of a drag
// selection, so boxing over a base picks up the army without dragging them along. Covers
// KINDOF_DOZER and KINDOF_IGNORES_SELECT_ALL, the same kinds Select All already disqualifies.
Bool OptionPreferences::getEasyMilitaryDragEnabled() const
{
OptionPreferences::const_iterator it = find("EasyMilitaryDrag");
if (it == end())
return FALSE;

if (stricmp(it->second.str(), "yes") == 0) {
return TRUE;
}
return FALSE;
}

Bool OptionPreferences::getRetaliationModeEnabled()
{
OptionPreferences::const_iterator it = find("Retaliation");
Expand Down
43 changes: 43 additions & 0 deletions Core/GameEngine/Source/GameClient/MessageStream/SelectionXlat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,49 @@ GameMessageDisposition SelectionTranslator::onMouseLeftClick(MAYBE_UNUSED const
pds.isPointSelection = isPoint;
TheTacticalView->iterateDrawablesInRegion(&selectionRegion, addDrawableToList, &pds);

// TheSuperHackers @feature EasyMilitaryDrag drops builders from a drag, but if that leaves
// no actual units then the box held only builders, and refusing to select them is just an
// unresponsive click. Run the region again with the filter suspended so they are selected
// after all.
//
// The test is "no units survived", not "the list is empty", because structures are never
// filtered and so stay in the list. Dragging over workers standing next to a building left
// the building as the only survivor, and the selection logic below has a branch that hands
// you a building when it is the only selectable thing in the box -- so the drag silently
// grabbed the building instead of the workers.
//
// Deliberately not done for the Ctrl inverted drag: there the player has explicitly asked
// for the builders, so grabbing the army instead would be the opposite of the request.
if (pds.easyMilitaryDrag)
{
Bool anyNonStructure = FALSE;
for (DrawableListIt it = drawablesThatWillSelect.begin();
it != drawablesThatWillSelect.end(); ++it)
{
// Only units the final selection loop will actually keep count: a foreign drawable
// is rejected by the ownership filtering downstream, and a rider inside a container
// such as the Overlord's bunker is discarded as contained - letting either satisfy
// this test would suppress the builders-only pass and leave the drag selecting
// nothing.
const Drawable *d = *it;
Comment thread
greptile-apps[bot] marked this conversation as resolved.
if (d && !d->isKindOf(KINDOF_STRUCTURE) &&
d->getObject() && d->getObject()->isLocallyControlled() &&
d->getObject()->getContainedBy() == nullptr)
{
anyNonStructure = TRUE;
break;
}
}

if (!anyNonStructure)
{
// start clean, or the structures already gathered would be listed twice
drawablesThatWillSelect.clear();
pds.easyMilitaryDragDisabled = TRUE;
TheTacticalView->iterateDrawablesInRegion(&selectionRegion, addDrawableToList, &pds);
}
}

if (drawablesThatWillSelect.empty())
{
return KEEP_MESSAGE;
Expand Down
72 changes: 72 additions & 0 deletions Core/GameEngine/Source/GameClient/SelectionInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include "GameLogic/Module/ContainModule.h"

#include "Common/ActionManager.h"
// TheSuperHackers @feature for the EasyMilitaryDrag option
#include "Common/GlobalData.h"
#include "Common/ThingTemplate.h"
#include "Common/PlayerList.h"
#include "Common/Player.h"
Expand All @@ -38,6 +40,8 @@
#include "GameClient/Drawable.h"
#include "GameClient/GameClient.h"
#include "GameClient/KeyDefs.h"
// TheSuperHackers @feature for the EasyMilitaryDrag ctrl override
#include "GameClient/Keyboard.h"


//-------------------------------------------------------------------------------------------------
Expand All @@ -62,10 +66,52 @@ SelectionInfo::SelectionInfo() :
selectFriends(FALSE)
{ }

//-------------------------------------------------------------------------------------------------
// TheSuperHackers @feature See SelectionInfo.h. A point selection is never an inverted drag, so
// plain Ctrl clicking still force attacks exactly as it always did.
Bool isEasyMilitaryDragInvertedActive( Bool selectionIsPoint )
{
if (selectionIsPoint)
return FALSE;

if (!TheKeyboard || !TheKeyboard->isCtrl())
return FALSE;

// Read from the cached GlobalData copy: constructing OptionPreferences reparses
// Options.ini from disk, far too heavy for the input path.
return TheGlobalData && TheGlobalData->m_easyMilitaryDrag;
}

//-------------------------------------------------------------------------------------------------
PickDrawableStruct::PickDrawableStruct() : drawableListToFill(nullptr), isPointSelection(FALSE)
{
forceAttackMode = TheInGameUI->isInForceAttackMode();

// TheSuperHackers @feature Read once per selection, not once per drawable. Holding Ctrl
// inverts the filter for this one drag, so the builders it normally skips are exactly what
// gets boxed -- the escape hatch for grabbing them deliberately.
//
// Ctrl is also what puts the game into force attack mode, so an inverted drag has to cancel
// that for this selection. Otherwise the click is treated as attack targeting and nothing is
// selected at all. Force attack has nothing to act on here anyway: this only engages while
// dragging a box, and what it selects is your own builders.
easyMilitaryDrag = FALSE;
easyMilitaryDragInverted = FALSE;
easyMilitaryDragDisabled = FALSE;
if (TheGlobalData && TheGlobalData->m_easyMilitaryDrag)
{
if (TheKeyboard && TheKeyboard->isCtrl())
easyMilitaryDragInverted = TRUE;
else
easyMilitaryDrag = TRUE;
}

// isPointSelection is set by the caller after construction, so this uses the flag resolved
// above rather than isEasyMilitaryDragInvertedActive. The two agree for drags, which is the
// only case that reaches the filter.
if (easyMilitaryDragInverted)
forceAttackMode = FALSE;

UnsignedInt pickType = getPickTypesForContext(forceAttackMode);
translatePickTypesToKindof(pickType, kindofsToMatch);
if (!forceAttackMode)
Expand All @@ -90,6 +136,11 @@ extern Bool contextCommandForNewSelection(const DrawableList *currentlySelectedD
Bool forceFire = TheInGameUI->isInForceAttackMode();
Bool forceMove = TheInGameUI->isInForceMoveToMode();

// TheSuperHackers @feature An inverted EasyMilitaryDrag is a selection, not force attack
// targeting, even though Ctrl is what triggers both.
if (isEasyMilitaryDragInvertedActive(selectionIsPoint))
forceFire = FALSE;

if (forceFire || forceMove) {
return FALSE;
}
Expand Down Expand Up @@ -360,6 +411,27 @@ Bool addDrawableToList( Drawable *draw, void *userData )
if (!draw->getTemplate()->isAnyKindOf(pds->kindofsToMatch))
return FALSE;

// TheSuperHackers @feature EasyMilitaryDrag leaves builders out of a drag selection, so boxing
// over your own base picks up the army without dragging them off their work. Only drags are
// affected -- a point selection still picks them normally, as do double click, control groups
// and the select-matching hotkeys.
//
// The two kinds mirror what Select All already disqualifies: KINDOF_DOZER, and the explicit
// KINDOF_IGNORES_SELECT_ALL marker a mod can put on anything else it wants left alone. Note
// KINDOF_DOZER also catches GLA Workers, which carry it alongside KINDOF_INFANTRY and
// KINDOF_HARVESTER.
if (!pds->easyMilitaryDragDisabled &&
!pds->isPointSelection && (pds->easyMilitaryDrag || pds->easyMilitaryDragInverted))
{
const Bool isBuilder =
draw->isKindOf(KINDOF_DOZER) || draw->isKindOf(KINDOF_IGNORES_SELECT_ALL);

// normally drop the builders; with Ctrl held, drop everything else instead
const Bool wantBuilders = pds->easyMilitaryDragInverted;
if (isBuilder != wantBuilders)
return FALSE;
}

if (!draw->isSelectable())
{
const Object *obj = draw->getObject();
Expand Down
2 changes: 2 additions & 0 deletions Generals/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ class GlobalData : public SubsystemInterface
Bool m_useAlternateMouse;
Bool m_useRightMouseScrollWithAlternateMouse; // TheSuperHackers @feature User option for RMB scroll in Alternate Mouse mode.
Bool m_clientRetaliationModeEnabled;
// TheSuperHackers @feature Leave builders out of drag selections.
Bool m_easyMilitaryDrag;
Bool m_doubleClickAttackMove;
Bool m_rightMouseAlwaysScrolls;
Int m_jpegQuality; // TheSuperHackers @feature Quality for JPEG screenshots.
Expand Down
2 changes: 2 additions & 0 deletions Generals/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,7 @@ GlobalData::GlobalData()
m_useRightMouseScrollWithAlternateMouse = TRUE;
#endif
m_clientRetaliationModeEnabled = TRUE; //On by default.
m_easyMilitaryDrag = FALSE;
m_doubleClickAttackMove = FALSE;

}
Expand Down Expand Up @@ -1199,6 +1200,7 @@ void GlobalData::parseGameDataDefinition( INI* ini )
TheWritableGlobalData->m_useRightMouseScrollWithAlternateMouse = optionPref.getRightMouseScrollWithAlternateMouseEnabled();
TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled();
TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled();
TheWritableGlobalData->m_easyMilitaryDrag = optionPref.getEasyMilitaryDragEnabled();
TheWritableGlobalData->m_jpegQuality = optionPref.getJpegQuality();
TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor();
TheWritableGlobalData->m_drawScrollAnchor = optionPref.getDrawScrollAnchor();
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class GlobalData : public SubsystemInterface
Bool m_useAlternateMouse;
Bool m_useRightMouseScrollWithAlternateMouse; // TheSuperHackers @feature User option for RMB scroll in Alternate Mouse mode.
Bool m_clientRetaliationModeEnabled;
// TheSuperHackers @feature Leave builders out of drag selections.
Bool m_easyMilitaryDrag;
Bool m_doubleClickAttackMove;
Bool m_rightMouseAlwaysScrolls;
Int m_jpegQuality; // TheSuperHackers @feature Quality for JPEG screenshots.
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ GlobalData::GlobalData()
m_useRightMouseScrollWithAlternateMouse = TRUE;
#endif
m_clientRetaliationModeEnabled = TRUE; //On by default.
m_easyMilitaryDrag = FALSE;
m_doubleClickAttackMove = FALSE;

}
Expand Down Expand Up @@ -1206,6 +1207,7 @@ void GlobalData::parseGameDataDefinition( INI* ini )
TheWritableGlobalData->m_useRightMouseScrollWithAlternateMouse = optionPref.getRightMouseScrollWithAlternateMouseEnabled();
TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled();
TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled();
TheWritableGlobalData->m_easyMilitaryDrag = optionPref.getEasyMilitaryDragEnabled();
TheWritableGlobalData->m_jpegQuality = optionPref.getJpegQuality();
TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor();
TheWritableGlobalData->m_drawScrollAnchor = optionPref.getDrawScrollAnchor();
Expand Down