diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 6da98b38f02..7fad187ae5a 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -998,9 +998,8 @@ public void resetOnceResolved() { public String yieldKey() { if (getHostCard() != null) { return getHostCard().toString() + ": " + toUnsuppressedString(); - } else { - return toUnsuppressedString(); } + return toUnsuppressedString(); } public String getStackDescription() { diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index 312aeeb04a7..14cbcfbcf2e 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -120,33 +120,29 @@ public final String toString() { } public String toString(boolean active) { - if (hasParam("TriggerDescription") && !this.isSuppressed()) { - StringBuilder sb = new StringBuilder(); - ITranslatable nameSource = getHostName(this); - String desc = getParam("TriggerDescription"); - if (!desc.contains("ABILITY")) { - desc = CardTranslation.translateSingleDescriptionText(getParam("TriggerDescription"), nameSource); - String translatedName = nameSource.getTranslatedName(); - desc = TextUtil.fastReplace(desc,"CARDNAME", translatedName); - desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(translatedName)); - if (desc.contains("ORIGINALHOST") && this.getOriginalHost() != null) { - desc = TextUtil.fastReplace(desc, "ORIGINALHOST", this.getOriginalHost().getDisplayName()); - } - } - if (getHostCard().getEffectSource() != null) { - if (active) - desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString()); - else - desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().getDisplayName()); - } - sb.append(desc); - if (!this.triggerRemembered.isEmpty()) { - sb.append(" (").append(this.triggerRemembered).append(")"); - } - return sb.toString(); - } else { + if (!hasParam("TriggerDescription") || isSuppressed()) { return ""; } + StringBuilder sb = new StringBuilder(); + ITranslatable nameSource = getHostName(this); + String desc = getParam("TriggerDescription"); + if (!desc.contains("ABILITY")) { + desc = CardTranslation.translateSingleDescriptionText(getParam("TriggerDescription"), nameSource); + String translatedName = nameSource.getTranslatedName(); + desc = TextUtil.fastReplace(desc,"CARDNAME", translatedName); + desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(translatedName)); + if (desc.contains("ORIGINALHOST") && this.getOriginalHost() != null) { + desc = TextUtil.fastReplace(desc, "ORIGINALHOST", this.getOriginalHost().getDisplayName()); + } + } + if (getHostCard().getEffectSource() != null) { + if (active) + desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString()); + else + desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().getDisplayName()); + } + sb.append(desc); + return sb.toString(); } public final String replaceAbilityText(final String desc, final CardState state) { diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index cdc8f2151ad..bdd9d6d5a69 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -155,12 +155,10 @@ public String yieldKey() { if (getTrigger() != null) { if (getHostCard() != null) { return getHostCard().toString() + ": " + getTrigger().toString(); - } else { - return getTrigger().toString(); } - } else { - return super.yieldKey(); + return getTrigger().toString(); } + return super.yieldKey(); } // include triggering information so that different effects look different @@ -185,6 +183,9 @@ public String getStackDescription(boolean withTargets) { if (regtrig == null) return ""; final StringBuilder sb = new StringBuilder(regtrig.replaceAbilityText(regtrig.toString(true), this, true)); + if (!regtrig.getTriggerRemembered().isEmpty()) { + sb.append(" (").append(regtrig.getTriggerRemembered()).append(")"); + } // prevent text growing too long when SA target other in a chain and also potential StackOverflow if (withTargets) { diff --git a/forge-gui-desktop/src/main/java/forge/control/KeyboardShortcuts.java b/forge-gui-desktop/src/main/java/forge/control/KeyboardShortcuts.java index 526d8d93d46..5a8a8d19702 100644 --- a/forge-gui-desktop/src/main/java/forge/control/KeyboardShortcuts.java +++ b/forge-gui-desktop/src/main/java/forge/control/KeyboardShortcuts.java @@ -24,6 +24,7 @@ import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; +import forge.player.AutoYieldStore.TriggerDecision; import forge.screens.home.settings.VSubmenuPreferences.KeyboardShortcutField; import forge.screens.match.CMatchUI; import forge.toolbox.special.CardZoomer; @@ -159,12 +160,11 @@ public void actionPerformed(final ActionEvent e) { if (matchUI == null) { return; } StackItemView si = matchUI.getGameView().peekStack(); if (si != null && si.isAbility()) { - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); - matchUI.getGameController().setShouldAutoYield(si.getKey(), true, abilityScope); - int triggerID = si.getSourceTrigger(); - if (si.isOptionalTrigger() && matchUI.isLocalPlayer(si.getActivatingPlayer())) { - matchUI.getGameController().setShouldAlwaysAcceptTrigger(triggerID); + boolean abilityScope = matchUI.getGameController().getYieldController().isAbilityScope(); + String key = si.getKey(); + matchUI.getGameController().setShouldAutoYield(key, true, abilityScope); + if (si.isOptionalTrigger() && matchUI.isLocalPlayer(si.getActivatingPlayer()) && !key.isEmpty()) { + matchUI.getGameController().setTriggerDecision(key, TriggerDecision.ACCEPT, abilityScope); } matchUI.getGameController().passPriority(); } @@ -179,12 +179,11 @@ public void actionPerformed(final ActionEvent e) { if (matchUI == null) { return; } StackItemView si = matchUI.getGameView().peekStack(); if (si != null && si.isAbility()) { - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); - matchUI.getGameController().setShouldAutoYield(si.getKey(), true, abilityScope); - int triggerID = si.getSourceTrigger(); - if (si.isOptionalTrigger() && matchUI.isLocalPlayer(si.getActivatingPlayer())) { - matchUI.getGameController().setShouldAlwaysDeclineTrigger(triggerID); + boolean abilityScope = matchUI.getGameController().getYieldController().isAbilityScope(); + String key = si.getKey(); + matchUI.getGameController().setShouldAutoYield(key, true, abilityScope); + if (si.isOptionalTrigger() && matchUI.isLocalPlayer(si.getActivatingPlayer()) && !key.isEmpty()) { + matchUI.getGameController().setTriggerDecision(key, TriggerDecision.DECLINE, abilityScope); } matchUI.getGameController().passPriority(); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java index 1fc86bbf9c4..b3247c483f9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java @@ -227,7 +227,7 @@ public void initialize() { initializeLandPlayedComboBox(); initializeColorIdentityCombobox(); initializeSwitchStatesCombobox(); - initializeAutoYieldModeComboBox(); + initializeAutoDecisionModeComboBox(); initializeStackGroupPermanentsComboBox(); initializeMaxStackDepthComboBox(); initializeCounterDisplayTypeComboBox(); @@ -571,15 +571,15 @@ private void initializeSwitchStatesCombobox() { panel.setComboBox(comboBox, selectedItem); } - private void initializeAutoYieldModeComboBox() { + private void initializeAutoDecisionModeComboBox() { final String[] elems = { - ForgeConstants.AUTO_YIELD_PER_CARD, - ForgeConstants.AUTO_YIELD_PER_ABILITY, - ForgeConstants.AUTO_YIELD_PER_ABILITY_SESSION, - ForgeConstants.AUTO_YIELD_PER_ABILITY_INSTALL, + ForgeConstants.AUTO_DECISION_PER_CARD, + ForgeConstants.AUTO_DECISION_PER_ABILITY, + ForgeConstants.AUTO_DECISION_PER_ABILITY_SESSION, + ForgeConstants.AUTO_DECISION_PER_ABILITY_INSTALL, }; - final FPref userSetting = FPref.UI_AUTO_YIELD_MODE; - final FComboBoxPanel panel = this.view.getAutoYieldModeComboBoxPanel(); + final FPref userSetting = FPref.UI_AUTO_DECISION_MODE; + final FComboBoxPanel panel = this.view.getAutoDecisionModeComboBoxPanel(); final FComboBox comboBox = createComboBox(elems, userSetting); final String selectedItem = this.prefs.getPref(userSetting); panel.setComboBox(comboBox, selectedItem); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java index a5f0dc7c341..1abb37bef61 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java @@ -152,7 +152,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final FComboBoxPanel cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":"); private final FComboBoxPanel cbpLandPlayed = new FComboBoxPanel<>(localizer.getMessage("cbpLandPlayed")+":"); private final FComboBoxPanel cbpDisplayCurrentCardColors = new FComboBoxPanel<>(localizer.getMessage("cbpDisplayCurrentCardColors")+":"); - private final FComboBoxPanel cbpAutoYieldMode = new FComboBoxPanel<>(localizer.getMessage("cbpAutoYieldMode")+":"); + private final FComboBoxPanel cbpAutoDecisionMode = new FComboBoxPanel<>(localizer.getMessage("cbpAutoDecisionMode")+":"); private final FComboBoxPanel cbpStackGroupPermanents = new FComboBoxPanel<>(localizer.getMessage("cbpStackGroupPermanents")+":"); private final FComboBoxPanel cbpMaxStackDepth = new FComboBoxPanel<>(localizer.getMessage("cbpMaxStackDepth")+":"); private final FComboBoxPanel cbpCounterDisplayType = new FComboBoxPanel<>(localizer.getMessage("cbpCounterDisplayType")+":"); @@ -301,8 +301,8 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbpGraveyardOrdering, comboBoxConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpGraveyardOrdering")), descriptionConstraints); - pnlPrefs.add(cbpAutoYieldMode, comboBoxConstraints); - pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpAutoYieldMode")), descriptionConstraints); + pnlPrefs.add(cbpAutoDecisionMode, comboBoxConstraints); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpAutoDecisionMode")), descriptionConstraints); //Server Preferences pnlPrefs.add(new SectionLabel(localizer.getMessage("ServerPreferences")), sectionConstraints); @@ -902,9 +902,8 @@ public FComboBoxPanel getCbpDefaultLanguageComboBoxPanel() { return cbpDefaultLanguage; } - - public FComboBoxPanel getAutoYieldModeComboBoxPanel() { - return cbpAutoYieldMode; + public FComboBoxPanel getAutoDecisionModeComboBoxPanel() { + return cbpAutoDecisionMode; } public FComboBoxPanel getCbpStackGroupPermanents() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index bb20baed87e..7802c09dba9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -219,11 +219,6 @@ public String getAvatarImage(final String playerName) { return avatarImages.get(playerName); } - @Override - public boolean isLibgdxPort() { - return false; - } - @Override public void setGameView(GameView gameView0) { super.setGameView(gameView0); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAutoYields.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAutoYields.java deleted file mode 100644 index 7070c4e5915..00000000000 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VAutoYields.java +++ /dev/null @@ -1,109 +0,0 @@ -package forge.screens.match; - -import java.awt.Dimension; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.AbstractListModel; - -import forge.Singletons; -import forge.gui.UiCommand; -import forge.toolbox.FButton; -import forge.toolbox.FCheckBox; -import forge.toolbox.FList; -import forge.toolbox.FOptionPane; -import forge.toolbox.FScrollPane; -import forge.util.Localizer; -import forge.view.FDialog; - -@SuppressWarnings("serial") -public class VAutoYields extends FDialog { - private static final int PADDING = 10; - private static final int BUTTON_WIDTH = 150; - private static final int BUTTON_HEIGHT = 26; - - private final FButton btnOk; - private final FButton btnRemove; - private final FList lstAutoYields; - private final FScrollPane listScroller; - private final FCheckBox chkDisableAll; - private final List autoYields; - - public VAutoYields(final CMatchUI matchUI) { - super(); - setTitle(Localizer.getInstance().getMessage("lblAutoYields")); - - autoYields = new ArrayList<>(); - for (final String autoYield : matchUI.getGameController().getYieldController().getAutoYields()) { - autoYields.add(autoYield); - } - lstAutoYields = new FList<>(new AutoYieldsListModel()); - - int x = PADDING; - int y = PADDING; - int width = Singletons.getView().getFrame().getWidth() * 2 / 3; - int w = width - 2 * PADDING; - - listScroller = new FScrollPane(lstAutoYields, true); - - chkDisableAll = new FCheckBox(Localizer.getInstance().getMessage("lblDisableAllAutoYields"), matchUI.getGameController().getDisableAutoYields()); - chkDisableAll.addChangeListener(e -> matchUI.getGameController().setDisableAutoYields(chkDisableAll.isSelected())); - - btnOk = new FButton(Localizer.getInstance().getMessage("lblOK")); - btnOk.setCommand((UiCommand) () -> setVisible(false)); - btnRemove = new FButton(Localizer.getInstance().getMessage("lblRemoveYield")); - btnRemove.setCommand((UiCommand) () -> { - String selected = lstAutoYields.getSelectedValue(); - if (selected != null) { - autoYields.remove(selected); - btnRemove.setEnabled(autoYields.size() > 0); - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); - matchUI.getGameController().setShouldAutoYield(selected, false, abilityScope); - VAutoYields.this.revalidate(); - lstAutoYields.repaint(); - } - }); - if (autoYields.size() > 0) { - lstAutoYields.setSelectedIndex(0); - } - else { - btnRemove.setEnabled(false); - } - - Dimension checkBoxSize = chkDisableAll.getPreferredSize(); - int listHeight = lstAutoYields.getMinimumSize().height + 2 * PADDING; - - add(listScroller, x, y, w, listHeight); - y += listHeight + PADDING; - add(chkDisableAll, x, y, checkBoxSize.width, checkBoxSize.height); - x = w - 2 * BUTTON_WIDTH - PADDING; - add(btnOk, x, y, BUTTON_WIDTH, BUTTON_HEIGHT); - x += BUTTON_WIDTH + PADDING; - add(btnRemove, x, y, BUTTON_WIDTH, BUTTON_HEIGHT); - - this.pack(); - this.setSize(width, getHeight()); - } - - private class AutoYieldsListModel extends AbstractListModel { - @Override - public int getSize() { - return autoYields.size(); - } - - @Override - public String getElementAt(final int index) { - return autoYields.get(index); - } - } - - public void showAutoYields() { - if (lstAutoYields.getCount() > 0) { - setVisible(true); - dispose(); - } else { - FOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblNoActiveAutoYield"), Localizer.getInstance().getMessage("lblNoAutoYield"), FOptionPane.INFORMATION_ICON); - } - } -} diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAutoYieldsAndTriggers.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAutoYieldsAndTriggers.java new file mode 100644 index 00000000000..1dab5bcd3bd --- /dev/null +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAutoYieldsAndTriggers.java @@ -0,0 +1,201 @@ +package forge.screens.match; + +import java.awt.Dimension; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import javax.swing.AbstractListModel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import forge.Singletons; +import forge.gui.UiCommand; +import forge.player.AutoYieldStore; +import forge.toolbox.FButton; +import forge.toolbox.FCheckBox; +import forge.toolbox.FList; +import forge.toolbox.FOptionPane; +import forge.toolbox.FScrollPane; +import forge.toolbox.FTextField; +import forge.util.Localizer; +import forge.view.FDialog; + +@SuppressWarnings("serial") +public class VAutoYieldsAndTriggers extends FDialog { + private static final int PADDING = 10; + private static final int BUTTON_WIDTH = 150; + private static final int BUTTON_HEIGHT = 26; + + private static final String YIELD_PREFIX = "[" + Localizer.getInstance().getMessage("lblYield") + "] "; + private static final String ACCEPT_PREFIX = "[" + Localizer.getInstance().getMessage("lblAlwaysYes") + "] "; + private static final String DECLINE_PREFIX = "[" + Localizer.getInstance().getMessage("lblAlwaysNo") + "] "; + + /** Sort by the post-tag substring so same-card entries sit adjacent regardless of tag. */ + private static final Comparator ENTRY_COMPARATOR = (a, b) -> { + Collator c = Collator.getInstance(); + int byCard = c.compare(stripTag(a), stripTag(b)); + return byCard != 0 ? byCard : c.compare(a, b); + }; + + private final FButton btnOk; + private final FButton btnRemove; + private final FTextField filterField; + private final FList lstEntries; + private final FScrollPane listScroller; + private final FCheckBox chkDisableYields; + private final FCheckBox chkDisableTriggers; + /** Master list, sorted, mutated by Remove. */ + private final List allEntries; + /** Currently displayed subset (filtered view of allEntries). */ + private final List visibleEntries; + private final EntriesListModel listModel; + + public VAutoYieldsAndTriggers(final CMatchUI matchUI) { + super(); + setTitle(Localizer.getInstance().getMessage("lblAutoYieldsAndTriggers")); + + allEntries = new ArrayList<>(); + for (final String key : matchUI.getGameController().getYieldController().getAutoYields()) { + allEntries.add(YIELD_PREFIX + key); + } + for (final Map.Entry e : matchUI.getGameController().getYieldController().getAutoTriggers()) { + String prefix = e.getValue() == AutoYieldStore.TriggerDecision.ACCEPT ? ACCEPT_PREFIX : DECLINE_PREFIX; + allEntries.add(prefix + e.getKey()); + } + Collections.sort(allEntries, ENTRY_COMPARATOR); + visibleEntries = new ArrayList<>(allEntries); + + listModel = new EntriesListModel(); + lstEntries = new FList<>(listModel); + + filterField = new FTextField.Builder() + .ghostText(Localizer.getInstance().getMessage("lblSearch")) + .showGhostTextWithFocus() + .build(); + filterField.getDocument().addDocumentListener(new DocumentListener() { + @Override public void insertUpdate(DocumentEvent e) { applyFilter(); } + @Override public void removeUpdate(DocumentEvent e) { applyFilter(); } + @Override public void changedUpdate(DocumentEvent e) { applyFilter(); } + }); + + int x = PADDING; + int y = PADDING; + int width = Singletons.getView().getFrame().getWidth() * 2 / 3; + int w = width - 2 * PADDING; + + listScroller = new FScrollPane(lstEntries, true); + + chkDisableYields = new FCheckBox(Localizer.getInstance().getMessage("lblDisableAllAutoYields"), + matchUI.getGameController().getDisableAutoYields()); + chkDisableYields.addChangeListener(e -> matchUI.getGameController().setDisableAutoYields(chkDisableYields.isSelected())); + + chkDisableTriggers = new FCheckBox(Localizer.getInstance().getMessage("lblDisableAllAutoTriggers"), + matchUI.getGameController().getDisableAutoTriggers()); + chkDisableTriggers.addChangeListener(e -> matchUI.getGameController().setDisableAutoTriggers(chkDisableTriggers.isSelected())); + + btnOk = new FButton(Localizer.getInstance().getMessage("lblOK")); + btnOk.setCommand((UiCommand) () -> setVisible(false)); + btnRemove = new FButton(Localizer.getInstance().getMessage("lblRemove")); + btnRemove.setCommand((UiCommand) () -> { + String selected = lstEntries.getSelectedValue(); + if (selected == null) return; + allEntries.remove(selected); + visibleEntries.remove(selected); + listModel.refresh(); + btnRemove.setEnabled(!allEntries.isEmpty()); + boolean abilityScope = matchUI.getGameController().getYieldController().isAbilityScope(); + if (selected.startsWith(YIELD_PREFIX)) { + String key = selected.substring(YIELD_PREFIX.length()); + matchUI.getGameController().setShouldAutoYield(key, false, abilityScope); + } else if (selected.startsWith(ACCEPT_PREFIX)) { + String key = selected.substring(ACCEPT_PREFIX.length()); + matchUI.getGameController().setTriggerDecision(key, AutoYieldStore.TriggerDecision.ASK, abilityScope); + } else if (selected.startsWith(DECLINE_PREFIX)) { + String key = selected.substring(DECLINE_PREFIX.length()); + matchUI.getGameController().setTriggerDecision(key, AutoYieldStore.TriggerDecision.ASK, abilityScope); + } + VAutoYieldsAndTriggers.this.revalidate(); + lstEntries.repaint(); + }); + if (!allEntries.isEmpty()) { + lstEntries.setSelectedIndex(0); + } + else { + btnRemove.setEnabled(false); + } + + Dimension yieldChkSize = chkDisableYields.getPreferredSize(); + Dimension trigChkSize = chkDisableTriggers.getPreferredSize(); + int filterHeight = FTextField.HEIGHT; + int listHeight = lstEntries.getMinimumSize().height + 2 * PADDING; + int chkHeight = Math.max(yieldChkSize.height, trigChkSize.height); + + add(filterField, x, y, w, filterHeight); + y += filterHeight + PADDING; + add(listScroller, x, y, w, listHeight); + y += listHeight + PADDING; + add(chkDisableYields, x, y, yieldChkSize.width, chkHeight); + add(chkDisableTriggers, x + yieldChkSize.width + PADDING, y, trigChkSize.width, chkHeight); + y += chkHeight + PADDING; + x = w - 2 * BUTTON_WIDTH - PADDING; + add(btnOk, x, y, BUTTON_WIDTH, BUTTON_HEIGHT); + x += BUTTON_WIDTH + PADDING; + add(btnRemove, x, y, BUTTON_WIDTH, BUTTON_HEIGHT); + + this.pack(); + this.setSize(width, getHeight()); + } + + private void applyFilter() { + String needle = filterField.getText(); + visibleEntries.clear(); + if (needle == null || needle.isEmpty()) { + visibleEntries.addAll(allEntries); + } else { + String lower = needle.toLowerCase(); + for (String e : allEntries) { + if (e.toLowerCase().contains(lower)) visibleEntries.add(e); + } + } + listModel.refresh(); + } + + private static String stripTag(String s) { + if (s.startsWith(YIELD_PREFIX)) return s.substring(YIELD_PREFIX.length()); + if (s.startsWith(ACCEPT_PREFIX)) return s.substring(ACCEPT_PREFIX.length()); + if (s.startsWith(DECLINE_PREFIX)) return s.substring(DECLINE_PREFIX.length()); + return s; + } + + private class EntriesListModel extends AbstractListModel { + @Override + public int getSize() { + return visibleEntries.size(); + } + + @Override + public String getElementAt(final int index) { + return visibleEntries.get(index); + } + + void refresh() { + fireContentsChanged(this, 0, Math.max(0, visibleEntries.size() - 1)); + } + } + + public void showDialog() { + if (!allEntries.isEmpty()) { + setVisible(true); + dispose(); + } else { + FOptionPane.showMessageDialog( + Localizer.getInstance().getMessage("lblNoActiveAutoYieldOrTrigger"), + Localizer.getInstance().getMessage("lblNoAutoYieldOrTrigger"), + FOptionPane.INFORMATION_ICON); + } + } +} diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java b/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java index 8c27de5eb71..39484bdb0e0 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java @@ -18,7 +18,7 @@ import forge.menus.MenuUtil; import forge.model.FModel; import forge.screens.match.CMatchUI; -import forge.screens.match.VAutoYields; +import forge.screens.match.VAutoYieldsAndTriggers; import forge.screens.match.views.VField; import forge.screens.match.controllers.CDock.ArcState; import forge.toolbox.FSkin.SkinIcon; @@ -56,7 +56,7 @@ public JMenu getMenu() { menu.add(getSubmenu_StackGroupPermanents()); menu.add(getMenuItem_TokensSeparateRow()); menu.add(getMenuItem_SeparateCombatStacks()); - menu.add(getMenuItem_AutoYields()); + menu.add(getMenuItem_AutoYieldsAndTriggers()); menu.addSeparator(); menu.add(getMenuItem_ViewDeckList()); return menu; @@ -175,20 +175,14 @@ private static void setTargetingArcMenuIcon(SkinnedRadioButtonMenuItem item) { menu.setIcon(item.getIcon()); } - private SkinnedMenuItem getMenuItem_AutoYields() { + private SkinnedMenuItem getMenuItem_AutoYieldsAndTriggers() { final Localizer localizer = Localizer.getInstance(); - final SkinnedMenuItem menuItem = new SkinnedMenuItem(localizer.getMessage("lblAutoYields")); + final SkinnedMenuItem menuItem = new SkinnedMenuItem(localizer.getMessage("lblAutoYieldsAndTriggers")); menuItem.setIcon((showIcons ? MenuUtil.getMenuIcon(FSkinProp.ICO_WARNING) : null)); - menuItem.addActionListener(getAutoYieldsAction()); + menuItem.addActionListener(e -> new VAutoYieldsAndTriggers(matchUI).showDialog()); return menuItem; } - private ActionListener getAutoYieldsAction() { - return e -> { - new VAutoYields(matchUI).showAutoYields(); - }; - } - private SkinnedMenuItem getMenuItem_ViewDeckList() { final Localizer localizer = Localizer.getInstance(); final SkinnedMenuItem menuItem = new SkinnedMenuItem(localizer.getMessage("lblDeckList")); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java index 521c48a1ab4..2b67ab9a29e 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java @@ -46,6 +46,8 @@ import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; import forge.gui.framework.IVDoc; +import forge.interfaces.IGameController; +import forge.player.AutoYieldStore.TriggerDecision; import forge.screens.match.controllers.CDock.ArcState; import forge.screens.match.controllers.CStack; import forge.toolbox.FMouseAdapter; @@ -301,16 +303,14 @@ private final class AbilityMenu extends JPopupMenu { private final JMenuItem jmiYieldToEntireStack; private StackItemView item; - private Integer triggerID = 0; + private String yieldKey = ""; + private boolean abilityScope; public AbilityMenu(){ jmiAutoYield = new JCheckBoxMenuItem(Localizer.getInstance().getMessage("cbpAutoYieldMode")); jmiAutoYield.addActionListener(arg0 -> { - final String key = item.getKey(); - final boolean autoYield = controller.getMatchUI().getGameController().shouldAutoYield(key); - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); - controller.getMatchUI().getGameController().setShouldAutoYield(key, !autoYield, abilityScope); + final boolean autoYield = controller.getMatchUI().getGameController().shouldAutoYield(yieldKey); + controller.getMatchUI().getGameController().setShouldAutoYield(yieldKey, !autoYield, abilityScope); if (!autoYield && controller.getMatchUI().getGameView().peekStack() == item) { //auto-pass priority if ability is on top of stack controller.getMatchUI().getGameController().passPriority(); @@ -320,23 +320,19 @@ public AbilityMenu(){ jmiAlwaysYes = new JCheckBoxMenuItem(Localizer.getInstance().getMessage("lblAlwaysYes")); jmiAlwaysYes.addActionListener(arg0 -> { - if (controller.getMatchUI().getGameController().shouldAlwaysAcceptTrigger(triggerID)) { - controller.getMatchUI().getGameController().setShouldAlwaysAskTrigger(triggerID); - } - else { - controller.getMatchUI().getGameController().setShouldAlwaysAcceptTrigger(triggerID); - } + if (yieldKey.isEmpty()) return; + IGameController gc = controller.getMatchUI().getGameController(); + TriggerDecision next = gc.getTriggerDecision(yieldKey) == TriggerDecision.ACCEPT ? TriggerDecision.ASK : TriggerDecision.ACCEPT; + gc.setTriggerDecision(yieldKey, next, abilityScope); }); add(jmiAlwaysYes); jmiAlwaysNo = new JCheckBoxMenuItem(Localizer.getInstance().getMessage("lblAlwaysNo")); jmiAlwaysNo.addActionListener(arg0 -> { - if (controller.getMatchUI().getGameController().shouldAlwaysDeclineTrigger(triggerID)) { - controller.getMatchUI().getGameController().setShouldAlwaysAskTrigger(triggerID); - } - else { - controller.getMatchUI().getGameController().setShouldAlwaysDeclineTrigger(triggerID); - } + if (yieldKey.isEmpty()) return; + IGameController gc = controller.getMatchUI().getGameController(); + TriggerDecision next = gc.getTriggerDecision(yieldKey) == TriggerDecision.DECLINE ? TriggerDecision.ASK : TriggerDecision.DECLINE; + gc.setTriggerDecision(yieldKey, next, abilityScope); }); add(jmiAlwaysNo); @@ -352,15 +348,17 @@ public AbilityMenu(){ public void setStackInstance(final StackItemView item0) { item = item0; - triggerID = item.getSourceTrigger(); + yieldKey = item.getKey(); + abilityScope = controller.getMatchUI().getGameController().getYieldController().isAbilityScope(); jmiAutoYield.setVisible(item.isAbility()); jmiAutoYield.setSelected(item.isAbility() - && controller.getMatchUI().getGameController().shouldAutoYield(item.getKey())); + && controller.getMatchUI().getGameController().shouldAutoYield(yieldKey)); - if (item.isOptionalTrigger() && controller.getMatchUI().isLocalPlayer(item.getActivatingPlayer())) { - jmiAlwaysYes.setSelected(controller.getMatchUI().getGameController().shouldAlwaysAcceptTrigger(triggerID)); - jmiAlwaysNo.setSelected(controller.getMatchUI().getGameController().shouldAlwaysDeclineTrigger(triggerID)); + if (item.isOptionalTrigger() && controller.getMatchUI().isLocalPlayer(item.getActivatingPlayer()) && !yieldKey.isEmpty()) { + TriggerDecision decision = controller.getMatchUI().getGameController().getTriggerDecision(yieldKey); + jmiAlwaysYes.setSelected(decision == TriggerDecision.ACCEPT); + jmiAlwaysNo.setSelected(decision == TriggerDecision.DECLINE); jmiAlwaysYes.setVisible(true); jmiAlwaysNo.setVisible(true); } else { diff --git a/forge-gui-desktop/src/test/java/forge/net/HeadlessNetworkGuiGame.java b/forge-gui-desktop/src/test/java/forge/net/HeadlessNetworkGuiGame.java index 66f60c1acca..7539d2962c3 100644 --- a/forge-gui-desktop/src/test/java/forge/net/HeadlessNetworkGuiGame.java +++ b/forge-gui-desktop/src/test/java/forge/net/HeadlessNetworkGuiGame.java @@ -82,7 +82,6 @@ public void setGameView(forge.game.GameView gameView) { @Override public void updatePhase(boolean saveState) { } @Override public void updateTurn(PlayerView player) { } @Override public void updatePlayerControl() { } - @Override public boolean isLibgdxPort() { return false; } @Override public void enableOverlay() { } @Override public void disableOverlay() { } @Override public void showManaPool(PlayerView player) { } diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index c9fd9c45db5..9480eb49608 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -93,11 +93,6 @@ public static MatchScreen getView() { return view; } - @Override - public boolean isLibgdxPort() { - return true; - } - @Override protected void updateCurrentPlayer(final PlayerView player) { for (final PlayerView other : getLocalPlayers()) { diff --git a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java index f92530c42e7..4badae1f341 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java @@ -49,6 +49,7 @@ import forge.menu.FMenuItem; import forge.menu.FMenuTab; import forge.model.FModel; +import forge.player.AutoYieldStore.TriggerDecision; import forge.player.PlayerZoneUpdate; import forge.screens.FScreen; import forge.screens.match.views.VAvatar; @@ -688,17 +689,13 @@ public boolean keyDown(int keyCode) { if (!stackInstance.isAbility()) { return false; } - final int triggerID = stackInstance.getSourceTrigger(); - - if (controller.shouldAlwaysAcceptTrigger(triggerID)) { - controller.setShouldAlwaysAskTrigger(triggerID); - } else { - controller.setShouldAlwaysAcceptTrigger(triggerID); + final boolean abilityScope = controller.getYieldController().isAbilityScope(); + final String key = stackInstance.getKey(); + if (!key.isEmpty()) { + TriggerDecision next = controller.getTriggerDecision(key) == TriggerDecision.ACCEPT ? TriggerDecision.ASK : TriggerDecision.ACCEPT; + controller.setTriggerDecision(key, next, abilityScope); } - final String key = stackInstance.getKey(); - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); controller.setShouldAutoYield(key, true, abilityScope); if (stackInstance.equals(gameView.peekStack())) { //auto-pass priority if ability is on top of stack @@ -718,18 +715,14 @@ public boolean keyDown(int keyCode) { if (!stackInstance.isAbility()) { return false; } - final int triggerID = stackInstance.getSourceTrigger(); - - if (controller.shouldAlwaysDeclineTrigger(triggerID)) { - controller.setShouldAlwaysAskTrigger(triggerID); - } else { - controller.setShouldAlwaysDeclineTrigger(triggerID); + final boolean abilityScope = controller.getYieldController().isAbilityScope(); + final String key = stackInstance.getKey(); + if (!key.isEmpty()) { + TriggerDecision next = controller.getTriggerDecision(key) == TriggerDecision.DECLINE ? TriggerDecision.ASK : TriggerDecision.DECLINE; + controller.setTriggerDecision(key, next, abilityScope); } - final String key = stackInstance.getKey(); - boolean abilityScope2 = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); - controller.setShouldAutoYield(key, true, abilityScope2); + controller.setShouldAutoYield(key, true, abilityScope); if (stackInstance.equals(gameView.peekStack())) { //auto-pass priority if ability is on top of stack controller.passPriority(); diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAutoYields.java b/forge-gui-mobile/src/forge/screens/match/views/VAutoYields.java deleted file mode 100644 index 6d69c04b8ce..00000000000 --- a/forge-gui-mobile/src/forge/screens/match/views/VAutoYields.java +++ /dev/null @@ -1,84 +0,0 @@ -package forge.screens.match.views; - -import java.util.ArrayList; -import java.util.List; - -import forge.Forge; -import forge.screens.match.MatchController; -import forge.toolbox.FCheckBox; -import forge.toolbox.FChoiceList; -import forge.toolbox.FDialog; -import forge.toolbox.FOptionPane; -import forge.util.TextBounds; - -public class VAutoYields extends FDialog { - private final FChoiceList lstAutoYields; - private final FCheckBox chkDisableAll; - - public VAutoYields() { - super(Forge.getLocalizer().getMessage("lblAutoYields"), 2); - List autoYields = new ArrayList<>(); - for (String autoYield : MatchController.instance.getGameController().getYieldController().getAutoYields()) { - autoYields.add(autoYield); - } - lstAutoYields = add(new FChoiceList(autoYields) { - @Override - protected void onCompactModeChange() { - VAutoYields.this.revalidate(); //revalidate entire dialog so height updated - } - - @Override - protected boolean allowDefaultItemWrap() { - return true; - } - }); - chkDisableAll = add(new FCheckBox(Forge.getLocalizer().getMessage("lblDisableAllAutoYields"), MatchController.instance.getGameController().getDisableAutoYields())); - chkDisableAll.setCommand(e -> MatchController.instance.getGameController().setDisableAutoYields(chkDisableAll.isSelected())); - initButton(0, Forge.getLocalizer().getMessage("lblOK"), e -> hide()); - initButton(1, Forge.getLocalizer().getMessage("lblRemoveYield"), e -> { - String selected = lstAutoYields.getSelectedItem(); - if (selected != null) { - lstAutoYields.removeItem(selected); - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); - MatchController.instance.getGameController().setShouldAutoYield(selected, false, abilityScope); - setButtonEnabled(1, lstAutoYields.getCount() > 0); - lstAutoYields.cleanUpSelections(); - VAutoYields.this.revalidate(); - } - }); - setButtonEnabled(1, autoYields.size() > 0); - } - - @Override - public void show() { - if (lstAutoYields.getCount() > 0) { - super.show(); - } - else { - FOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblNoActiveAutoYield"), Forge.getLocalizer().getMessage("lblNoAutoYield"), FOptionPane.INFORMATION_ICON); - } - } - - @Override - protected float layoutAndGetHeight(float width, float maxHeight) { - float padding = FOptionPane.PADDING; - float x = padding; - float y = padding; - float w = width - 2 * padding; - TextBounds checkBoxSize = chkDisableAll.getAutoSizeBounds(); - - float listHeight = lstAutoYields.getListItemRenderer().getItemHeight() * lstAutoYields.getCount(); - float maxListHeight = maxHeight - 3 * padding - checkBoxSize.height; - if (listHeight > maxListHeight) { - listHeight = maxListHeight; - } - - lstAutoYields.setBounds(x, y, w, listHeight); - y += listHeight + padding; - chkDisableAll.setBounds(x, y, Math.min(checkBoxSize.width, w), checkBoxSize.height); - y += checkBoxSize.height + padding; - - return y; - } -} diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAutoYieldsAndTriggers.java b/forge-gui-mobile/src/forge/screens/match/views/VAutoYieldsAndTriggers.java new file mode 100644 index 00000000000..39e8fb76d12 --- /dev/null +++ b/forge-gui-mobile/src/forge/screens/match/views/VAutoYieldsAndTriggers.java @@ -0,0 +1,160 @@ +package forge.screens.match.views; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import forge.Forge; +import forge.player.AutoYieldStore; +import forge.screens.match.MatchController; +import forge.toolbox.FCheckBox; +import forge.toolbox.FChoiceList; +import forge.toolbox.FDialog; +import forge.toolbox.FOptionPane; +import forge.toolbox.FTextField; +import forge.util.TextBounds; + +public class VAutoYieldsAndTriggers extends FDialog { + private static final String YIELD_PREFIX = "[" + Forge.getLocalizer().getMessage("lblYield") + "] "; + private static final String ACCEPT_PREFIX = "[" + Forge.getLocalizer().getMessage("lblAlwaysYes") + "] "; + private static final String DECLINE_PREFIX = "[" + Forge.getLocalizer().getMessage("lblAlwaysNo") + "] "; + + /** Sort by the post-tag substring so same-card entries sit adjacent regardless of tag. */ + private static final Comparator ENTRY_COMPARATOR = (a, b) -> { + Collator c = Collator.getInstance(); + int byCard = c.compare(stripTag(a), stripTag(b)); + return byCard != 0 ? byCard : c.compare(a, b); + }; + + private final FTextField filterField; + private final FChoiceList lstEntries; + private final FCheckBox chkDisableYields; + private final FCheckBox chkDisableTriggers; + /** Master list, sorted, mutated by Remove. */ + private final List allEntries; + + public VAutoYieldsAndTriggers() { + super(Forge.getLocalizer().getMessage("lblAutoYieldsAndTriggers"), 2); + + allEntries = new ArrayList<>(); + for (String key : MatchController.instance.getGameController().getYieldController().getAutoYields()) { + allEntries.add(YIELD_PREFIX + key); + } + for (Map.Entry e : MatchController.instance.getGameController().getYieldController().getAutoTriggers()) { + String prefix = e.getValue() == AutoYieldStore.TriggerDecision.ACCEPT ? ACCEPT_PREFIX : DECLINE_PREFIX; + allEntries.add(prefix + e.getKey()); + } + Collections.sort(allEntries, ENTRY_COMPARATOR); + + filterField = add(new FTextField()); + filterField.setGhostText(Forge.getLocalizer().getMessage("lblSearch")); + filterField.setChangedHandler(e -> applyFilter()); + + lstEntries = add(new FChoiceList(allEntries) { + @Override + protected void onCompactModeChange() { + VAutoYieldsAndTriggers.this.revalidate(); + } + + @Override + protected boolean allowDefaultItemWrap() { + return true; + } + }); + chkDisableYields = add(new FCheckBox(Forge.getLocalizer().getMessage("lblDisableAllAutoYields"), + MatchController.instance.getGameController().getDisableAutoYields())); + chkDisableYields.setCommand(e -> MatchController.instance.getGameController().setDisableAutoYields(chkDisableYields.isSelected())); + chkDisableTriggers = add(new FCheckBox(Forge.getLocalizer().getMessage("lblDisableAllAutoTriggers"), + MatchController.instance.getGameController().getDisableAutoTriggers())); + chkDisableTriggers.setCommand(e -> MatchController.instance.getGameController().setDisableAutoTriggers(chkDisableTriggers.isSelected())); + + initButton(0, Forge.getLocalizer().getMessage("lblOK"), e -> hide()); + initButton(1, Forge.getLocalizer().getMessage("lblRemove"), e -> { + String selected = lstEntries.getSelectedItem(); + if (selected == null) return; + allEntries.remove(selected); + lstEntries.removeItem(selected); + boolean abilityScope = MatchController.instance.getGameController().getYieldController().isAbilityScope(); + if (selected.startsWith(YIELD_PREFIX)) { + String key = selected.substring(YIELD_PREFIX.length()); + MatchController.instance.getGameController().setShouldAutoYield(key, false, abilityScope); + } else if (selected.startsWith(ACCEPT_PREFIX)) { + String key = selected.substring(ACCEPT_PREFIX.length()); + MatchController.instance.getGameController().setTriggerDecision(key, AutoYieldStore.TriggerDecision.ASK, abilityScope); + } else if (selected.startsWith(DECLINE_PREFIX)) { + String key = selected.substring(DECLINE_PREFIX.length()); + MatchController.instance.getGameController().setTriggerDecision(key, AutoYieldStore.TriggerDecision.ASK, abilityScope); + } + setButtonEnabled(1, !allEntries.isEmpty()); + lstEntries.cleanUpSelections(); + VAutoYieldsAndTriggers.this.revalidate(); + }); + setButtonEnabled(1, !allEntries.isEmpty()); + } + + private void applyFilter() { + String needle = filterField.getText(); + if (needle == null || needle.isEmpty()) { + lstEntries.setListData(allEntries); + return; + } + String lower = needle.toLowerCase(); + List filtered = new ArrayList<>(); + for (String e : allEntries) { + if (e.toLowerCase().contains(lower)) filtered.add(e); + } + lstEntries.setListData(filtered); + } + + private static String stripTag(String s) { + if (s.startsWith(YIELD_PREFIX)) return s.substring(YIELD_PREFIX.length()); + if (s.startsWith(ACCEPT_PREFIX)) return s.substring(ACCEPT_PREFIX.length()); + if (s.startsWith(DECLINE_PREFIX)) return s.substring(DECLINE_PREFIX.length()); + return s; + } + + @Override + public void show() { + if (!allEntries.isEmpty()) { + super.show(); + } + else { + FOptionPane.showMessageDialog( + Forge.getLocalizer().getMessage("lblNoActiveAutoYieldOrTrigger"), + Forge.getLocalizer().getMessage("lblNoAutoYieldOrTrigger"), + FOptionPane.INFORMATION_ICON); + } + } + + @Override + protected float layoutAndGetHeight(float width, float maxHeight) { + float padding = FOptionPane.PADDING; + float x = padding; + float y = padding; + float w = width - 2 * padding; + float filterHeight = FTextField.getDefaultHeight(); + TextBounds yieldChkSize = chkDisableYields.getAutoSizeBounds(); + TextBounds trigChkSize = chkDisableTriggers.getAutoSizeBounds(); + float chkHeight = Math.max(yieldChkSize.height, trigChkSize.height); + + float listHeight = lstEntries.getListItemRenderer().getItemHeight() * lstEntries.getCount(); + float maxListHeight = maxHeight - 5 * padding - 2 * chkHeight - filterHeight; + if (listHeight > maxListHeight) { + listHeight = maxListHeight; + } + + filterField.setBounds(x, y, w, filterHeight); + y += filterHeight + padding; + lstEntries.setBounds(x, y, w, listHeight); + y += listHeight + padding; + chkDisableYields.setBounds(x, y, Math.min(yieldChkSize.width, w), chkHeight); + y += chkHeight + padding; + chkDisableTriggers.setBounds(x, y, Math.min(trigChkSize.width, w), chkHeight); + y += chkHeight + padding; + + return y; + } +} diff --git a/forge-gui-mobile/src/forge/screens/match/views/VGameMenu.java b/forge-gui-mobile/src/forge/screens/match/views/VGameMenu.java index d0fb7c4470a..7a1842b0b77 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VGameMenu.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VGameMenu.java @@ -33,11 +33,11 @@ public void handleEvent(FEvent e) { GameStateDeserializer.loadGameState(MatchUtil.getGame(), ForgeConstants.USER_GAMES_DIR + "GameSave.txt"); } }));*/ - addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblAutoYields"), Forge.hdbuttons ? FSkinImage.HDYIELD : FSkinImage.WARNING, new FEventHandler() { + addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblAutoYieldsAndTriggers"), Forge.hdbuttons ? FSkinImage.HDYIELD : FSkinImage.WARNING, new FEventHandler() { @Override public void handleEvent(FEvent e) { final boolean autoYieldsDisabled = MatchController.instance.getGameController().getDisableAutoYields(); - final VAutoYields autoYields = new VAutoYields() { + final VAutoYieldsAndTriggers dialog = new VAutoYieldsAndTriggers() { @Override public void setVisible(boolean b0) { super.setVisible(b0); @@ -47,8 +47,7 @@ public void setVisible(boolean b0) { if (MatchController.instance.getGameView().peekStack() != null) { final String key = MatchController.instance.getGameView().peekStack().getKey(); final boolean autoYield = MatchController.instance.getGameController().shouldAutoYield(key); - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); + boolean abilityScope = MatchController.instance.getGameController().getYieldController().isAbilityScope(); MatchController.instance.getGameController().setShouldAutoYield(key, !autoYield, abilityScope); if (!autoYield && MatchController.instance.getGameController().shouldAutoYield(key)) { //auto-pass priority if ability is on top of stack @@ -59,7 +58,7 @@ public void setVisible(boolean b0) { } } }; - autoYields.show(); + dialog.show(); } })); if (!Forge.isMobileAdventureMode) { diff --git a/forge-gui-mobile/src/forge/screens/match/views/VStack.java b/forge-gui-mobile/src/forge/screens/match/views/VStack.java index bfc37d27b74..67788b50d75 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VStack.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VStack.java @@ -32,6 +32,7 @@ import forge.menu.FMenuItem; import forge.menu.FMenuTab; import forge.menu.FPopupMenu; +import forge.player.AutoYieldStore.TriggerDecision; import forge.player.PlayerZoneUpdates; import forge.screens.match.MatchController; import forge.screens.match.MatchScreen; @@ -294,8 +295,7 @@ protected void buildMenu() { final boolean autoYield = controller.shouldAutoYield(key); addItem(new FCheckBoxMenuItem(Forge.getLocalizer().getMessage("cbpAutoYieldMode"), autoYield, e -> { - boolean abilityScope = !forge.localinstance.properties.ForgeConstants.AUTO_YIELD_PER_CARD.equals( - forge.model.FModel.getPreferences().getPref(forge.localinstance.properties.ForgePreferences.FPref.UI_AUTO_YIELD_MODE)); + boolean abilityScope = controller.getYieldController().isAbilityScope(); controller.setShouldAutoYield(key, !autoYield, abilityScope); if (!autoYield && stackInstance.equals(gameView.peekStack())) { //auto-pass priority if ability is on top of stack @@ -303,27 +303,19 @@ protected void buildMenu() { } })); if (stackInstance.isOptionalTrigger() && stackInstance.getActivatingPlayer().equals(player)) { - final int triggerID = stackInstance.getSourceTrigger(); - addItem(new FCheckBoxMenuItem(Forge.getLocalizer().getMessage("lblAlwaysYes"), - controller.shouldAlwaysAcceptTrigger(triggerID), - e -> { - if (controller.shouldAlwaysAcceptTrigger(triggerID)) { - controller.setShouldAlwaysAskTrigger(triggerID); - } - else { - controller.setShouldAlwaysAcceptTrigger(triggerID); - } - })); - addItem(new FCheckBoxMenuItem(Forge.getLocalizer().getMessage("lblAlwaysNo"), - controller.shouldAlwaysDeclineTrigger(triggerID), - e -> { - if (controller.shouldAlwaysDeclineTrigger(triggerID)) { - controller.setShouldAlwaysAskTrigger(triggerID); - } - else { - controller.setShouldAlwaysDeclineTrigger(triggerID); - } - })); + if (!key.isEmpty()) { + final boolean abilityScope = controller.getYieldController().isAbilityScope(); + addItem(new FCheckBoxMenuItem(Forge.getLocalizer().getMessage("lblAlwaysYes"), + controller.getTriggerDecision(key) == TriggerDecision.ACCEPT, + e -> controller.setTriggerDecision(key, + controller.getTriggerDecision(key) == TriggerDecision.ACCEPT ? TriggerDecision.ASK : TriggerDecision.ACCEPT, + abilityScope))); + addItem(new FCheckBoxMenuItem(Forge.getLocalizer().getMessage("lblAlwaysNo"), + controller.getTriggerDecision(key) == TriggerDecision.DECLINE, + e -> controller.setTriggerDecision(key, + controller.getTriggerDecision(key) == TriggerDecision.DECLINE ? TriggerDecision.ASK : TriggerDecision.DECLINE, + abilityScope))); + } } } addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblYieldToEntireStack"), diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index e7a660a59fd..25cba29eb0b 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -259,14 +259,14 @@ public void valueChanged(String newValue) { ForgeConstants.GRAVEYARD_ORDERING_NEVER, ForgeConstants.GRAVEYARD_ORDERING_OWN_CARDS, ForgeConstants.GRAVEYARD_ORDERING_ALWAYS }), 1); - lstSettings.addItem(new CustomSelectSetting(FPref.UI_AUTO_YIELD_MODE, - Forge.getLocalizer().getMessage("lblAutoYields"), - Forge.getLocalizer().getMessage("nlpAutoYieldMode"), + lstSettings.addItem(new CustomSelectSetting(FPref.UI_AUTO_DECISION_MODE, + Forge.getLocalizer().getMessage("lblAutoYieldsAndTriggers"), + Forge.getLocalizer().getMessage("nlpAutoDecisionMode"), new String[] { - ForgeConstants.AUTO_YIELD_PER_CARD, - ForgeConstants.AUTO_YIELD_PER_ABILITY, - ForgeConstants.AUTO_YIELD_PER_ABILITY_SESSION, - ForgeConstants.AUTO_YIELD_PER_ABILITY_INSTALL, + ForgeConstants.AUTO_DECISION_PER_CARD, + ForgeConstants.AUTO_DECISION_PER_ABILITY, + ForgeConstants.AUTO_DECISION_PER_ABILITY_SESSION, + ForgeConstants.AUTO_DECISION_PER_ABILITY_INSTALL, }), 1); lstSettings.addItem(new BooleanSetting(FPref.UI_ALLOW_ESC_TO_END_TURN, Forge.getLocalizer().getMessage("cbEscapeEndsTurn"), diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index d0fd9a98970..2c00dd76f8c 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -204,7 +204,6 @@ nlShowStormCount=Wenn aktiviert, wird ein Sturmzähler angezeigt. nlRemindOnPriority=Wenn aktiviert, dann blinkt der Auswahlbereich des Spielers bei Erhalt der Priorität. nlPreselectPrevAbOrder=Wenn aktiviert, wird die letzte genutzte Reihenfolge von Fähigkeiten im Auswahlfenster vorbelegt. nlpGraveyardOrdering=Entscheidet, wann auf die Reihenfolge, in welcher Karten auf den Friedhof wandern, geachtet wird. (Niemals, immer oder nur wenn bestimmte Karten es nötig machen.) -nlpAutoYieldMode=Definiert die Ebene der automatischen Bestätigung (pro Fähigkeit oder pro einzelner Karte). RandomDeckGeneration=Zufällige Deck-Erstellung nlRemoveSmall=Verhindert 1/1- und 0/X-Kreaturen in erzeugten Decks nlSingletons=Verhindert Duplikate von Nicht-Landkarten in erzeugten Decks @@ -400,7 +399,6 @@ lblTargetingArcs=Zielpfeile lblOff=Aus lblCardMouseOver=Bei Zeiger über Karte lblAlwaysOn=Immer an -lblAutoYields=Automatische Bestätigung lblDeckList=Deckliste lblClose=Schließen lblExitForge=Forge verlassen @@ -2279,11 +2277,8 @@ lblClearSelection=Lösche Auswahl lblCardEditionTypeList=Kartenausgaben (pro Typ) #CMatchUI.java lblAbilities=Fähigkeiten -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=Deaktiviere jedes Auto-Bestätigen -lblRemoveYield=Entferne Bestätigen -lblNoActiveAutoYield=Derzeit kein aktives Auto-Bestätigen. -lblNoAutoYield=Kein Autobestätigen #GameEntityPicker.java lblChoices=Auswahl #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index bb15102094c..bb9cc063c3d 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -152,6 +152,7 @@ cbpMusicSets=Music Set cbpStackAdditions=Stack effect notifications cbpDisplayCurrentCardColors=Show Detailed Card Color cbpAutoYieldMode=Auto-Yield +cbpAutoDecisionMode=Auto Yield/Trigger Mode cbpCounterDisplayType=Counter Display Type cbpCounterDisplayLocation=Counter Display Location cbpGraveyardOrdering=Allow Ordering Cards Put in Graveyard @@ -220,7 +221,7 @@ nlShowStormCount=When enabled, displays the current storm count in the prompt pa nlRemindOnPriority=When enabled, flashes the player choice area upon receiving priority. nlPreselectPrevAbOrder=When enabled, preselects the last defined simultaneous ability order in the ordering dialog. nlpGraveyardOrdering=Determines when to let the player choose the order of cards simultaneously put in graveyard (never, always, or only when playing with cards for which it matters, for example, Volrath''s Shapeshifter). -nlpAutoYieldMode=Auto-yield scope: per card (one game), or per ability (this match, this session, or all sessions). +nlpAutoDecisionMode=How long auto-yield and Always Yes/No trigger decisions persist: per card (one game), or per ability (this match, this session, or all sessions). RandomDeckGeneration=Random Deck Generation nlRemoveSmall=Disables 1/1 and 0/X creatures in generated decks nlSingletons=Disables non-land duplicates in generated decks @@ -453,7 +454,7 @@ lblTargetingArcs=Targeting Arcs lblOff=Off lblCardMouseOver=Card Mouseover lblAlwaysOn=Always On -lblAutoYields=Auto-Yields +lblAutoYieldsAndTriggers=Auto Yields and Triggers lblLogPanel=Log Detail Level lblLogShowCardImages=Show Card Images lblLogZoneChange={0} was put into {1} from {2}. @@ -2436,11 +2437,12 @@ lblClearSelection=Clear Selection lblCardEditionTypeList=Card Editions (per Type) #CMatchUI.java lblAbilities=Abilities -#VAutoYields.java +#VAutoYieldsAndTriggers.java +lblYield=Yield lblDisableAllAutoYields=Disable All Auto Yields -lblRemoveYield=Remove Yield -lblNoActiveAutoYield=There are no active auto-yields. -lblNoAutoYield=No Auto-Yields +lblDisableAllAutoTriggers=Disable All Auto Triggers +lblNoActiveAutoYieldOrTrigger=There are no active auto-yields or trigger decisions. +lblNoAutoYieldOrTrigger=No Auto-Yields or Triggers #GameEntityPicker.java lblChoices=Choices #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 874e1fc2d1d..95038ff3577 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -180,7 +180,6 @@ nlShowStormCount=Cuando está habilitado, muestra el recuento de tormentas actua nlRemindOnPriority=Cuando está habilitado, parpadea el área de elección del jugador al recibir prioridad. nlPreselectPrevAbOrder=Cuando está habilitado, preselecciona el último orden de habilidad simultáneo definido en el cuadro de diálogo de ordenación. nlpGraveyardOrdering=Determina cuándo dejar que el jugador elija el orden de las cartas colocadas simultáneamente en el cementerio (nunca, siempre, o solo cuando juega con las cartas que le interesan, por ejemplo, Volrath''s Shapeshifter) -nlpAutoYieldMode=Define el nivel de granularidad de la opción auto-ceder (por habilidad única o por carta única). RandomDeckGeneration=Generación aleatoria de mazo nlRemoveSmall=Deshabilita las criaturas 1/1 y 0/X en los mazos generados. nlSingletons=Deshabilita duplicados de cartas que no sean tierras en los mazos generados @@ -377,7 +376,6 @@ lblTargetingArcs=Flechas objetivo lblOff=Desactivado lblCardMouseOver=Cuando sea señalada por el ratón lblAlwaysOn=Siempre activado -lblAutoYields=Auto-ceder lblDeckList=Lista del mazo lblClose=Cerrar lblExitForge=Salir de Forge @@ -2264,11 +2262,8 @@ lblClearSelection=Borrar selección lblCardEditionTypeList=Ediciones (por tipo) #CMatchUI.java lblAbilities=Habilidades -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=Deshabilitar todas las auto-cesiones -lblRemoveYield=Eliminar cesión -lblNoActiveAutoYield=No hay auto-cesiones activas. -lblNoAutoYield=No Auto-Cesiones #GameEntityPicker.java lblChoices=Opciones #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 47ca536dd7c..608628d2410 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -178,7 +178,6 @@ nlShowStormCount=Lorsqu'il est activé, affiche le nombre actuel de tempêtes da nlRemindOnPriority=Lorsqu'il est activé, clignote la zone de choix du joueur lors de la réception de la priorité. nlPreselectPrevAbOrder=Lorsqu'il est activé, présélectionne le dernier ordre de capacité simultanée défini dans la boîte de dialogue de commande. nlpGraveyardOrdering=Détermine quand laisser le joueur choisir l'ordre des cartes mises simultanément dans le cimetière (jamais, toujours, ou seulement en jouant avec des cartes pour lesquelles cela compte, par exemple, le Changeforme de Volrath). -nlpAutoYieldMode=Définit le niveau de granularité des rendements automatiques (par capacité unique ou par carte unique). RandomDeckGeneration=Génération de deck aléatoire nlRemoveSmall=Désactive les créatures 1/1 et 0/X dans les decks générés nlSingletons=Désactive les doublons non terrestres dans les decks générés @@ -375,7 +374,6 @@ lblTargetingArcs=Arcs de ciblage lblOff=Off lblCardMouseOver=Survol de carte lblAlwaysOn=Toujours activé -lblAutoYields=Rendements automatiques lblDeckList=Liste des decks lblClose=Fermer lblExitForge=Quitter Forge @@ -2257,11 +2255,8 @@ lblClearSelection=Effacer la sélection lblCardEditionTypeList=Éditions de cartes (par type) #CMatchUI.java lblAbilities=Capacités -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=Désactiver tous les rendements automatiques -lblRemoveYield=Supprimer le rendement -lblNoActiveAutoYield=Il n'y a pas de rendements automatiques actifs. -lblNoAutoYield=Aucun rendement automatique #GameEntityPicker.java lblChoices=Choix #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 005da955264..59b8319b121 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -177,7 +177,6 @@ nlShowStormCount=Se abilitato, visualizza il numero di magie lanciate nel riquad nlRemindOnPriority=Se abilitato, lampeggia l'area di scelta del giocatore alla ricezione della priorità. nlPreselectPrevAbOrder=Se abilitato, preseleziona l'ultimo ordine di abilità simultaneo definito nella finestra di dialogo degli ordini. nlpGraveyardOrdering=Determina quando lasciare che il giocatore scelga l'ordine delle carte messe simultaneamente nel cimitero (mai, sempre o solo quando gioca con le carte per le quali è importante, ad esempio, Volpath's Shapeshifter). -nlpAutoYieldMode=Definisce il livello di granularità dei consensi automatici (per abilità unica o per carta unica). RandomDeckGeneration=Generazione casuale del mazzo nlRemoveSmall=Disabilita le creature 1/1 e 0 / X nei mazzi generati nlSingletons=Disabilita i duplicati non-terra nei deck generati @@ -374,7 +373,6 @@ lblTargetingArcs=Indicatori dei bersagli lblOff=Spento lblCardMouseOver=Mouse sopra alla carta lblAlwaysOn=Sempre acceso -lblAutoYields=Consensi automatici lblDeckList=Lista del mazzo lblClose=Chiudi lblExitForge=Esci da Forge @@ -2254,11 +2252,8 @@ lblClearSelection=Cancella le selezioni lblCardEditionTypeList=Espansioni (per Tipo) #CMatchUI.java lblAbilities=Abilità -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=Disabilita tutti i consensi automatici -lblRemoveYield=Rimuovi consenso -lblNoActiveAutoYield=Non ci sono consensi automatici attivi. -lblNoAutoYield=Nessun consenso automatico #GameEntityPicker.java lblChoices=Scelte #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index ffeded71ab6..e495c3e99f6 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -178,7 +178,6 @@ nlShowStormCount=有効にすると、現在のストームカウントがプロ nlRemindOnPriority=有効にすると、優先権が渡るプレーヤーの選択エリアをフラッシュします。 nlPreselectPrevAbOrder=有効にすると、順序付けダイアログで最後に選択した同時能力の順序が事前に選択されます。 nlpGraveyardOrdering=プレイヤーが同時に墓地に置かれるカードの順序を選択できるようにします(Never[しない]、With Relevant Cards[関連カードを使う時だけ、たとえば、ヴォルラスの多相の戦士で遊ぶとき]、Always[常時]) -nlpAutoYieldMode=優先権の自動放棄を有効する時の範囲を選択します(Per Ability[能力ごと]、Per Card[カードごと])。 RandomDeckGeneration=ランダムデッキ生成 nlRemoveSmall=生成されたデッキは 1/1 および 0/X クリーチャーを含まない nlSingletons=生成されたデッキは基本土地以外に 1種類につき 1枚まで @@ -375,7 +374,6 @@ lblTargetingArcs=ターゲットアーク lblOff=オフ lblCardMouseOver=カードマウスオーバー lblAlwaysOn=常にオン -lblAutoYields=優先権の自動放棄 lblDeckList=デッキリスト lblClose=閉じる lblExitForge=Forge を終了します @@ -2253,11 +2251,8 @@ lblClearSelection=全てクリア lblCardEditionTypeList=カードエディション(タイプごと) #CMatchUI.java lblAbilities=能力 -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=自動優先権放棄を無効 -lblRemoveYield=優先権放棄を削除 -lblNoActiveAutoYield=有効の優先権放棄がありません。 -lblNoAutoYield=優先権放棄がありません #GameEntityPicker.java lblChoices=選択対象 #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/ko-KR.properties b/forge-gui/res/languages/ko-KR.properties index 144c24f2661..1e3e7b83347 100644 --- a/forge-gui/res/languages/ko-KR.properties +++ b/forge-gui/res/languages/ko-KR.properties @@ -209,7 +209,6 @@ nlShowStormCount=활성화하면, 현재 스톰 카운트가 프롬프트 패널 nlRemindOnPriority=활성화하면, 우선권이 넘어가는 플레이어의 선택 영역을 깜빡입니다. nlPreselectPrevAbOrder=활성화하면, 정렬 대화 상자에서 마지막으로 선택한 동시 능력의 순서가 미리 선택됩니다. nlpGraveyardOrdering=플레이어가 동시에 무덤에 놓이는 카드의 순서를 선택할 수 있게 합니다 (Never[안 함], With Relevant Cards[관련 카드가 있을 때만, 예: 볼라스의 다면 전사 사용 시], Always[항상]). -nlpAutoYieldMode=우선권 자동 포기를 활성화할 때의 범위를 선택합니다 (Per Ability[능력별], Per Card[카드별]). RandomDeckGeneration=랜덤 덱 생성 nlRemoveSmall=생성된 덱은 1/1 및 0/X 크리처를 포함하지 않습니다. nlSingletons=생성된 덱은 기본 토지를 제외하고 종류당 최대 1장까지. @@ -409,7 +408,6 @@ lblTargetingArcs=타깃 아크 lblOff=오프 lblCardMouseOver=카드 마우스 오버 lblAlwaysOn=항상 켜기 -lblAutoYields=우선권 자동 포기 lblDeckList=덱 리스트 lblClose=닫기 lblExitForge=Forge 종료 @@ -2343,11 +2341,8 @@ lblClearSelection=전체 초기화 lblCardEditionTypeList=카드 에디션(타입별) #CMatchUI.java lblAbilities=능력 -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=자동 양보 비활성화 -lblRemoveYield=양보 삭제 -lblNoActiveAutoYield=활성된 양보가 없습니다 -lblNoAutoYield=양보가 없습니다 #GameEntityPicker.java lblChoices=선택 대상 #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 7f600ce6343..b3acf235a82 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -179,7 +179,6 @@ nlShowStormCount=Quando ativado, exibe os marcadores de rajadas atuais no painel nlRemindOnPriority=Quando ativado, pisca a área de escolha do jogador ao receber prioridade. nlPreselectPrevAbOrder=Quando ativado, pré-seleciona a última ordem de habilidade simultânea na caixa de diálogo ordenação. nlpGraveyardOrdering=Determina quando deixar o jogador escolher a ordem das cartas colocadas simultaneamente no cemitério (nunca, sempre, ou apenas quando conjurando cartas que isto importa, por exemplo, Volrath''s Shapeshifter). -nlpAutoYieldMode=Define o nível de granularidade quando resolve automático (por habilidade ou carta). RandomDeckGeneration=Geração Aleatória de Deck nlRemoveSmall=Desativa criaturas 1/1 e 0/X em decks gerados nlSingletons=Desativar cópias de não terrenos em decks gerados @@ -387,7 +386,6 @@ lblTargetingArcs=Flechas de Alvo lblOff=Desligado lblCardMouseOver=Quando apontado pelo mouse lblAlwaysOn=Sempre Ligado -lblAutoYields=Resolver automático lblDeckList=Lista de Deck lblClose=Fechar lblExitForge=Sair da Forge @@ -2312,11 +2310,8 @@ lblClearSelection=Limpar Seleção lblCardEditionTypeList=Edição da Carta (por Tipo) #CMatchUI.java lblAbilities=Habilidades -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=Desativar Todas as Resoluções Automáticas -lblRemoveYield=Remover Resolução -lblNoActiveAutoYield=Não existem resolução automática ativa. -lblNoAutoYield=Sem Resolver Automático #GameEntityPicker.java lblChoices=Opções #ConquestRewardDialog.java diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index cacf69eb512..c5c63cd75e5 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -180,7 +180,6 @@ nlShowStormCount=启用后,提示窗格将会显示当前的风暴计数 nlRemindOnPriority=启用后,获得优先权时玩家区域将闪烁。 nlPreselectPrevAbOrder=启用后,将预先将异能排序。 nlpGraveyardOrdering=确定何时让玩家确定同时进入坟场的牌的顺序(最好只在关键操作时启用此选项,比如:瓦拉司的变形兽) -nlpAutoYieldMode=定义自动让过的详细程度(每个异能或者每张卡)。 RandomDeckGeneration=生成随机套牌 nlRemoveSmall=在生成的套牌中禁用1/1和0/X生物 nlSingletons=禁止在生成的套牌中非地牌重复出现 @@ -377,7 +376,6 @@ lblTargetingArcs=瞄准弧 lblOff=关闭 lblCardMouseOver=卡牌悬停 lblAlwaysOn=总是打开 -lblAutoYields=自动让过 lblDeckList=套牌列表 lblClose=关闭 lblExitForge=退出Forge @@ -2260,11 +2258,8 @@ lblClearSelection=清空选择 lblCardEditionTypeList=牌张系列(每种类型) #CMatchUI.java lblAbilities=异能 -#VAutoYields.java +#VAutoYieldsAndTriggers.java lblDisableAllAutoYields=禁用所有自动让过 -lblRemoveYield=移除让过 -lblNoActiveAutoYield=没有有效的自动让过 -lblNoAutoYield=没有自动让过 #GameEntityPicker.java lblChoices=可选项 #ConquestRewardDialog.java diff --git a/forge-gui/src/main/java/forge/gamemodes/match/YieldController.java b/forge-gui/src/main/java/forge/gamemodes/match/YieldController.java index 9cc26533bb8..4ae104fd261 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/YieldController.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/YieldController.java @@ -8,7 +8,7 @@ import forge.model.FModel; import forge.player.AutoYieldStore; import forge.player.LobbyPlayerHuman; -import forge.player.PersistentYieldStore; +import forge.player.PersistentAutoDecisionStore; import forge.player.PlayerControllerHuman; import java.util.EnumSet; @@ -139,7 +139,7 @@ public boolean shouldAutoYield(String key) { || store.shouldYield(AutoYieldStore.Tier.GAME, AutoYieldStore.abilitySuffix(key)); } if (activeModeIsInstall()) { - return PersistentYieldStore.get().contains(AutoYieldStore.abilitySuffix(key)); + return PersistentAutoDecisionStore.get().contains(AutoYieldStore.abilitySuffix(key)); } AutoYieldStore.Tier tier = activeTier(); boolean abilityScope = tier != AutoYieldStore.Tier.GAME; @@ -147,11 +147,16 @@ public boolean shouldAutoYield(String key) { return store.shouldYield(tier, storageKey); } + /** True when the active auto-decision scope is per-ability (any tier above per-card/per-game). */ + public boolean isAbilityScope() { + return !ForgeConstants.AUTO_DECISION_PER_CARD.equals(FModel.getPreferences().getPref(FPref.UI_AUTO_DECISION_MODE)); + } + /** Tier-aware user-initiated set. Returns the storage key (stripped if ability-scope) for wire propagation. */ public String setShouldAutoYield(String key, boolean autoYield, boolean abilityScope) { String storageKey = abilityScope ? AutoYieldStore.abilitySuffix(key) : key; if (activeModeIsInstall()) { - PersistentYieldStore.get().setYield(storageKey, autoYield); + PersistentAutoDecisionStore.get().setYield(storageKey, autoYield); } else { activeStore().setYield(activeTier(), storageKey, autoYield); } @@ -175,13 +180,13 @@ private boolean tierAware() { } private static boolean activeModeIsInstall() { - return ForgeConstants.AUTO_YIELD_PER_ABILITY_INSTALL.equals(FModel.getPreferences().getPref(FPref.UI_AUTO_YIELD_MODE)); + return ForgeConstants.AUTO_DECISION_PER_ABILITY_INSTALL.equals(FModel.getPreferences().getPref(FPref.UI_AUTO_DECISION_MODE)); } private static AutoYieldStore.Tier activeTier() { - String mode = FModel.getPreferences().getPref(FPref.UI_AUTO_YIELD_MODE); - if (ForgeConstants.AUTO_YIELD_PER_CARD.equals(mode)) return AutoYieldStore.Tier.GAME; - if (ForgeConstants.AUTO_YIELD_PER_ABILITY_SESSION.equals(mode)) return AutoYieldStore.Tier.SESSION; + String mode = FModel.getPreferences().getPref(FPref.UI_AUTO_DECISION_MODE); + if (ForgeConstants.AUTO_DECISION_PER_CARD.equals(mode)) return AutoYieldStore.Tier.GAME; + if (ForgeConstants.AUTO_DECISION_PER_ABILITY_SESSION.equals(mode)) return AutoYieldStore.Tier.SESSION; return AutoYieldStore.Tier.MATCH; } @@ -193,7 +198,7 @@ public void applyAutoYieldFromWire(String storageKey, boolean active) { public Iterable getAutoYields() { AutoYieldStore store = activeStore(); if (!tierAware()) return store.getYields(AutoYieldStore.Tier.GAME); - if (activeModeIsInstall()) return PersistentYieldStore.get().getYields(); + if (activeModeIsInstall()) return PersistentAutoDecisionStore.get().getYields(); return store.getYields(activeTier()); } @@ -213,39 +218,72 @@ public void setDisableAutoYields(boolean disable) { activeStore().setDisabled(disable); } - public boolean shouldAlwaysAcceptTrigger(int trigger) { - return activeStore().getTriggerDecision(trigger) == AutoYieldStore.TriggerDecision.ACCEPT; - } - public boolean shouldAlwaysDeclineTrigger(int trigger) { - return activeStore().getTriggerDecision(trigger) == AutoYieldStore.TriggerDecision.DECLINE; + public AutoYieldStore.TriggerDecision getTriggerDecision(String key) { + AutoYieldStore store = activeStore(); + if (key == null || key.isEmpty() || store.isTriggerDecisionsDisabled()) return AutoYieldStore.TriggerDecision.ASK; + if (!tierAware()) { + // Cache mode: keys stored at storageKey shape (full or stripped). + AutoYieldStore.TriggerDecision d = store.getTriggerDecision(AutoYieldStore.Tier.GAME, key); + if (d != AutoYieldStore.TriggerDecision.ASK) return d; + return store.getTriggerDecision(AutoYieldStore.Tier.GAME, AutoYieldStore.abilitySuffix(key)); + } + if (activeModeIsInstall()) { + return PersistentAutoDecisionStore.get().getTriggerDecision(AutoYieldStore.abilitySuffix(key)); + } + AutoYieldStore.Tier tier = activeTier(); + boolean abilityScope = tier != AutoYieldStore.Tier.GAME; + String storageKey = abilityScope ? AutoYieldStore.abilitySuffix(key) : key; + return store.getTriggerDecision(tier, storageKey); } - public void setAlwaysAcceptTrigger(int trigger) { - activeStore().setTriggerDecision(trigger, AutoYieldStore.TriggerDecision.ACCEPT); - } - public void setAlwaysDeclineTrigger(int trigger) { - activeStore().setTriggerDecision(trigger, AutoYieldStore.TriggerDecision.DECLINE); + /** Tier-aware user-initiated set. Returns the storage key (stripped if ability-scope) for wire propagation. */ + public String setTriggerDecision(String key, AutoYieldStore.TriggerDecision decision, boolean abilityScope) { + String storageKey = abilityScope ? AutoYieldStore.abilitySuffix(key) : key; + if (activeModeIsInstall()) { + PersistentAutoDecisionStore.get().setTriggerDecision(storageKey, decision); + } else { + activeStore().setTriggerDecision(activeTier(), storageKey, decision); + } + return storageKey; } - public void setAlwaysAskTrigger(int trigger) { - activeStore().setTriggerDecision(trigger, AutoYieldStore.TriggerDecision.ASK); + + /** Cache-mode write of a wire-received trigger decision. Storage key is already at the right shape. */ + public void applyTriggerDecisionFromWire(String storageKey, AutoYieldStore.TriggerDecision decision) { + activeStore().setTriggerDecision(AutoYieldStore.Tier.GAME, storageKey, decision); } - public void setTriggerDecision(int trigger, AutoYieldStore.TriggerDecision decision) { - activeStore().setTriggerDecision(trigger, decision); + public Iterable> getAutoTriggers() { + if (!tierAware()) return activeStore().getAutoTriggers(AutoYieldStore.Tier.GAME); + if (activeModeIsInstall()) return PersistentAutoDecisionStore.get().getAutoTriggers(); + return activeStore().getAutoTriggers(activeTier()); } + public boolean getDisableAutoTriggers() { return activeStore().isTriggerDecisionsDisabled(); } + public void setDisableAutoTriggers(boolean disable) { activeStore().setTriggerDecisionsDisabled(disable); } + /** Build the seed payload from this controller's authoritative store. */ public YieldStateSnapshot buildClientSnapshot(Map> skipPhases) { + boolean abilityScope = activeModeIsInstall() || activeTier() != AutoYieldStore.Tier.GAME; + Set cardYields = new HashSet<>(); Set abilityYields = new HashSet<>(); - boolean abilityScope = activeModeIsInstall() || activeTier() != AutoYieldStore.Tier.GAME; for (String key : getAutoYields()) { if (abilityScope) abilityYields.add(key); else cardYields.add(key); } - // Trigger decisions are per-game; deltas flow during play. - Map triggers = new HashMap<>(); - return new YieldStateSnapshot(cardYields, abilityYields, triggers, getDisableAutoYields(), skipPhases); + + Map cardTriggers = new HashMap<>(); + Map abilityTriggers = new HashMap<>(); + for (Map.Entry e : getAutoTriggers()) { + if (abilityScope) abilityTriggers.put(e.getKey(), e.getValue()); + else cardTriggers.put(e.getKey(), e.getValue()); + } + + return new YieldStateSnapshot( + cardYields, abilityYields, + cardTriggers, abilityTriggers, + getDisableAutoYields(), getDisableAutoTriggers(), + skipPhases); } /** Atomic seed of client-persistent state at game start or reconnection. Cache mode only. */ @@ -253,10 +291,14 @@ public void applyClientSeed(YieldStateSnapshot snap) { localStore.clear(); for (String k : snap.cardYields()) localStore.setYield(AutoYieldStore.Tier.GAME, k, true); for (String k : snap.abilityYields()) localStore.setYield(AutoYieldStore.Tier.GAME, k, true); - for (Map.Entry e : snap.triggerDecisions().entrySet()) { - localStore.setTriggerDecision(e.getKey(), e.getValue()); + for (Map.Entry e : snap.cardTriggerDecisions().entrySet()) { + localStore.setTriggerDecision(AutoYieldStore.Tier.GAME, e.getKey(), e.getValue()); + } + for (Map.Entry e : snap.abilityTriggerDecisions().entrySet()) { + localStore.setTriggerDecision(AutoYieldStore.Tier.GAME, e.getKey(), e.getValue()); } localStore.setDisabled(snap.autoYieldsDisabled()); + localStore.setTriggerDecisionsDisabled(snap.autoTriggersDisabled()); skipPhases.clear(); skipPhases.putAll(snap.skipPhases()); } diff --git a/forge-gui/src/main/java/forge/gamemodes/match/YieldStateSnapshot.java b/forge-gui/src/main/java/forge/gamemodes/match/YieldStateSnapshot.java index 0e4ef02e470..7d44f545ed7 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/YieldStateSnapshot.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/YieldStateSnapshot.java @@ -10,14 +10,19 @@ import java.util.Set; /** - * Atomic snapshot of a client's persistent yield state, transmitted at - * game start (and on reconnection) so the host's PCH proxy is fully - * seeded in one wire message instead of N. + * Atomic snapshot of a client's persistent yield + trigger state, transmitted at + * game start (and on reconnection) so the host's PCH proxy is fully seeded in + * one wire message instead of N. + * + * Trigger decisions split by scope (parallel to yields) so the host's remote + * cache can match incoming game-time keys against either bucket. */ public record YieldStateSnapshot( Set cardYields, Set abilityYields, - Map triggerDecisions, + Map cardTriggerDecisions, + Map abilityTriggerDecisions, boolean autoYieldsDisabled, + boolean autoTriggersDisabled, Map> skipPhases ) implements Serializable {} diff --git a/forge-gui/src/main/java/forge/gamemodes/match/YieldUpdate.java b/forge-gui/src/main/java/forge/gamemodes/match/YieldUpdate.java index 09096e6cdcc..a1a379fce33 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/YieldUpdate.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/YieldUpdate.java @@ -7,7 +7,7 @@ import java.io.Serializable; /** - * Unified envelope for all yield-related sync between client and host. + * Unified envelope for all yield- and trigger-related sync between client and host. * * Receiver dispatches via exhaustive switch in PlayerControllerHuman * (host-side) and NetworkGuiGame (client-side). @@ -16,8 +16,10 @@ public sealed interface YieldUpdate extends Serializable permits YieldUpdate.SetMarker, YieldUpdate.ClearMarker, YieldUpdate.StackYield, - YieldUpdate.TriggerDecision, YieldUpdate.CardAutoYield, + YieldUpdate.TriggerDecision, + YieldUpdate.SetDisableYields, + YieldUpdate.SetDisableTriggers, YieldUpdate.SkipPhase, YieldUpdate.SeedFromClient { @@ -28,10 +30,17 @@ record ClearMarker(PlayerView player) implements YieldUpdate {} record StackYield(PlayerView player, boolean active) implements YieldUpdate {} - record TriggerDecision(int trigId, AutoYieldStore.TriggerDecision decision) implements YieldUpdate {} - record CardAutoYield(String cardKey, boolean active, boolean abilityScope) implements YieldUpdate {} + /** Param order mirrors {@link CardAutoYield} (key, value, scope). */ + record TriggerDecision(String storageKey, AutoYieldStore.TriggerDecision decision, boolean abilityScope) implements YieldUpdate {} + + /** Runtime toggle of the global auto-yield disable flag — host applies to its remote-cache, client to its local controller. */ + record SetDisableYields(boolean disabled) implements YieldUpdate {} + + /** Runtime toggle of the global auto-trigger disable flag — host applies to its remote-cache, client to its local controller. */ + record SetDisableTriggers(boolean disabled) implements YieldUpdate {} + record SkipPhase(PlayerView turnPlayer, PhaseType phase, boolean skip) implements YieldUpdate {} record SeedFromClient(YieldStateSnapshot snapshot) implements YieldUpdate {} diff --git a/forge-gui/src/main/java/forge/gamemodes/net/client/NetGameController.java b/forge-gui/src/main/java/forge/gamemodes/net/client/NetGameController.java index 62978501ed5..79e24ceab62 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/client/NetGameController.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/client/NetGameController.java @@ -145,7 +145,6 @@ public void requestResync() { public boolean shouldAutoYield(final String key) { return yieldController.shouldAutoYield(key); } - @Override public void setShouldAutoYield(final String key, final boolean autoYield, final boolean isAbilityScope) { String storageKey = yieldController.setShouldAutoYield(key, autoYield, isAbilityScope); @@ -153,33 +152,33 @@ public void setShouldAutoYield(final String key, final boolean autoYield, final } @Override - public boolean getDisableAutoYields() { return yieldController.getDisableAutoYields(); } + public boolean getDisableAutoYields() { + return yieldController.getDisableAutoYields(); + } @Override - public void setDisableAutoYields(final boolean disable) { yieldController.setDisableAutoYields(disable); } + public void setDisableAutoYields(final boolean disable) { + yieldController.setDisableAutoYields(disable); + send(ProtocolMethod.sendYieldUpdate, new YieldUpdate.SetDisableYields(disable)); + } @Override - public boolean shouldAlwaysAcceptTrigger(final int trigger) { - return yieldController.shouldAlwaysAcceptTrigger(trigger); + public AutoYieldStore.TriggerDecision getTriggerDecision(final String key) { + return yieldController.getTriggerDecision(key); } @Override - public boolean shouldAlwaysDeclineTrigger(final int trigger) { - return yieldController.shouldAlwaysDeclineTrigger(trigger); + public void setTriggerDecision(final String key, final AutoYieldStore.TriggerDecision decision, final boolean isAbilityScope) { + String storageKey = yieldController.setTriggerDecision(key, decision, isAbilityScope); + send(ProtocolMethod.sendYieldUpdate, new YieldUpdate.TriggerDecision(storageKey, decision, isAbilityScope)); } @Override - public void setShouldAlwaysAcceptTrigger(final int trigger) { - yieldController.setAlwaysAcceptTrigger(trigger); - send(ProtocolMethod.sendYieldUpdate, new YieldUpdate.TriggerDecision(trigger, AutoYieldStore.TriggerDecision.ACCEPT)); - } - @Override - public void setShouldAlwaysDeclineTrigger(final int trigger) { - yieldController.setAlwaysDeclineTrigger(trigger); - send(ProtocolMethod.sendYieldUpdate, new YieldUpdate.TriggerDecision(trigger, AutoYieldStore.TriggerDecision.DECLINE)); + public boolean getDisableAutoTriggers() { + return yieldController.getDisableAutoTriggers(); } @Override - public void setShouldAlwaysAskTrigger(final int trigger) { - yieldController.setAlwaysAskTrigger(trigger); - send(ProtocolMethod.sendYieldUpdate, new YieldUpdate.TriggerDecision(trigger, AutoYieldStore.TriggerDecision.ASK)); + public void setDisableAutoTriggers(final boolean disable) { + yieldController.setDisableAutoTriggers(disable); + send(ProtocolMethod.sendYieldUpdate, new YieldUpdate.SetDisableTriggers(disable)); } public void setUiShouldSkipPhase(final PlayerView turnPlayer, final PhaseType phase, final boolean shouldSkip) { diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java index 19efc469e27..772326e66bf 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java @@ -18,6 +18,7 @@ import forge.gamemodes.match.YieldUpdate; import forge.gamemodes.match.input.InputConfirm; import forge.gamemodes.net.DeltaPacket; +import forge.gui.GuiBase; import forge.gui.control.PlaybackSpeed; import forge.interfaces.IGameController; import forge.item.PaperCard; @@ -35,13 +36,14 @@ public interface IGuiGame { /** - * Whether the renderer for this GUI is the libgdx (mobile) port. - * For local GUIs this matches GuiBase.getInterface().isLibgdxPort(); - * for RemoteClientGuiGame it reflects the connected client's renderer + * Whether the renderer for this GUI is the mobile port. + * For non-local GUIs this reflects the connected client's renderer * (learned at lobby handshake), letting the host pick code paths that * are actually implemented on the target. */ - boolean isLibgdxPort(); + default boolean isLibgdxPort() { + return GuiBase.getInterface().isLibgdxPort(); + } void setGameView(GameView gameView); diff --git a/forge-gui/src/main/java/forge/interfaces/IGameController.java b/forge-gui/src/main/java/forge/interfaces/IGameController.java index d10bb1032d8..91e604e84b5 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGameController.java +++ b/forge-gui/src/main/java/forge/interfaces/IGameController.java @@ -8,6 +8,7 @@ import forge.gamemodes.match.NextGameDecision; import forge.gamemodes.match.YieldController; import forge.gamemodes.match.YieldUpdate; +import forge.player.AutoYieldStore.TriggerDecision; import forge.util.ITriggerEvent; public interface IGameController { @@ -58,18 +59,17 @@ public interface IGameController { /** * @param isAbilityScope true if {@code key} is an ability suffix (Per Ability * modes); * false if {@code key} is the full raw key (Per Card mode). Server-side handlers - * route storage by this flag instead of consulting the host's own UI_AUTO_YIELD_MODE. + * route storage by this flag instead of consulting the host's own UI_AUTO_DECISION_MODE. */ void setShouldAutoYield(String key, boolean autoYield, boolean isAbilityScope); boolean getDisableAutoYields(); void setDisableAutoYields(boolean disable); // Trigger accept/decline preferences - boolean shouldAlwaysAcceptTrigger(int trigger); - boolean shouldAlwaysDeclineTrigger(int trigger); - void setShouldAlwaysAcceptTrigger(int trigger); - void setShouldAlwaysDeclineTrigger(int trigger); - void setShouldAlwaysAskTrigger(int trigger); + TriggerDecision getTriggerDecision(String key); + void setTriggerDecision(String key, TriggerDecision decision, boolean isAbilityScope); + boolean getDisableAutoTriggers(); + void setDisableAutoTriggers(boolean disable); /** Apply a unified yield update envelope to this controller's YieldController. */ void applyYieldUpdate(YieldUpdate update); diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 46de30bc89a..ed7e1f0d6f4 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -346,11 +346,11 @@ public final class ForgeConstants { public static final String DISP_CURRENT_COLORS_MULTI_OR_CHANGED = "Multi+Changed"; public static final String DISP_CURRENT_COLORS_NEVER = "Never"; - // Constants for Auto-Yield Mode - public static final String AUTO_YIELD_PER_CARD = "Per Card (Each Game)"; - public static final String AUTO_YIELD_PER_ABILITY = "Per Ability (Each Match)"; - public static final String AUTO_YIELD_PER_ABILITY_SESSION = "Per Ability (Each Session)"; - public static final String AUTO_YIELD_PER_ABILITY_INSTALL = "Per Ability (Each Install)"; + // Constants for Auto-Decision Mode (auto-yield + auto-trigger persistence scope) + public static final String AUTO_DECISION_PER_CARD = "Per Card (Each Game)"; + public static final String AUTO_DECISION_PER_ABILITY = "Per Ability (Each Match)"; + public static final String AUTO_DECISION_PER_ABILITY_SESSION = "Per Ability (Each Session)"; + public static final String AUTO_DECISION_PER_ABILITY_INSTALL = "Per Ability (Each Install)"; // Constants for Graveyard Ordering public static final String GRAVEYARD_ORDERING_NEVER = "Never"; diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java index 81fc7b43797..5b2cbb502c7 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java @@ -144,7 +144,7 @@ public enum FPref implements PreferencesStore.IPref { UI_ALT_PLAYERINFOLAYOUT ("false"), UI_ALT_PLAYERZONETABS ("false"), UI_PRESELECT_PREVIOUS_ABILITY_ORDER ("false"), - UI_AUTO_YIELD_MODE (ForgeConstants.AUTO_YIELD_PER_ABILITY), + UI_AUTO_DECISION_MODE (ForgeConstants.AUTO_DECISION_PER_ABILITY), UI_SHOW_STORM_COUNT_IN_PROMPT ("false"), UI_REMIND_ON_PRIORITY ("false"), UI_CARD_COUNTER_DISPLAY_TYPE(ForgeConstants.CounterDisplayType.TEXT.getName()), diff --git a/forge-gui/src/main/java/forge/player/AutoYieldStore.java b/forge-gui/src/main/java/forge/player/AutoYieldStore.java index fce4832a2e4..cd46190a553 100644 --- a/forge-gui/src/main/java/forge/player/AutoYieldStore.java +++ b/forge-gui/src/main/java/forge/player/AutoYieldStore.java @@ -12,48 +12,61 @@ public enum Tier { GAME, MATCH, SESSION } public enum TriggerDecision { ASK, ACCEPT, DECLINE } private final EnumMap> yieldsByTier = new EnumMap<>(Tier.class); - private final Map triggerDecisions = Maps.newTreeMap(); + private final EnumMap> triggerDecisionsByTier = new EnumMap<>(Tier.class); private boolean disabled; + private boolean triggerDecisionsDisabled; public AutoYieldStore() { - for (Tier t : Tier.values()) yieldsByTier.put(t, Sets.newHashSet()); + for (Tier t : Tier.values()) { + yieldsByTier.put(t, Sets.newHashSet()); + triggerDecisionsByTier.put(t, Maps.newHashMap()); + } } public boolean shouldYield(Tier tier, String key) { return !disabled && yieldsByTier.get(tier).contains(key); } - public void setYield(Tier tier, String key, boolean autoYield) { if (autoYield) yieldsByTier.get(tier).add(key); else yieldsByTier.get(tier).remove(key); } - public Iterable getYields(Tier tier) { return yieldsByTier.get(tier); } - public boolean isDisabled() { return disabled; } public void setDisabled(boolean disabled) { this.disabled = disabled; } + public boolean isTriggerDecisionsDisabled() { return triggerDecisionsDisabled; } + public void setTriggerDecisionsDisabled(boolean disabled) { this.triggerDecisionsDisabled = disabled; } + + public TriggerDecision getTriggerDecision(Tier tier, String key) { + TriggerDecision d = triggerDecisionsByTier.get(tier).get(key); + return d == null ? TriggerDecision.ASK : d; + } + public void setTriggerDecision(Tier tier, String key, TriggerDecision decision) { + if (decision == TriggerDecision.ASK) triggerDecisionsByTier.get(tier).remove(key); + else triggerDecisionsByTier.get(tier).put(key, decision); + } - public TriggerDecision getTriggerDecision(int triggerId) { - return triggerDecisions.getOrDefault(triggerId, TriggerDecision.ASK); + public Iterable getYields(Tier tier) { + return yieldsByTier.get(tier); } - public void setTriggerDecision(int triggerId, TriggerDecision decision) { - if (decision == TriggerDecision.ASK) triggerDecisions.remove(triggerId); - else triggerDecisions.put(triggerId, decision); + public Iterable> getAutoTriggers(Tier tier) { + return triggerDecisionsByTier.get(tier).entrySet(); } public void onGameEnd(boolean matchOver) { - triggerDecisions.clear(); yieldsByTier.get(Tier.GAME).clear(); + triggerDecisionsByTier.get(Tier.GAME).clear(); if (matchOver) { yieldsByTier.get(Tier.MATCH).clear(); + triggerDecisionsByTier.get(Tier.MATCH).clear(); } } - /** Wipe all yields, trigger decisions, and the disabled flag — used to reseed the cache from a client snapshot. */ + /** Wipe all yields, trigger decisions, and the disabled flags — used to reseed the cache from a client snapshot. */ public void clear() { for (Set set : yieldsByTier.values()) set.clear(); - triggerDecisions.clear(); + for (Map map : triggerDecisionsByTier.values()) map.clear(); disabled = false; + triggerDecisionsDisabled = false; } /** Strips the "Card (id=N): " prefix to derive the ability-scope key, or returns the input unchanged. */ diff --git a/forge-gui/src/main/java/forge/player/PersistentAutoDecisionStore.java b/forge-gui/src/main/java/forge/player/PersistentAutoDecisionStore.java new file mode 100644 index 00000000000..256b1caaa4a --- /dev/null +++ b/forge-gui/src/main/java/forge/player/PersistentAutoDecisionStore.java @@ -0,0 +1,133 @@ +package forge.player; + +import forge.localinstance.properties.ForgeConstants; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * File-backed singleton for INSTALL-tier auto-yield keys and trigger decisions. + * Shared across all local controllers on this install of Forge. + * + * Entries are keyed by SpellAbility.yieldKey() and are not + * guaranteed stable across Forge versions or card text edits. Stale entries + * silently fail to match — acceptable; users can re-add as needed. + * + * File schema (auto-yields.dat) + * yield.=true + * trigger.accept.=true + * trigger.decline.=true + */ +public class PersistentAutoDecisionStore { + private static final String YIELD_PREFIX = "yield."; + private static final String TRIGGER_ACCEPT_PREFIX = "trigger.accept."; + private static final String TRIGGER_DECLINE_PREFIX = "trigger.decline."; + + private static volatile PersistentAutoDecisionStore instance; + + public static PersistentAutoDecisionStore get() { + PersistentAutoDecisionStore local = instance; + if (local != null) return local; + synchronized (PersistentAutoDecisionStore.class) { + if (instance == null) { + instance = new PersistentAutoDecisionStore(Paths.get(ForgeConstants.USER_DIR, "auto-yields.dat")); + } + return instance; + } + } + + private final Path persistFile; + private final Set yields = new HashSet<>(); + private final Map triggerDecisions = new HashMap<>(); + + PersistentAutoDecisionStore(Path persistFile) { + this.persistFile = persistFile; + load(); + } + + public synchronized boolean contains(String key) { return yields.contains(key); } + + public synchronized void setYield(String key, boolean autoYield) { + boolean changed = autoYield ? yields.add(key) : yields.remove(key); + if (changed) save(); + } + + public synchronized Iterable getYields() { + return Collections.unmodifiableSet(new HashSet<>(yields)); + } + + public synchronized AutoYieldStore.TriggerDecision getTriggerDecision(String key) { + AutoYieldStore.TriggerDecision d = triggerDecisions.get(key); + return d == null ? AutoYieldStore.TriggerDecision.ASK : d; + } + + public synchronized void setTriggerDecision(String key, AutoYieldStore.TriggerDecision decision) { + boolean changed; + if (decision == AutoYieldStore.TriggerDecision.ASK) { + changed = triggerDecisions.remove(key) != null; + } else { + changed = decision != triggerDecisions.put(key, decision); + } + if (changed) save(); + } + + public synchronized Iterable> getAutoTriggers() { + return new HashMap<>(triggerDecisions).entrySet(); + } + + private void load() { + if (persistFile == null || !Files.exists(persistFile)) return; + try (BufferedReader r = Files.newBufferedReader(persistFile)) { + Properties p = new Properties(); + p.load(r); + for (String name : p.stringPropertyNames()) { + String value = p.getProperty(name); + if (name.startsWith(YIELD_PREFIX) && "true".equals(value)) { + yields.add(URLDecoder.decode(name.substring(YIELD_PREFIX.length()), "UTF-8")); + } else if (name.startsWith(TRIGGER_ACCEPT_PREFIX) && "true".equals(value)) { + triggerDecisions.put(URLDecoder.decode(name.substring(TRIGGER_ACCEPT_PREFIX.length()), "UTF-8"), + AutoYieldStore.TriggerDecision.ACCEPT); + } else if (name.startsWith(TRIGGER_DECLINE_PREFIX) && "true".equals(value)) { + triggerDecisions.put(URLDecoder.decode(name.substring(TRIGGER_DECLINE_PREFIX.length()), "UTF-8"), + AutoYieldStore.TriggerDecision.DECLINE); + } + } + } catch (IOException ignored) { + // Stale/corrupt file — treat as empty; next save overwrites. + } + } + + private void save() { + if (persistFile == null) return; + try { + Properties p = new Properties(); + for (String key : yields) { + p.setProperty(YIELD_PREFIX + URLEncoder.encode(key, "UTF-8"), "true"); + } + for (Map.Entry e : triggerDecisions.entrySet()) { + String prefix = e.getValue() == AutoYieldStore.TriggerDecision.ACCEPT + ? TRIGGER_ACCEPT_PREFIX : TRIGGER_DECLINE_PREFIX; + p.setProperty(prefix + URLEncoder.encode(e.getKey(), "UTF-8"), "true"); + } + Path parent = persistFile.getParent(); + if (parent != null) Files.createDirectories(parent); + try (BufferedWriter w = Files.newBufferedWriter(persistFile)) { + p.store(w, "Forge auto-yield persistent store"); + } + } catch (IOException ignored) { + // Best-effort persistence; in-memory state remains correct for this session. + } + } +} diff --git a/forge-gui/src/main/java/forge/player/PersistentYieldStore.java b/forge-gui/src/main/java/forge/player/PersistentYieldStore.java deleted file mode 100644 index 6b1b524a9c0..00000000000 --- a/forge-gui/src/main/java/forge/player/PersistentYieldStore.java +++ /dev/null @@ -1,93 +0,0 @@ -package forge.player; - -import forge.localinstance.properties.ForgeConstants; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; - -/** - * File-backed singleton holding INSTALL-tier auto-yield keys. Shared across all - * local controllers on this install of Forge. Trigger accept/decline decisions - * are not persisted: trigger IDs are not stable across games. - * - * Key stability: ability keys derive from SpellAbility.toUnsuppressedString() and - * are not guaranteed stable across Forge versions or card text edits. A stale - * persisted key silently fails to match — acceptable; users can re-add as needed. - */ -public class PersistentYieldStore { - private static final String YIELD_PREFIX = "yield."; - private static volatile PersistentYieldStore instance; - - public static PersistentYieldStore get() { - PersistentYieldStore local = instance; - if (local != null) return local; - synchronized (PersistentYieldStore.class) { - if (instance == null) { - instance = new PersistentYieldStore(Paths.get(ForgeConstants.USER_DIR, "auto-yields.dat")); - } - return instance; - } - } - - private final Path persistFile; - private final Set yields = new HashSet<>(); - - PersistentYieldStore(Path persistFile) { - this.persistFile = persistFile; - load(); - } - - public synchronized boolean contains(String key) { return yields.contains(key); } - - public synchronized void setYield(String key, boolean autoYield) { - boolean changed = autoYield ? yields.add(key) : yields.remove(key); - if (changed) save(); - } - - public synchronized Iterable getYields() { - return Collections.unmodifiableSet(new HashSet<>(yields)); - } - - private void load() { - if (persistFile == null || !Files.exists(persistFile)) return; - try (BufferedReader r = Files.newBufferedReader(persistFile)) { - Properties p = new Properties(); - p.load(r); - for (String name : p.stringPropertyNames()) { - if (name.startsWith(YIELD_PREFIX) && "true".equals(p.getProperty(name))) { - yields.add(URLDecoder.decode(name.substring(YIELD_PREFIX.length()), "UTF-8")); - } - } - } catch (IOException ignored) { - // UnsupportedEncodingException is unreachable ("UTF-8" is always supported). - // IOException: stale or corrupt file — treat as empty; next save overwrites. - } - } - - private void save() { - if (persistFile == null) return; - try { - Properties p = new Properties(); - for (String key : yields) { - p.setProperty(YIELD_PREFIX + URLEncoder.encode(key, "UTF-8"), "true"); - } - Path parent = persistFile.getParent(); - if (parent != null) Files.createDirectories(parent); - try (BufferedWriter w = Files.newBufferedWriter(persistFile)) { - p.store(w, "Forge auto-yield persistent store"); - } - } catch (IOException ignored) { - // Best-effort persistence; in-memory state remains correct for this session. - } - } -} diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 364f33eb5ef..187d6af275d 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -779,12 +779,9 @@ public boolean confirmStaticApplication(final Card hostCard, PlayerActionConfirm public boolean confirmTrigger(final WrappedAbility wrapper) { final SpellAbility sa = wrapper.getWrappedAbility(); final Trigger regtrig = wrapper.getTrigger(); - if (shouldAlwaysAcceptTrigger(regtrig.getId())) { - return true; - } - if (shouldAlwaysDeclineTrigger(regtrig.getId())) { - return false; - } + AutoYieldStore.TriggerDecision decision = getTriggerDecision(wrapper.yieldKey()); + if (decision == AutoYieldStore.TriggerDecision.ACCEPT) return true; + if (decision == AutoYieldStore.TriggerDecision.DECLINE) return false; // triggers with costs can always be declined by not paying the cost if (sa.hasParam("Cost") && !sa.getParam("Cost").equals("0")) { @@ -819,9 +816,8 @@ public boolean confirmTrigger(final WrappedAbility wrapper) { else cardView = wrapper.getCardView(); return this.getGui().confirm(cardView, buildQuestion.toString().replaceAll("\n", " ")); - } else { - return InputConfirm.confirm(this, wrapper, buildQuestion.toString()); } + return InputConfirm.confirm(this, wrapper, buildQuestion.toString()); } @Override @@ -3501,32 +3497,31 @@ public void setDisableAutoYields(final boolean disable) { } @Override - public boolean shouldAlwaysAcceptTrigger(final int trigger) { - return yieldController.shouldAlwaysAcceptTrigger(trigger); - } - @Override - public boolean shouldAlwaysDeclineTrigger(final int trigger) { - return yieldController.shouldAlwaysDeclineTrigger(trigger); + public AutoYieldStore.TriggerDecision getTriggerDecision(final String key) { + return yieldController.getTriggerDecision(key); } @Override - public void setShouldAlwaysAcceptTrigger(final int trigger) { - yieldController.setAlwaysAcceptTrigger(trigger); - if (isPromptingForTrigger(trigger)) selectButtonOk(); + public void setTriggerDecision(final String key, final AutoYieldStore.TriggerDecision decision, final boolean isAbilityScope) { + yieldController.setTriggerDecision(key, decision, isAbilityScope); + + if (!(inputQueue.getInput() instanceof InputConfirm)) return; + final SpellAbilityStackInstance top = getGame().getStack().peek(); + if (top == null || !top.isTrigger()) return; + final String topKey = top.getSpellAbility().yieldKey(); + if (key.equals(topKey) || key.equals(forge.player.AutoYieldStore.abilitySuffix(topKey))) { + if (decision == AutoYieldStore.TriggerDecision.ACCEPT) selectButtonOk(); + else if (decision == AutoYieldStore.TriggerDecision.DECLINE) selectButtonCancel(); + } } + @Override - public void setShouldAlwaysDeclineTrigger(final int trigger) { - yieldController.setAlwaysDeclineTrigger(trigger); - if (isPromptingForTrigger(trigger)) selectButtonCancel(); + public boolean getDisableAutoTriggers() { + return yieldController.getDisableAutoTriggers(); } @Override - public void setShouldAlwaysAskTrigger(final int trigger) { - yieldController.setAlwaysAskTrigger(trigger); - } - private boolean isPromptingForTrigger(final int trigger) { - if (!(inputQueue.getInput() instanceof InputConfirm)) return false; - final SpellAbilityStackInstance top = getGame().getStack().peek(); - return top != null && top.isStateTrigger(trigger); + public void setDisableAutoTriggers(final boolean disable) { + yieldController.setDisableAutoTriggers(disable); } public boolean isUiSetToSkipPhase(final PlayerView turnPlayer, final PhaseType phase) { @@ -3548,11 +3543,15 @@ public void applyYieldUpdate(final YieldUpdate update) { yieldController.setAutoPassUntilStackEmpty(u.active()); activatedYield = u.active(); } else if (update instanceof YieldUpdate.TriggerDecision u) { - yieldController.setTriggerDecision(u.trigId(), u.decision()); + yieldController.applyTriggerDecisionFromWire(u.storageKey(), u.decision()); } else if (update instanceof YieldUpdate.CardAutoYield u) { yieldController.applyAutoYieldFromWire(u.cardKey(), u.active()); } else if (update instanceof YieldUpdate.SkipPhase u) { yieldController.setSkipPhase(u.turnPlayer(), u.phase(), u.skip()); + } else if (update instanceof YieldUpdate.SetDisableYields u) { + yieldController.setDisableAutoYields(u.disabled()); + } else if (update instanceof YieldUpdate.SetDisableTriggers u) { + yieldController.setDisableAutoTriggers(u.disabled()); } else if (update instanceof YieldUpdate.SeedFromClient u) { yieldController.applyClientSeed(u.snapshot()); }