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
4 changes: 3 additions & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -924,4 +924,6 @@ This page lists all the individual contributions to the project by their author.
- **Chang_zhi**:
- Interop export interface for accessing scenario local/global variables
- Add `ClampToScreen` tag for `BannerType` to control whether banner position is clamped to the visible area
- **obsidianus** - Automatic conversion based on health
- **Igor Kolchinskii (leosnake2208)**:
- Right-click to command
- Type selection by double/triple-click
2 changes: 2 additions & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,10 @@
<ClCompile Include="src\Misc\Hooks.UI.cpp" />
<ClCompile Include="src\Misc\Hooks.VeinholeMonster.cpp" />
<ClCompile Include="src\Misc\MessageColumn.cpp" />
<ClCompile Include="src\Misc\MultiClickTypeSelect.cpp" />
<ClCompile Include="src\Misc\PhobosToolTip.cpp" />
<ClCompile Include="src\Misc\RetryDialog.cpp" />
<ClCompile Include="src\Misc\RightClickCommand.cpp" />
<ClCompile Include="src\Misc\Selection.cpp" />
<ClCompile Include="src\Misc\SyncLogging.cpp" />
<ClCompile Include="src\Misc\TextInput.cpp" />
Expand Down
38 changes: 38 additions & 0 deletions docs/User-Interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,24 @@ RealTimeTimers=false ; boolean
RealTimeTimers.Adaptive=false ; boolean
```

### Right-click to command

- An optional control scheme matching modern RTS games: the **right mouse button** issues orders to the current selection (move, attack, enter, capture, etc.), while the **left mouse button** only selects, box-selects and self-deploys, and deselects when clicking empty ground. It reuses the game's own command dispatch, so all order and network logic is unchanged.
- Special left-click modes are preserved on both buttons: while placing a building, repairing, selling, toggling power, planting a beacon, planning a path or targeting a superweapon, the left button performs that action and the right button cancels it, exactly as in vanilla.
- The minimap follows the same scheme: the right button orders the current selection to the clicked spot, the left button moves the view there. In vanilla it is the other way around, the left button commands and only moves the view when there is nothing to command.
- Enable it with `RightClickCommand=true`.
- Because the left button no longer commands, double and triple clicks are free for [type selection](#type-selection-by-multi-click).

```{note}
This only changes mouse behaviour; keyboard hotkeys are unaffected. It does not rebind any keys.
```

In `RA2MD.INI`:
```ini
[Phobos]
RightClickCommand=false ; boolean
```

### Select Box

![selectbox](_static/images/selectbox.png)
Expand Down Expand Up @@ -465,6 +483,26 @@ BuildingTypeSelectable=false ; boolean
Due to technical limitations, this feature is forcibly disabled without Ares.
```

### Type selection by multi-click

- Double-clicking a unit selects every unit of the same selection group near it; triple-clicking selects every unit of that group across the whole map. This adds the double/triple-click gesture common to modern RTS games next to the vanilla type-select hotkey (hold `T` and click). Only your own selectable units are affected, and the selection group is the same one the hotkey uses (the `[TechnoType] -> GroupAs` tag, falling back to the type's ID).
- Enable it with `TypeSelectByMultiClick=true`. It requires [right-click to command](#right-click-to-command) and is ignored without it, see the note below.
- `TypeSelectByMultiClick.Range` is how far a double-click reaches, in cells around the clicked unit. A negative value means everything currently drawn on screen.
- `TypeSelectByMultiClick.DeployDelay` is how long, in milliseconds, the left button refuses to deploy a unit after a click selected it. Without this delay the second click of a double-click would unpack an MCV instead of selecting its group. Set it to `0` to turn the delay off.
- The delay starts when a click selects something, so it does not cover a unit that is already selected on its own. Double-clicking such a unit still deploys it on the first click, because at that moment nothing tells the game that a second click is coming.

```{note}
This needs the left mouse button to be select-only, so it only works together with `RightClickCommand=true`. With vanilla controls a click on an already selected unit is a command, so a double-click would deploy an MCV or an Allied GI rather than select the group. If you enable it anyway, it is turned off and a line is written to the debug log.
```

In `RA2MD.ini`:
```ini
[Phobos]
TypeSelectByMultiClick=false ; boolean
TypeSelectByMultiClick.Range=-1 ; integer, cells
TypeSelectByMultiClick.DeployDelay=500 ; integer, milliseconds
```

### Visual effects toggling

- It is possible to toggle certain light flash effects off. These light flash effects include:
Expand Down
6 changes: 6 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ ShowBriefing=true ; boolean
DigitalDisplay.Enable=false ; boolean
ShowDesignatorRange=false ; boolean
PrioritySelectionFiltering=true ; boolean
RightClickCommand=false ; boolean
TypeSelectByMultiClick=false ; boolean
TypeSelectByMultiClick.Range=-1 ; integer, cells
TypeSelectByMultiClick.DeployDelay=500 ; integer, milliseconds
PriorityDeployFiltering=true ; boolean
ShowPlacementPreview=yes ; boolean
RealTimeTimers=false ; boolean
Expand Down Expand Up @@ -402,6 +406,8 @@ HideShakeEffects=false ; boolean
:open:

#### New:
- [Right-click to command](User-Interface.md#right-click-to-command) (by leosnake2208)
- [Type selection by double/triple-click](User-Interface.md#type-selection-by-multi-click) (by leosnake2208)
- [Allow using waypoints, area guard and attack move with aircraft](Fixed-or-Improved-Logics.md#extended-aircraft-missions) (by CrimRecya)
- [Enhanced Straight trajectory](New-or-Enhanced-Logics.md#straight-trajectory) (by CrimRecya)
- [Enable building production queue](User-Interface.md#building-production-queue) (by CrimRecya)
Expand Down
142 changes: 142 additions & 0 deletions src/Misc/MultiClickTypeSelect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include <Phobos.h>

#include <FootClass.h>
#include <TacticalClass.h>
#include <Ext/Techno/Body.h>
#include <Ext/TechnoType/Body.h>

// Multi-click type selection:
// * double-click a unit -> select every unit of its selection group nearby;
// * triple-click a unit -> select that group across the whole map.
//
// Vanilla only has the type-select hotkey (hold T, then click or drag); there is no
// double-click equivalent, a double click in the tactical area falls through to the message
// handler's default case. Grouping reuses TechnoTypeExt::GetSelectionGroupID /
// HasSelectionGroupID (the GroupAs tag, with the type ID as fallback), so it stays consistent
// with the hotkey type select in Selection.cpp.
//
// This needs RightClickCommand. With the vanilla left button a click on an already selected
// unit is a command, so a double click would deploy an MCV or an Allied GI instead of
// selecting. Phobos.INI.cpp turns the setting off if RightClickCommand is not enabled.

namespace RightClickCommand
{
// Defined in RightClickCommand.cpp. Set by the RMB command redirect, cleared here.
extern bool RmbCommandInProgress;
}

namespace MultiClickTypeSelect
{
// Clicks within GetDoubleClickTime() and this many screen pixels of the previous one
// count as part of the same streak.
static constexpr int PositionTolerance = 4;

static DWORD LastClickTick = 0;
static POINT LastClickPos = { -9999, -9999 };
static int ClickStreak = 0;

// Mirrors ExtSelection::ObjectClass_IsSelectable (Selection.cpp): an own, alive, currently
// selectable object.
static bool IsOwnSelectable(TechnoClass* pTechno)
{
const auto pOwner = pTechno->GetOwningHouse();
return pOwner && pOwner->IsControlledByCurrentPlayer()
&& pTechno->CanBeSelected() && pTechno->CanBeSelectedNow()
&& !pTechno->InLimbo;
}

// Add to the current selection every own, selectable mobile unit sharing the clicked
// unit's selection group. wholeMap takes the whole map; otherwise the reach is
// TypeSelectByMultiClick.Range cells around the clicked unit, or, if that is negative,
// everything drawn in the tactical viewport.
static void SelectSameGroup(FootClass* pClicked, bool wholeMap)
{
const char* groupID = TechnoTypeExt::GetSelectionGroupID(pClicked->GetTechnoType());
const int rangeCells = Phobos::Config::TypeSelectByMultiClick_Range;

for (auto const pTechno : TechnoClass::Array)
{
const auto pFoot = abstract_cast<FootClass*, true>(pTechno);

if (!pFoot || pFoot->IsSelected || !IsOwnSelectable(pFoot))
continue;

if (!TechnoTypeExt::HasSelectionGroupID(pFoot->GetTechnoType(), groupID))
continue;

if (!wholeMap)
{
if (rangeCells >= 0)
{
if (pClicked->DistanceFrom(pFoot) > rangeCells * Unsorted::LeptonsPerCell)
continue;
}
else if (!TacticalClass::Instance->CoordsToClient(pFoot->GetCoords()).second)
{
continue;
}
}

pFoot->Select();
}
}

// Update the click streak from the current cursor time/position and act on it. pClicked is
// the object the click landed on, or null for empty ground.
static void HandleClick(FootClass* pClicked)
{
POINT pos { 0, 0 };
GetCursorPos(&pos);
const DWORD now = GetTickCount();

const int dx = pos.x - LastClickPos.x;
const int dy = pos.y - LastClickPos.y;
const bool sameSpot = dx >= -PositionTolerance && dx <= PositionTolerance
&& dy >= -PositionTolerance && dy <= PositionTolerance;

if (now - LastClickTick <= GetDoubleClickTime() && sameSpot)
ClickStreak = ClickStreak < 3 ? ClickStreak + 1 : 3;
else
ClickStreak = 1;

LastClickTick = now;
LastClickPos = pos;

if (!pClicked) // only mobile units drive type select
return;

if (ClickStreak == 2)
SelectSameGroup(pClicked, false);
else if (ClickStreak == 3)
SelectSameGroup(pClicked, true);
}
}

// Tactical LBUTTONUP handler, just after the click's own selection has been applied (call to
// 0x4AB9B0) and before the drag flag is cleared. This point is only reached by a genuine
// single click - a completed rubber-band selection returns earlier. Stolen bytes:
// mov byte ptr [esi+0x555A], bl (absolute operand, safe to relocate).
DEFINE_HOOK(0x693290, TacticalMsgHandler_LButtonUp_MultiClickTypeSelect, 0x6)
{
// The RMB command redirect (RightClickCommand.cpp) ends here too. Consume the flag and
// skip, so an RMB order is not counted as a left click for the streak.
if (RightClickCommand::RmbCommandInProgress)
{
RightClickCommand::RmbCommandInProgress = false;
return 0;
}

if (!Phobos::Config::TypeSelectByMultiClick)
return 0;

// The object this click landed on, as ProcessClickCoords resolved it at 0x69325E and
// DecideAction and the applier were given it. ESP here equals ESP at the start of the
// command dispatch (0x69323E), which is where that output slot is addressed from.
// Not CurrentObjects[0]: with several units selected that is not the clicked one.
// Null when the click landed on empty ground, so this cast must keep its null check.
const auto pClicked = abstract_cast<FootClass*>(R->Stack<ObjectClass*>(0x2C));

MultiClickTypeSelect::HandleClick(pClicked);

return 0;
}
Loading