-
Notifications
You must be signed in to change notification settings - Fork 929
Yield Rework V2 — configurable interrupts, APINA, suggestions, settings UI #10606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MostCromulent
wants to merge
43
commits into
Card-Forge:master
Choose a base branch
from
MostCromulent:YieldReworkV2
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,754
−309
Open
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
86c2e37
YieldReworkV2 — configurable interrupts, APINA, smart suggestions, se…
MostCromulent 304456d
Drop APINA state-scanner; route interrupts through yield event handlers
MostCromulent f8436a9
Move auto-pass and yield settings to the dock panel
MostCromulent d355680
Split stack-yield into interruptible and non-interruptible variants
MostCromulent 32036f3
Merge branch 'master' into YieldReworkV2
tool4ever 415d0cb
Fix merge
8fa44c9
Clean up start
d4ac70c
Clean up start
949837c
Clean up start
3925c20
Clean up start
f53f714
Route end-turn through the SetAutoPassUntilEndOfTurn envelope
MostCromulent b3bf969
Seed proxy with full pref set; drop redundant local override stores
MostCromulent d50617b
Skip AvailableActions compute in tryAutoPassNow when yielding.
MostCromulent 4249262
Clean up
6ee9452
Fix import
4049989
Minor clean up
1e7f792
Merge remote-tracking branch 'origin/master' into YieldReworkV2
MostCromulent 9fc5034
User Guide: split Auto-Yield section; add sidebar entry
MostCromulent 4b3f169
Don't let APINA skip declare-attackers; treat forced prompt as yield …
MostCromulent 10c706c
Unify yield-pref wire envelope; gate APINA recompute
MostCromulent 723a2b4
Remove getters
dab3fe3
Clean up obsolete refactor
928f870
Unify ESC yield-cancel through Input layer
MostCromulent 0c23bee
Split yield shortcuts: F2 stop-all, F3 APINA toggle
MostCromulent 5f34180
Unify yield shortcut: P toggles Auto-Pass or cancels active yields
MostCromulent b6f104a
Translate updated lblSHORTCUT_ENDTURN into other locales
MostCromulent 64b26fc
Refinements
8acf9a7
Remove redundancy
415c34c
Fix redundant label
f34b8ab
Fix redundant label
cb74bc8
Clean up
b03264c
Drop unused YieldController.resetForNewGame; fix yield threading comment
MostCromulent 66c72d1
Merge branch 'master' into YieldReworkV2
tool4ever 52f775e
Merge branch 'master' into YieldReworkV2
tool4ever e6cf292
Clean up
c3922a0
Fix missing labels
f8da52f
Clean up
8d35da9
Merge branch 'master' into YieldReworkV2
MostCromulent 9a8db62
Clear host UI when auto-passing through chooseSpellAbilityToPlay
MostCromulent c951dae
Merge branch 'master' into YieldReworkV2
tool4ever 4608f04
Clean up
9a07de0
Add Enable auto-pass checkbox to desktop Game menu
MostCromulent 484afc5
Merge branch 'master' into YieldReworkV2
tool4ever File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| ## Contents | ||
| - [How to Access](#how-to-access) | ||
| - [Auto-Pass](#auto-pass) | ||
| - [Yield markers](#yield-markers) | ||
| - [Yield Settings Menu](#yield-settings-menu) | ||
| - [Yield Interrupt Settings](#yield-interrupt-settings) | ||
| - [Automatic Yield Suggestions](#automatic-yield-suggestions) | ||
| - [Speed Options](#speed-options) | ||
| - [Troubleshooting](#troubleshooting) | ||
| - [Network Play](#network-play) | ||
|
|
||
| # Advanced Yield Options | ||
| The standard priority system in Forge can involve dozens of priority passes every turn. This can can slow down the game, particularly in multiplayer games like Commander, where one player's delay responding to priority halts the game for everybody else. | ||
|
|
||
| Forge offers a range of **Advanced Yield Options** to: | ||
|
|
||
| - enable players to automatically yield when there is no available action they can take. | ||
| - give players the ability to yield until a specific phase is reached, without responding to priority passes in the meantime. | ||
| - configure yield interrupt conditions, so you'll always get control back when something important happens (e.g. you are attacked or targeted by a spell). | ||
| - provide smart suggestions to enable yield if there are no useful actions you can take (e.g. it is another player's turn and you have no mana or playable cards). | ||
|
|
||
| These features are highly configurable through the **Yield Settings** dialog, and can be set up to suit your own gameplay preferences. | ||
|
|
||
| ## Auto-Pass | ||
|
|
||
| **Auto-Pass** is a persistent toggle (**P** on desktop, or the Auto-Pass icon on the dock) that automatically passes priority whenever you have no playable actions available. It's the simplest way to speed up games where you often have nothing to do — enable it once and Forge stops asking for input you'd only use to pass. The same **P** key also doubles as a "stop everything" shortcut: if any yield is currently active (transient yield or Auto-Pass itself), pressing P clears them all in one shot without dismissing any prompt that's up. | ||
|
|
||
| **How it works:** | ||
| - When enabled, Forge scans your hand, battlefield, and external zones (graveyard, exile, command) for castable spells, playable lands, and activatable abilities. | ||
| - If you have any available action, you keep priority as usual. | ||
| - If you have no available action, Forge passes priority on your behalf without prompting. | ||
| - On desktop, the Auto-Pass dock icon's background lights up gold while active. On mobile, the menu entry text reads `Auto-Pass: ON` / `Auto-Pass: OFF`. | ||
|
|
||
| **Interaction with interrupts:** By default, Auto-Pass ignores your interrupt settings — it keeps passing as long as you have no actions, regardless of attackers, opponent spells, mass-removal, etc. Enable **Auto-pass respects interrupts** in the Yield Interrupt Settings section if you want interrupts to break Auto-Pass too. | ||
|
|
||
| **Persistence:** Unlike yield markers, Auto-Pass does not end on a game event. It stays active until you toggle it off. | ||
|
|
||
| **Performance and timeout:** The action-availability scan can be expensive in complex board states. The scan is subject to the **Auto-pass calculation timeout** setting in the Yield Settings dialog. On timeout the system prompts you instead of auto-passing, so a false positive means an extra prompt rather than a long stall. The default is **Dynamic** — the budget scales with the number of playable cards (approximately 50ms per card, clamped between 50ms and 1500ms). Set your own value in the Yield Settings dialog to override. | ||
|
|
||
| > [!NOTE] | ||
| > **The Auto-pass AI is not perfect.** It is designed to avoid false negatives (passing priority when there is action you can take) as much as possible. There may be times it produces a false positive (giving you priority when there is nothing you can do). Use with appropriate caution. | ||
|
|
||
| ## Yield markers | ||
|
|
||
| A **yield marker** tells Forge to auto-pass priority until a specific phase is reached. Markers are set directly on the phase indicator strip in the match UI. | ||
|
|
||
| **Setting a marker:** | ||
| - **Desktop:** right-click the phase indicator cell for the phase you want to yield to. | ||
| - **Mobile:** long-press the phase indicator cell. | ||
|
|
||
| A fast-forward symbol will appear on the targeted cell to show the marker is active. The prompt area also describes what phase you are yielding to. Forge then auto-passes priority on your behalf until that phase is reached, at which point the marker clears automatically and you regain priority. | ||
|
|
||
| **Per-(player, phase) precision:** Each phase indicator cell is distinct per player. Right-clicking your own End Step yields to *your* End Step; right-clicking an opponent's End Step yields to *that opponent's* End Step. In multiplayer (e.g. four-player Commander) this lets you express things like "yield until that opponent's End Step" without affecting how you respond to the other opponents' end steps. | ||
|
|
||
| **Cancelling:** | ||
| - Right-click (or long-press) the marker again to cancel it. | ||
| - Press **ESC** (desktop) to cancel any active marker. | ||
| - An enabled interrupt firing (see [Yield Interrupt Settings](#yield-interrupt-settings)) cancels the marker and hands priority back to you. | ||
|
|
||
| **Re-targeting:** Right-clicking (or long-pressing) a different phase indicator while a marker is active moves the marker to the new cell. Only one marker is active at a time. | ||
|
|
||
| ## Yield Settings Menu | ||
|
|
||
| The **Yield Settings** dialog is the central configuration UI for yield behavior. It's accessible from: | ||
| - **Desktop:** the cog button on the dock, the Game menu > **Yield Settings** entry, or Ctrl+Y. | ||
| - **Mobile:** Game menu > **Yield Options**. | ||
|
|
||
| The dialog has three sections: | ||
|
|
||
| ### Yield Interrupt Settings | ||
|
|
||
| Yield markers, end-of-turn yield, and the interruptible **Yield to stack** automatically cancel when important game events occur. The non-interruptible **Resolve entire stack** is exempt — its purpose is to watch the stack resolve to completion, so opponent spells hitting the stack do not cancel it. | ||
|
|
||
| You can decide which game events interrupt a yield: | ||
|
|
||
| | Interrupt | Default | Description | | ||
| |-----------|---------|-------------| | ||
| | **Attackers declared against you** | ON | Triggers when creatures attack you specifically (not when other players are attacked). | | ||
| | **Opponent casts any spell** | ON | Triggers on opponent spells and activated abilities (not triggered abilities). | | ||
| | **You or your permanents targeted** | OFF | Triggers when a spell/ability targets you or something you control. | | ||
| | **Mass removal spell cast** | OFF | Triggers when an opponent casts a board wipe (DestroyAll / DamageAll / SacrificeAll / ChangeZoneAll spell). | | ||
| | **Triggered abilities on stack** | OFF | Triggers when triggered abilities are on the stack. | | ||
| | **Cards revealed or choices made** | OFF | Triggers when reveal dialogs / non-trivial value notifications fire. | | ||
|
|
||
| ### Automatic Yield Suggestions | ||
|
|
||
| When the system detects situations where you likely cannot take action, it can prompt you with a yield suggestion. Each suggestion type has a dropdown controlling its decline behavior: | ||
|
|
||
| | Suggestion | When it appears | Suggested action | Decline scope options | | ||
| |------------|-----------------|------------------|-----------------------| | ||
| | **Can't respond to stack** | You have no instant-speed responses available | Yield to stack (interruptible — auto-pass until stack empties or an interrupt fires) | Never (default) / Always / Once per stack / Once per turn | | ||
| | **No actions available** | No playable cards or activatable abilities (not your turn, stack empty) | Yield to your next turn | Never (default) / Always / Once per turn | | ||
|
|
||
| **Decline scope options:** | ||
| - **Never:** suggestion is disabled entirely (never shown). | ||
| - **Always:** suggestion re-appears on the next priority pass, even if just declined. | ||
| - **Once per stack:** declining suppresses the suggestion until the current stack resolves. A new stack will re-prompt. (Only available for "Can't respond to stack".) | ||
| - **Once per turn:** declining suppresses the suggestion for the rest of the current turn. | ||
|
|
||
| In addition to the per-suggestion dropdowns, this section contains two global suppression toggles: | ||
|
|
||
| - **Suppress on own turn** (default ON): suppress suggestions during your own turn, when you typically want to take actions. Suggestions are always suppressed on your first turn regardless of this setting, since you won't have any lands or mana yet. | ||
| - **Suppress immediately after yield ends** (default ON): suppress suggestions for one priority pass when a yield expires or is interrupted, giving you time to assess the game state before deciding whether to re-yield. | ||
|
|
||
| ### Speed Options | ||
|
|
||
| - **Skip delay between phases:** skip Forge's default 200ms delay between each phase resolving. | ||
| - **Skip delay when stack resolves:** skip Forge's default 400ms delay between items on the stack resolving. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Yield marker doesn't appear when right-clicking / long-pressing a phase indicator | ||
| - Markers cannot be set during pre-game, mulligan, or cleanup phases. | ||
|
|
||
| ### Yield clears unexpectedly | ||
| - Check interrupt settings in the Yield Settings dialog. | ||
| - A marker also clears automatically the moment its target phase is reached. | ||
|
|
||
| ### Smart suggestions not appearing | ||
| - Verify the suggestion's decline scope is not set to "Never" in the Yield Settings dialog. | ||
| - Suggestions don't appear if you're already yielding. | ||
| - If you declined a suggestion, check the decline scope to understand when it will re-appear. | ||
|
|
||
| ## Network Play | ||
| - Each player controls their own yield preferences. Your yield marker, stack-yield state, interrupt settings, and decline-scope choices apply to you only and propagate across the network — they do not affect other players. The host does not impose its own preferences on connected clients. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package forge.ai; | ||
|
|
||
| import forge.game.card.Card; | ||
| import forge.game.card.CardLists; | ||
| import forge.game.player.Player; | ||
| import forge.game.spellability.SpellAbility; | ||
| import forge.game.zone.ZoneType; | ||
| import org.tinylog.Logger; | ||
|
|
||
| import java.util.stream.Collectors; | ||
|
|
||
| // Heuristic: does the player have any playable action this priority window? | ||
| // Bounded by timeoutMs; returns true on expiry (false-positive — player is prompted). | ||
| public final class AvailableActions { | ||
|
|
||
| private AvailableActions() {} | ||
|
|
||
| public static boolean compute(Player player, long timeoutMs) { | ||
| long deadlineNanos = System.nanoTime() + timeoutMs * 1_000_000L; | ||
|
|
||
| for (Card card : sortedCardsIn(player, ZoneType.Hand)) { | ||
| for (SpellAbility sa : card.getAllPossibleAbilities(player, true)) { | ||
| if (checkTimeout(deadlineNanos, timeoutMs)) return true; | ||
| if (sa.isSpell()) { | ||
| if (canAfford(sa, player) && ComputerUtilAbility.isFullyTargetable(sa)) { | ||
| return true; | ||
| } | ||
| } else if (sa.isLandAbility()) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Not sorted: activation costs are per-ability, not the permanent's CMC. | ||
| for (Card card : player.getCardsIn(ZoneType.Battlefield)) { | ||
| for (SpellAbility sa : card.getAllPossibleAbilities(player, true)) { | ||
| if (checkTimeout(deadlineNanos, timeoutMs)) return true; | ||
| if (!sa.isManaAbility() && canAfford(sa, player) && ComputerUtilAbility.isFullyTargetable(sa)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (Card card : sortedCardsIn(player, ZoneType.Flashback)) { | ||
| for (SpellAbility sa : card.getAllPossibleAbilities(player, true)) { | ||
| if (checkTimeout(deadlineNanos, timeoutMs)) return true; | ||
| if (!sa.isManaAbility() && canAfford(sa, player) && ComputerUtilAbility.isFullyTargetable(sa)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| // Sort cheap cards first so cheap-to-validate matches early-exit | ||
| private static Iterable<Card> sortedCardsIn(Player player, ZoneType zone) { | ||
| return player.getCardsIn(zone).stream().sorted(CardLists.CmcComparator).collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private static boolean canAfford(SpellAbility sa, Player player) { | ||
| if (sa.getPayCosts() == null || !sa.getPayCosts().hasManaCost()) { | ||
| return true; | ||
| } | ||
| return ComputerUtilMana.canPayManaCost(sa, player, 0, false); | ||
| } | ||
|
|
||
| private static boolean checkTimeout(long deadlineNanos, long timeoutMs) { | ||
| if (System.nanoTime() < deadlineNanos) { | ||
| return false; | ||
| } | ||
| Logger.warn("AvailableActions: heuristic timed out after {}ms; returning true.", timeoutMs); | ||
| return true; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.