Skip to content

fix: chain action ignores underscores in command names and mouse-button binds#7952

Merged
WatchTheFort merged 4 commits into
beyond-all-reason:masterfrom
Arcadiyyyyyyyy:patch-1
Jul 8, 2026
Merged

fix: chain action ignores underscores in command names and mouse-button binds#7952
WatchTheFort merged 4 commits into
beyond-all-reason:masterfrom
Arcadiyyyyyyyy:patch-1

Conversation

@Arcadiyyyyyyyy

Copy link
Copy Markdown
Contributor

Work done

Two independent bugs in luaui/Widgets/cmd_chainactions.lua prevented the chain action from working with a large class of commands and from working at all on mouse-button binds. Both are fixed; each is described separately below.


Fix 1 — chain breaks on command names containing underscores

Problem

Each chained sub-command is parsed with:

local cmd, extra = string.match(rawCmd, "^(%w+)[%s]*(.*)$")

Lua's %w matches [A-Za-z0-9] and excludes _, so the command name is truncated at the first underscore. Any action whose name contains _ (e.g. set_camera_anchor, focus_camera_anchor, and most Lua-registered actions) is parsed as a wrong command plus leftover text:

set_camera_anchor 4    ->  cmd="set",   extra="_camera_anchor 4"
focus_camera_anchor 1  ->  cmd="focus", extra="_camera_anchor 1"

The truncated name (set, focus) is never present in actionHandler's keyPressActions, so actionHandler:KeyAction returns false. The sub-command can then only reach the engine through the Spring.SendCommands fallback (which re-tokenizes the full line correctly), and tryRawCmd returns false.

Consequences
  • Non-force mode: the chain uses tryRawCmd's return value as the "did the first command respond" gate. An underscored action returns false (it only ran via the SendCommands fallback), so the chain executes the first sub-command but then aborts and silently drops every sub-command after it.
  • force mode: the gate is skipped, so every sub-command is attempted — but underscored Lua actions still only reach the engine via the SendCommands text fallback. So force is only a partial workaround: it works exclusively for actions that have text-command support ('t'), it never restores proper KeyAction dispatch, and it does nothing about the underlying parse bug.
Repro
bind x chain set_camera_anchor 4 | focus_camera_anchor 4   # no force: second action never fires
bind x chain forcestart | say hi                           # works only because those names have no underscore
Fix

Capture the command as the first whitespace-delimited token, matching how the engine (and actionHandler:TextAction) tokenizes action lines:

-	local cmd, extra = string.match(rawCmd, "^(%w+)[%s]*(.*)$")
+	local cmd, extra = string.match(rawCmd, "^(%S+)%s*(.*)$")

After this, cmd="set_camera_anchor" matches the action registered in keyPressActions, KeyAction returns true, and the chain proceeds through the proper dispatch path with no need for force.

Verified against the existing chain examples (select PrevSelection++_ClearSelection_SelectPart_50+, group unset, buildfacing inc): output is byte-for-byte identical with the old pattern. The only command names whose parsing changes are those that themselves contain an underscore — they are now captured in full instead of being truncated at the first _. (Underscores inside the argument part, e.g. inside a select … filter, were always in extra and are unaffected.)


Fix 2 — chain cannot be triggered from a mouse-button bind

Problem

The action is registered press-only:

widgetHandler.actionHandler:AddAction(self, "chain", chainHandler, nil, "p")

In Recoil the two input sources reach widget actions through different dispatch sets in actions.lua:

  • Keyboard bindings → actionHandler:KeyAction → looked up in keyPressActions (the 'p' set).
  • Mouse-button bindings → the text-command path → actionHandler:TextAction → looked up in textActions (the 't' set).

Because chain is registered as 'p' only, it exists in keyPressActions but not in textActions. A keyboard bind (sc_insert) finds it; a mouse-button bind (mouse5) does not, and the press is silently dropped. This is also why a direct bind mouse5 set_camera_anchor … always worked: set_camera_anchor/focus_camera_anchor are registered 'pt', so they are reachable from both paths.

Fix

Register chain for both press and text:

-	widgetHandler.actionHandler:AddAction(self, "chain", chainHandler, nil, "p")
+	widgetHandler.actionHandler:AddAction(self, "chain", chainHandler, nil, "pt")

chainHandler needs no change: the two paths hand it equivalent arguments. KeyAction calls TryAction(set, cmd, extra, split(extra), …) and TextAction calls TryAction(textActions, cmd, line, split(line), …), where in both cases the command word (chain) has already been stripped, so chainHandler receives the same extra (everything after chain) and the same bOpts (bOpts[1] == "force"). force detection, the | split, and per-segment dispatch are therefore identical on both paths. Keyboard behaviour is unchanged (keyboard still uses the press path only — no double dispatch).

Repro
bind mouse5 chain force set_camera_anchor 4 | say chain ok   # before: nothing happens on press
bind sc_insert chain force set_camera_anchor 4 | say chain ok # before: works (keyboard press path)

How to test

Setup:

  1. Start any skirmish (e.g. vs an AI) and spawn in so you have a commander and a live camera.
  2. Add the binds below to uikeys.txt, then reload them (restart the match, or reload hotkeys). The chain widget ("Chain Actions") is enabled by default; confirm it is loaded in the F11 widget list.
  3. Watch the in-game console for Camera anchor set: N (from set_camera_anchor) and for say output, or tail infolog.txt.

Checklist:

  • Keyboard, non-force, underscored (Fix 1): bind sc_insert chain set_camera_anchor 4 | focus_camera_anchor 4 — pressing Insert sets anchor 4 and focuses it. Before the fix the second action never fired.
  • Mouse, force (Fix 2): bind mouse5 chain force set_camera_anchor 4 | say chain ok — pressing mouse5 prints Camera anchor set: 4 and chain ok. Before the fix nothing happened.
  • Mouse, non-force, underscored (Fix 1 + Fix 2): bind mouse4 chain set_camera_anchor 2 | focus_camera_anchor 2 — pressing mouse4 runs both actions.
  • Regression — keyboard force still works: bind Alt+f chain force forcestart | say hi.
  • Regression — select with underscores unaffected: bind Ctrl+tab select AllMap+_Builder_Idle+_ClearSelection_SelectOne+.

AI / LLM usage statement

The change was written by Claude (Opus 4.8); my contribution was identifying the bugs and validating the fixes.

@efrec

efrec commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

I would prefer a separate PR to check other cases where p->pt is appropriate (vs spot-checked PRs for each such case). If you think the scope fits in a single PR, though, that's fine.

Otherwise looks good at a glance.

@efrec efrec added the Bug Something isn't working label Jun 14, 2026
@WatchTheFort

Copy link
Copy Markdown
Member

@Arcadiyyyyyyyy Have you had a chance to address the feedback?

@Arcadiyyyyyyyy

Copy link
Copy Markdown
Contributor Author

@Arcadiyyyyyyyy Have you had a chance to address the feedback?

Unfortunately, not yet, I will try my best to look into it this week

@Arcadiyyyyyyyy

Copy link
Copy Markdown
Contributor Author

I would prefer a separate PR to check other cases where p->pt is appropriate (vs spot-checked PRs for each such case). If you think the scope fits in a single PR, though, that's fine.

Otherwise looks good at a glance.

Addressed the feedback in #8168

Comment thread luaui/Widgets/cmd_chainactions.lua Outdated
Revert p -> pt change, left for separate PR
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Integration Test Results

14 tests  ±0   6 ✅ ±0   3s ⏱️ ±0s
 1 suites ±0   8 💤 ±0 
 1 files   ±0   0 ❌ ±0 

Results for commit 2a6316e. ± Comparison against base commit 095f0fc.

@WatchTheFort WatchTheFort merged commit 3151445 into beyond-all-reason:master Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants