[WIP] Group identical permanents into numbered stacks#9751
[WIP] Group identical permanents into numbered stacks#9751MostCromulent wants to merge 20 commits intoCard-Forge:masterfrom
Conversation
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>
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:
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>
|
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>
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>
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>
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? |
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 |
…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
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>
…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.
…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.
…acks (token grouping) # Conflicts: # forge-gui/src/main/java/forge/gamemodes/match/input/InputBlock.java
- 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
…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



WORK IN PROGRESS! (Updated 03/04/2026)
BEFORE (Default settings)


AFTER (Group all permanents)
Summary
Adds a "Stack/Group Permanents" submenu to the Game menu with four options:
Grouping behavior
Badge interaction
🤖 Generated with Claude Code