Skip to content

[WIP] Group identical permanents into numbered stacks#9751

Draft
MostCromulent wants to merge 20 commits intoCard-Forge:masterfrom
MostCromulent:tokengroup
Draft

[WIP] Group identical permanents into numbered stacks#9751
MostCromulent wants to merge 20 commits intoCard-Forge:masterfrom
MostCromulent:tokengroup

Conversation

@MostCromulent
Copy link
Copy Markdown
Contributor

@MostCromulent MostCromulent commented Feb 14, 2026

WORK IN PROGRESS! (Updated 03/04/2026)

BEFORE (Default settings)
image
AFTER (Group all permanents)
Screenshot 2026-04-03 114709

Summary

Adds a "Stack/Group Permanents" submenu to the Game menu with four options:

  • Default - existing behavior (lands/tokens/artifacts/enchantments stacked with fan-out, creatures unstacked)
  • Stack Creatures - existing behavior from "Stack Creatures" checkbox, also stacks creatures
  • Group Creatures/Tokens (NEW) - identical creatures and tokens are shown as a single compact pile of up to 4 visible cards, with a xN count badge on top
  • Group All Permanents (NEW) - groups all identical permanents with xN count badges
Screenshot 2026-04-03 114351

Grouping behavior

  • Identical permanents (same name, P/T, counters, abilities, tapped state, damage, no attachments) are collapsed into overlapping stacks of up to 4 visible cards with a xN badge on top.
  • When any criterion changes (one gets tapped, takes damage, gains a counter), that permanent separates from its group into a separate file
  • Blockers assigned to different attackers are kept in separate piles for visual separation; attackers with different blockers are also kept in separate piles so you can easily track group combat assignments.
  • Grouping respects the existing "Tokens in separate row" preference.
  • UI updates immediately when grouping preference changed, no need to restart match.
  • Grouping is UI layer only - as far as game engine is concerned all grouped objects are still processed separately. (Note this means e.g. selecting a pile of Treasure Tokens will still prompt you 1-by-1 to sacrifice and select mana color, rather than processing whole batch together. Batch processing could be improved in future but requires engine changes beyond scope of this PR).

Badge interaction

  • Left-click badge - select all cards in the group (e.g. tap all lands for mana)
  • Left-click badge on tapped group - undo last action per card (reverse a batch mana tap)
  • Left-click individual card in a group - split it out for individual declaration/selection; re-click to merge back
  • Right-click badge -prompt for how many to declare as attackers/blockers, remove from combat, or select for sacrifice/targeting
Screenshot 2026-04-03 115046 Screenshot 2026-04-03 115113 Screenshot 2026-04-03 122949

🤖 Generated with Claude Code

MostCromulent and others added 4 commits February 14, 2026 14:54
Visually groups identical permanents into stacks with count badges.
Groups of 5+ show a ×N badge; clicking it selects all for batch
attack/block. Configurable via Game > Card Overlays menu with three
modes: Off, Tokens & Creatures, All Permanents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
isBadgeHit() was comparing container-relative mouse coordinates against
panel-internal badge coordinates, so the hit test always failed. Use
getCardX()/getCardY() to convert to the correct coordinate space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation UX

- Group blockers by which attacker they're assigned to block (separate piles per attacker)
- Split cards from groups for attacker/blocker declaration with proper regrouping
- Only allow splitting cards the game accepts (skip summoning-sick creatures)
- Cards regroup with other tapped attackers after declaration via deferred layout
- Move Group Permanents menu from Card Overlays submenu to Game menu
- Group Permanents menu immediately refreshes the play area layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lower the split threshold from 5 to 2 when grouping is enabled, so
cards can be individually split from groups that have dropped below 5.
Mark solo cards as split when declared as attackers so they merge with
other split attackers from the same group. Track un-split state to
prevent re-adding cards to splitCardIds when un-declaring attackers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@paulsnoops
Copy link
Copy Markdown
Contributor

Looks cool! Does it interfere or replace this?

image

@MostCromulent
Copy link
Copy Markdown
Contributor Author

Does it interfere or replace this?

It respects the token row preference (i.e. it will group both creatures and tokens but place them in separate rows).

It overrides the existing stack preference if its enabled, since it's kind of built on top of it. The preferences could probably be integrated together since they are doing a similar sort of thing, so you end up with a "Group and Stack Permanents" option with:

  • Off: default behaviour without the existing stack preference.
  • Stack: existing stack preference (no grouping).
  • Group creatures/tokens: new functionality.
  • Group all permanents: new functionality.

Is the "group" vs "stack" naming intuitive? When I think of a "stack" I think more along the lines of what this PR does where it puts everything in one big pile than what the existing stack preference does, but I don't want to go and re-label existing functionality since people are used to it.

Replace separate "Stack Creatures" checkbox and "Group Permanents" submenu
with a single "Stack/Group Permanents" submenu offering four options:
Default, Stack Creatures, Group Creatures/Tokens, Group All Permanents.
Add "Tokens in Separate Row" checkbox. All options include descriptive
tooltips and trigger immediate layout updates. PlayArea now derives all
layout flags from the single UI_GROUP_PERMANENTS preference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MostCromulent
Copy link
Copy Markdown
Contributor Author

I note current default behavior with the existing "stack creatures" preference turned on is that creatures + enchantments + artifacts are in stacks of 4, while lands + tokens are in stacks of 5.

Is there a reason for that difference?

If we integrate the stack/group functions together should we replace with a default stack size of 5 for everything, or should we flow this on and maintain the 4 vs 5 stack size for the new grouping function as well?

Right-clicking the count badge on a group of 5+ identical permanents
now prompts for how many to select, then declares that many as
attackers/blockers and splits them into a separate visual pile.

- Add manual constructor to MouseTriggerEvent for synthetic triggers
- Gate prompt on active input (getActivateDescription) to prevent
  showing during opponent's turn
- Skip selectCard for already-declared cards to avoid toggling them off
- Clear stale splitCardIds before marking selected subset to ensure
  correct visual separation after repeated split/merge cycles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MostCromulent
Copy link
Copy Markdown
Contributor Author

You can now right-click the number badge and it will prompt how many creatures/tokens from the pile you want to declare, rather than needing to click them individually.

Screenshot 2026-02-15 101856 Screenshot 2026-02-15 101909

Right-click badge on a group of all-attackers/blockers now undeclares N
cards (button=3 trigger) instead of doing a visual-only split. Prompt
text adapts to game phase: "declare as attackers", "assign as blockers",
or "remove from combat".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MostCromulent and others added 3 commits February 19, 2026 08:08
Show the count badge and enable badge click interactions (left-click
select-all, right-click declare-N) for groups of 4 or more identical
tokens, down from the previous minimum of 5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously lands only split by tapped status in "Group All Permanents"
mode. Now they always split, matching creature behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@paulsnoops
Copy link
Copy Markdown
Contributor

paulsnoops commented Feb 23, 2026

Is the "group" vs "stack" naming intuitive? When I think of a "stack" I think more along the lines of what this PR does where it puts everything in one big pile than what the existing stack preference does, but I don't want to go and re-label existing functionality since people are used to it.

The only thing I'd say here is the use of the word stack (do we already use it in this context?) as MtG uses that specifically in the rules meaning a different thing, I don't know if it's really an issue though @tehdiplomat what do you think?

@paulsnoops
Copy link
Copy Markdown
Contributor

I note current default behavior with the existing "stack creatures" preference turned on is that creatures + enchantments + artifacts are in stacks of 4, while lands + tokens are in stacks of 5.

Is there a reason for that difference?

If we integrate the stack/group functions together should we replace with a default stack size of 5 for everything, or should we flow this on and maintain the 4 vs 5 stack size for the new grouping function as well?

Not sure honestly, I'd assume because most cards have a maximum of 4 in a deck so there was no reason to be any more, and tokens/lands were just set that way, I don't think it matters from a rules point of view

MostCromulent and others added 2 commits February 28, 2026 13:49
…uce duplication

- Fix badge hit detection on tapped cards by inverse-rotating mouse coordinates
- Cache badge font per cardWidth and make background color a static constant
- Scale badge corner radius proportionally to card size
- Lower badge click threshold from 4 to 2 for both left and right click
- Add badge-click undo for tapped lands (undoes batch mana tap)
- Dispatch non-combat badge right-clicks individually (sacrifice, targeting)
- Extract shared collectStacked() method from three near-identical collect methods
- Coalesce rapid tapped-state layout passes via layoutPending flag
- Update tooltip text removing outdated "(5 or more)" qualifier

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java
@tool4ever tool4ever linked an issue Mar 15, 2026 that may be closed by this pull request
MostCromulent and others added 6 commits March 31, 2026 07:36
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CardStack.add() recursively pulls in attached panels via
addAttachedPanels(), inflating stack.size(). The group badge
now counts only non-attached panels in the stack.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…play

- Use internal keys (default/stack/group_creatures/group_all) as preference
  values instead of English display strings, enabling future localization
- Localize all user-facing strings: menu labels, tooltips, combat prompts
- Remove dead l10n keys (cbpGroupPermanents, nlGroupPermanents)
- Add grouping field to eliminate redundant boolean expressions in PlayArea
- Replace silent catch-all exception with null guard in buildBlockerAssignments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ng logic

Right-clicking to undeclare attackers from a grouped pile left them
visually separated. splitCardIds was re-added after undeclare, and
doUpdateCard cleanup doesn't fire during InputAttack (cards aren't
tapped yet). Fixed badge and individual right-click to clean up
splitCardIds on undeclare.

Refactored PlayArea grouping code:
- Extract handleBadgeRightClick from inline block
- Unify collectAllOthers with collectStacked
- Eliminate wasUnsplit flag via early return in mouseLeftClicked
- Gate land tapped/damage checks behind groupAll for consistency
- Remove restating comments, revert cosmetic CardView.java change

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… grouping

The combat assignment comparison in collectStacked() used != on boxed
Integer values from Map.getOrDefault(). Java only caches boxed Integers
for -128 to 127, so for card IDs >= 128 (common in token-heavy games),
!= compared object references instead of values — causing identical
assignments like 164 vs 164 to be treated as different.

Fix by unboxing to primitive int before comparing. Also extend the
assignment map to cover attacker→blockerHash so identical attackers
blocked by different creatures are kept in separate piles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
buildCombatAssignments() now maps attackers to defenders and uses
Integer.MIN_VALUE high bit so combat assignments never collide with the
default 0 for non-combat cards. Blocker set hash uses multiplicative
combining (31*h+id) instead of XOR which cancels to 0 with certain ID
combinations.

showCombat() triggers doLayout() on all play areas and
GameEventCombatUpdate sets needCombatUpdate so the host refreshes when
the remote client declares blockers. Removed combatAssignments clearing
from update() to prevent a race where updateZones() wipes assignments
built by showCombat() in the same processEvents pass.

Split-from-group gated on isLocalPlayer(model) — clicking opponent's
cards no longer visually splits them. Combat assignment map handles
separation when blockers are actually assigned.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RafaelHGOliveira pushed a commit to RafaelHGOliveira/forge that referenced this pull request Apr 8, 2026
…acks

Groups tokens and identical permanents into visual stacks with a
numbered badge, reducing battlefield clutter in large board states.
Right-click the badge to declare N cards from group in combat.
Tapped and untapped lands split into separate piles.

Grouping modes: default / stack / group creatures / group all
via new Game menu > Stack Group Permanents submenu.

Conflict resolution: kept getYieldOptionsMenu() structure intact,
added token grouping menu items alongside it.
RafaelHGOliveira added a commit to RafaelHGOliveira/forge that referenced this pull request Apr 9, 2026
…rge#9751)

Ports the token/permanent grouping feature developed in the fork's PR Card-Forge#9751.
Identical permanents on the battlefield are stacked with a badge showing the
count. Right-click the badge to split N cards from the group for attacking or
blocking. Grouping is optional (Game menu toggle) and persists via ForgePrefs.

Source: fork branch pr/9751, integrated into release v2.0.19.
RafaelHGOliveira added a commit to RafaelHGOliveira/forge that referenced this pull request Apr 9, 2026
…acks (token grouping)

# Conflicts:
#	forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java
MostCromulent and others added 2 commits April 9, 2026 16:49
- Fix blockerHash XOR clearing the Integer.MIN_VALUE high-bit invariant
- Gate combat-assignment and split-card checks behind groupingEnabled
- Gate showCombat doLayout refresh behind UI_GROUP_PERMANENTS preference
- Replace orphaned Stack Creatures checkbox with Stack/Group Permanents
  dropdown in Preferences panel
- Wire FControlGameEventHandler phase-change refresh to UI_GROUP_PERMANENTS
- Rename unlimitedGrouping parameter to groupingEnabled
- Add comment explaining state-level oracle name for transform/MDFC
- Remove stray whitespace in CardView and restating comment in collectStacked
- Add nlGroupPermanents localization key for preferences description

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java
RafaelHGOliveira added a commit to RafaelHGOliveira/forge that referenced this pull request Apr 10, 2026
…yield/grouping refactor

- YieldState immutable with builder pattern (withMode/withStartTurn etc.)
- Unified setYieldMode(player, mode, fromRemote) replaces setYieldModeFromRemote
- PlayerView.findById() static helper for network PlayerView lookup
- TriggerChoice enum replaces magic ints 0/1/-1 in notifyTriggerChoiceChanged
- YieldPrefs snapshot synced on every yield-state message for remote prefs
- RemoteClientGuiGame stores volatile YieldPrefs for remote interrupt checks
- ProtocolMethod: notifyYieldModeChanged → notifyYieldStateChanged + YieldPrefs
- VYield: 2-col layout, Before Your Turn button replaces EndStepBeforeYourTurn
- VYieldSettings: accepts CMatchUI, pushes prefs to host on change
- CMatchUI.openView: sends initial YieldPrefs snapshot to server on connect
- Token grouping: boolean checkbox → string dropdown (PlayArea, preferences UI)
- ForgePreferences yield shortcuts reordered F2-F8 per new panel layout
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Desktop QOL Quality of Life

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Treasure tokens with counter instead of separate cards

3 participants