From 1aa0d321cc56991786bcfc1e8e2d622f3235e6df Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Tue, 21 May 2019 20:37:30 +0200 Subject: [PATCH 01/21] Settings i like more for the AI --- forge-gui/res/ai/Default.ai | 2 +- forge-gui/res/ai/Experimental.ai | 2 +- forge-gui/res/ai/Reckless.ai | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index 2801deae8af..9588dcf87fa 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -21,7 +21,7 @@ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA=30 # When enabled, the AI will run some additional checks in an attempt to avoid attacking into certain block situations # when it can't deal at least some permanent damage to the defending creature/planeswalker or to at least one defending # creature. Works in many but not all cases. -TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=true +TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=false # When enabled, the AI will use Berserk on offense to try to severely damage the opponent at the expense of the creature USE_BERSERK_AGGRESSIVELY=true # Try to hold combat tricks until blockers are declared in an attempt to trick the opponent into blocking a weak creature diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index 17744a3c4a1..8d3cf6fdeeb 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -21,7 +21,7 @@ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA=30 # When enabled, the AI will run some additional checks in an attempt to avoid attacking into certain block situations # when it can't deal at least some permanent damage to the defending creature/planeswalker or to at least one defending # creature. Works in many but not all cases. -TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=true +TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=false # When enabled, the AI will use Berserk on offense to try to severely damage the opponent at the expense of the creature USE_BERSERK_AGGRESSIVELY=true # Try to hold combat tricks until blockers are declared in an attempt to trick the opponent into blocking a weak creature diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index e2771362cd8..d6c1c08e5c5 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -21,7 +21,7 @@ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA=100 # When enabled, the AI will run some additional checks in an attempt to avoid attacking into certain block situations # when it can't deal at least some permanent damage to the defending creature/planeswalker or to at least one defending # creature. Works in many but not all cases. -TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=true +TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=false # When enabled, the AI will use Berserk on offense to try to severely damage the opponent at the expense of the creature USE_BERSERK_AGGRESSIVELY=true # Try to hold combat tricks until blockers are declared in an attempt to trick the opponent into blocking a weak creature From 41e07606781be7134c69f446464abef377090813 Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Sun, 26 May 2019 08:52:02 +0200 Subject: [PATCH 02/21] Riallineamento con branch master remoto --- forge-gui/res/ai/Default.ai | 2 +- forge-gui/res/ai/Experimental.ai | 2 +- forge-gui/res/ai/Reckless.ai | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index 3e273e52d5b..060ebb5f114 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -21,7 +21,7 @@ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA=30 # When enabled, the AI will run some additional checks in an attempt to avoid attacking into certain block situations # when it can't deal at least some permanent damage to the defending creature/planeswalker or to at least one defending # creature. Works in many but not all cases. -TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=false +TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=true # When enabled, the AI will use Berserk on offense to try to severely damage the opponent at the expense of the creature USE_BERSERK_AGGRESSIVELY=true # Try to hold combat tricks until blockers are declared in an attempt to trick the opponent into blocking a weak creature diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index 7a5a6fb88fa..c5592fa230c 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -21,7 +21,7 @@ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA=30 # When enabled, the AI will run some additional checks in an attempt to avoid attacking into certain block situations # when it can't deal at least some permanent damage to the defending creature/planeswalker or to at least one defending # creature. Works in many but not all cases. -TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=false +TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=true # When enabled, the AI will use Berserk on offense to try to severely damage the opponent at the expense of the creature USE_BERSERK_AGGRESSIVELY=true # Try to hold combat tricks until blockers are declared in an attempt to trick the opponent into blocking a weak creature diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index 6ce96c9e468..97cc512165f 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -21,7 +21,7 @@ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA=100 # When enabled, the AI will run some additional checks in an attempt to avoid attacking into certain block situations # when it can't deal at least some permanent damage to the defending creature/planeswalker or to at least one defending # creature. Works in many but not all cases. -TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=false +TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK=true # When enabled, the AI will use Berserk on offense to try to severely damage the opponent at the expense of the creature USE_BERSERK_AGGRESSIVELY=true # Try to hold combat tricks until blockers are declared in an attempt to trick the opponent into blocking a weak creature From eb376b8eb37c742b70f088d7268933c91d5ec7ea Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 00:55:29 -0600 Subject: [PATCH 03/21] pull all still needed `WordUtils.wrap` calls into one method --- .../forge/screens/home/quest/VSubmenuQuestData.java | 11 ++++++----- .../src/forge/screens/quest/NewQuestScreen.java | 13 +------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java index 65454ac06ce..b283bd01459 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java @@ -17,7 +17,6 @@ import forge.screens.home.EMenuGroup; import forge.screens.home.IVSubmenu; import forge.screens.home.VHomeUI; import forge.toolbox.*; -import forge.util.storage.IStorage; import forge.util.Localizer; import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.text.WordUtils; @@ -243,17 +242,15 @@ public enum VSubmenuQuestData implements IVSubmenu { cboAllowUnlocks.setSelected(true); final Map preconDescriptions = new HashMap<>(); - final IStorage preconDecks = QuestController.getPrecons(); - for (final PreconDeck preconDeck : preconDecks) { + for (final PreconDeck preconDeck : QuestController.getPrecons()) { if (QuestController.getPreconDeals(preconDeck).getMinWins() > 0) { continue; } final String name = preconDeck.getName(); cbxPreconDeck.addItem(name); String description = preconDeck.getDescription(); - description = "" + WordUtils.wrap(description, 40, "
", false) + ""; - preconDescriptions.put(name, description); + preconDescriptions.put(name, wordWrapAsHTML(description)); } // The cbx needs strictly typed renderer @@ -363,6 +360,10 @@ public enum VSubmenuQuestData implements IVSubmenu { pnlOptions.add(btnEmbark, "w 300px!, h 30px!, ax center, span 2, gap 0 0 15px 30px"); } + private static String wordWrapAsHTML(String str) { + return "" + WordUtils.wrap(str, 40, "
", false, " ") + ""; + } + /* (non-Javadoc) * @see forge.view.home.IViewSubmenu#populate() */ diff --git a/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java b/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java index fa5d33c5ba1..eecf8fc8ed4 100644 --- a/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java @@ -47,15 +47,10 @@ import forge.util.FileUtil; import forge.util.ThreadUtil; import forge.util.Utils; import forge.util.gui.SOptionPane; -import forge.util.storage.IStorage; - -import org.apache.commons.lang3.text.WordUtils; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -282,18 +277,12 @@ public class NewQuestScreen extends FScreen { cbAllowUnlocks.setSelected(true); - final Map preconDescriptions = new HashMap<>(); - IStorage preconDecks = QuestController.getPrecons(); - - for (PreconDeck preconDeck : preconDecks) { + for (PreconDeck preconDeck : QuestController.getPrecons()) { if (QuestController.getPreconDeals(preconDeck).getMinWins() > 0) { continue; } String name = preconDeck.getName(); cbxPreconDeck.addItem(name); - String description = preconDeck.getDescription(); - description = "" + WordUtils.wrap(description, 40, "
", false) + ""; - preconDescriptions.put(name, description); } // disable the very powerful sets -- they can be unlocked later for a high price From bc859b3e52b0e768e26571e379fa7acfd4319aa4 Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 00:56:12 -0600 Subject: [PATCH 04/21] inline `wrap` --- .../screens/home/quest/VSubmenuQuestData.java | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java index b283bd01459..2587fd428d0 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java @@ -19,7 +19,7 @@ import forge.screens.home.VHomeUI; import forge.toolbox.*; import forge.util.Localizer; import net.miginfocom.swing.MigLayout; -import org.apache.commons.lang3.text.WordUtils; +import org.apache.commons.lang3.StringUtils; import javax.swing.*; import javax.swing.plaf.basic.BasicComboBoxRenderer; @@ -28,6 +28,8 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Assembles Swing components of quest data submenu singleton. @@ -361,7 +363,80 @@ public enum VSubmenuQuestData implements IVSubmenu { } private static String wordWrapAsHTML(String str) { - return "" + WordUtils.wrap(str, 40, "
", false, " ") + ""; + String result = null; + int wrapLength = 40; + String newLineStr = "
"; + String wrapOn = " "; + if (str != null) { + if (newLineStr == null) { + newLineStr = System.lineSeparator(); + } + if (wrapLength < 1) { + wrapLength = 1; + } + if (StringUtils.isBlank(wrapOn)) { + wrapOn = " "; + } + final Pattern patternToWrapOn = Pattern.compile(wrapOn); + final int inputLineLength = str.length(); + int offset = 0; + final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); + while (offset < inputLineLength) { + int spaceToWrapAt = -1; + Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset + wrapLength + 1, inputLineLength))); + if (matcher.find()) { + if (matcher.start() == 0) { + offset += matcher.end(); + continue; + } + spaceToWrapAt = matcher.start() + offset; + } + + // only last line without leading spaces is left + if (inputLineLength - offset <= wrapLength) { + break; + } + + while (matcher.find()) { + spaceToWrapAt = matcher.start() + offset; + } + + if (spaceToWrapAt >= offset) { + // normal case + wrappedLine.append(str, offset, spaceToWrapAt); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + + } else { + // really long word or URL + if (false) { + // wrap really long word one line at a time + wrappedLine.append(str, offset, wrapLength + offset); + wrappedLine.append(newLineStr); + offset += wrapLength; + } else { + // do not wrap really long word, just extend beyond limit + matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength)); + if (matcher.find()) { + spaceToWrapAt = matcher.start() + offset + wrapLength; + } + + if (spaceToWrapAt >= 0) { + wrappedLine.append(str, offset, spaceToWrapAt); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + } else { + wrappedLine.append(str, offset, str.length()); + offset = inputLineLength; + } + } + } + }// Whatever is left in line is short enough to just pass through + wrappedLine.append(str, offset, str.length()); + result = wrappedLine.toString(); + } + + return "" + result + ""; } /* (non-Javadoc) From 70986149ef4bc31dbb40eeaf5021ce3583ecd544 Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 01:05:42 -0600 Subject: [PATCH 05/21] do less stuff since we know what the arguments will be. --- .../screens/home/quest/VSubmenuQuestData.java | 45 ++++++------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java index 2587fd428d0..ff95b6cb0d0 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java @@ -19,7 +19,6 @@ import forge.screens.home.VHomeUI; import forge.toolbox.*; import forge.util.Localizer; import net.miginfocom.swing.MigLayout; -import org.apache.commons.lang3.StringUtils; import javax.swing.*; import javax.swing.plaf.basic.BasicComboBoxRenderer; @@ -362,22 +361,12 @@ public enum VSubmenuQuestData implements IVSubmenu { pnlOptions.add(btnEmbark, "w 300px!, h 30px!, ax center, span 2, gap 0 0 15px 30px"); } + final static Pattern patternToWrapOn = Pattern.compile(" "); private static String wordWrapAsHTML(String str) { String result = null; int wrapLength = 40; String newLineStr = "
"; - String wrapOn = " "; if (str != null) { - if (newLineStr == null) { - newLineStr = System.lineSeparator(); - } - if (wrapLength < 1) { - wrapLength = 1; - } - if (StringUtils.isBlank(wrapOn)) { - wrapOn = " "; - } - final Pattern patternToWrapOn = Pattern.compile(wrapOn); final int inputLineLength = str.length(); int offset = 0; final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); @@ -408,27 +397,19 @@ public enum VSubmenuQuestData implements IVSubmenu { offset = spaceToWrapAt + 1; } else { - // really long word or URL - if (false) { - // wrap really long word one line at a time - wrappedLine.append(str, offset, wrapLength + offset); - wrappedLine.append(newLineStr); - offset += wrapLength; - } else { - // do not wrap really long word, just extend beyond limit - matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength)); - if (matcher.find()) { - spaceToWrapAt = matcher.start() + offset + wrapLength; - } + // do not wrap really long word, just extend beyond limit + matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength)); + if (matcher.find()) { + spaceToWrapAt = matcher.start() + offset + wrapLength; + } - if (spaceToWrapAt >= 0) { - wrappedLine.append(str, offset, spaceToWrapAt); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; - } else { - wrappedLine.append(str, offset, str.length()); - offset = inputLineLength; - } + if (spaceToWrapAt >= 0) { + wrappedLine.append(str, offset, spaceToWrapAt); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + } else { + wrappedLine.append(str, offset, str.length()); + offset = inputLineLength; } } }// Whatever is left in line is short enough to just pass through From 6808be7a42ebccdd99e5c197d5ca7fe92d633899 Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 01:33:52 -0600 Subject: [PATCH 06/21] route all `capitalize` calls through a new method --- .../src/main/java/forge/toolbox/FSkin.java | 4 ++-- .../src/main/java/forge/view/SimulateMatch.java | 4 ++-- forge-gui-mobile/src/forge/assets/FSkin.java | 4 ++-- forge-gui/src/main/java/forge/util/WordUtil.java | 11 +++++++++++ 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 forge-gui/src/main/java/forge/util/WordUtil.java diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java index f7e658da7ae..722c862906b 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java @@ -28,8 +28,8 @@ import forge.properties.ForgeConstants; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.util.OperatingSystem; +import forge.util.WordUtil; import forge.view.FView; -import org.apache.commons.lang3.text.WordUtils; import javax.imageio.ImageIO; import javax.swing.*; @@ -1084,7 +1084,7 @@ public class FSkin { allSkins = new ArrayList<>(); final List skinDirectoryNames = getSkinDirectoryNames(); for (String skinDirectoryName : skinDirectoryNames) { - allSkins.add(WordUtils.capitalize(skinDirectoryName.replace('_', ' '))); + allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' '))); } Collections.sort(allSkins); } diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java index c5b9f0fad36..a680839fcb4 100644 --- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java +++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java @@ -12,8 +12,8 @@ import forge.game.*; import forge.properties.ForgeConstants; import forge.tournament.system.*; import forge.util.TextUtil; +import forge.util.WordUtil; import forge.util.storage.IStorage; -import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.time.StopWatch; import forge.deck.Deck; @@ -75,7 +75,7 @@ public class SimulateMatch { GameType type = GameType.Constructed; if (params.containsKey("f")) { - type = GameType.valueOf(WordUtils.capitalize(params.get("f").get(0))); + type = GameType.valueOf(WordUtil.capitalize(params.get("f").get(0))); } GameRules rules = new GameRules(type); diff --git a/forge-gui-mobile/src/forge/assets/FSkin.java b/forge-gui-mobile/src/forge/assets/FSkin.java index 9ed94ee43c3..c783993411b 100644 --- a/forge-gui-mobile/src/forge/assets/FSkin.java +++ b/forge-gui-mobile/src/forge/assets/FSkin.java @@ -6,7 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.text.WordUtils; +import forge.util.WordUtil; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; @@ -101,7 +101,7 @@ public class FSkin { allSkins = new ArrayList<>(); final List skinDirectoryNames = getSkinDirectoryNames(); for (final String skinDirectoryName : skinDirectoryNames) { - allSkins.add(WordUtils.capitalize(skinDirectoryName.replace('_', ' '))); + allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' '))); } Collections.sort(allSkins); } diff --git a/forge-gui/src/main/java/forge/util/WordUtil.java b/forge-gui/src/main/java/forge/util/WordUtil.java new file mode 100644 index 00000000000..4022c50acea --- /dev/null +++ b/forge-gui/src/main/java/forge/util/WordUtil.java @@ -0,0 +1,11 @@ +package forge.util; + +import org.apache.commons.lang3.text.WordUtils; + +public class WordUtil { + public static String capitalize(String str) { + return WordUtils.capitalize(str); + } + + private WordUtil() {} +} From 4969547938df0fbc426c60e90438f53016ef61de Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 01:36:37 -0600 Subject: [PATCH 07/21] inline `capitalize` --- .../src/main/java/forge/util/WordUtil.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/forge-gui/src/main/java/forge/util/WordUtil.java b/forge-gui/src/main/java/forge/util/WordUtil.java index 4022c50acea..e69a52475df 100644 --- a/forge-gui/src/main/java/forge/util/WordUtil.java +++ b/forge-gui/src/main/java/forge/util/WordUtil.java @@ -1,10 +1,24 @@ package forge.util; -import org.apache.commons.lang3.text.WordUtils; +import org.apache.commons.lang3.StringUtils; public class WordUtil { public static String capitalize(String str) { - return WordUtils.capitalize(str); + if (StringUtils.isEmpty(str)) { + return str; + } + final char[] buffer = str.toCharArray(); + boolean capitalizeNext = true; + for (int i = 0; i < buffer.length; i++) { + final char ch = buffer[i]; + if (Character.isWhitespace(ch)) { + capitalizeNext = true; + } else if (capitalizeNext) { + buffer[i] = Character.toTitleCase(ch); + capitalizeNext = false; + } + } + return new String(buffer); } private WordUtil() {} From ac7f34d3f5f4dfef5d9355b4634b7020492f6644 Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 01:40:15 -0600 Subject: [PATCH 08/21] Cast to Object as recommended by warning --- .../src/main/java/forge/toolbox/special/PlayerDetailsPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java index bccc0c274d9..59afc0532b9 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java @@ -213,7 +213,7 @@ public class PlayerDetailsPanel extends JPanel { } public void setToolTip(final String... args) { - super.setToolTipText(String.format(tooltip, args)); + super.setToolTipText(String.format(tooltip, (Object) args)); } @Override From ea25ad2c3b654d200fcf3edf8c9cafa8de772ed7 Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 01:49:06 -0600 Subject: [PATCH 09/21] use `setVisible(true)` instead of `show` to get rid of the warning --- .../src/main/java/forge/gui/GuiChoose.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java b/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java index b47791c4b84..56bbdc0ea13 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java +++ b/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java @@ -288,16 +288,12 @@ public class GuiChoose { public static List manipulateCardList(final CMatchUI gui, final String title, final Iterable cards, final Iterable manipulable, final boolean toTop, final boolean toBottom, final boolean toAnywhere) { gui.setSelectables(manipulable); - final Callable> callable = new Callable>() { - @Override - public List call() { - ListCardArea tempArea = ListCardArea.show(gui,title,cards,manipulable,toTop,toBottom,toAnywhere); - // tempArea.pack(); - tempArea.show(); - final List cardList = tempArea.getCards(); - return cardList; - } - }; + final Callable> callable = () -> { + ListCardArea tempArea = ListCardArea.show(gui,title,cards,manipulable,toTop,toBottom,toAnywhere); + // tempArea.pack(); + tempArea.setVisible(true); + return tempArea.getCards(); + }; final FutureTask> ft = new FutureTask<>(callable); FThreads.invokeInEdtAndWait(ft); gui.clearSelectables(); From d53eb3a3fe12d20b4dee85751c581661774b28da Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 02:00:12 -0600 Subject: [PATCH 10/21] pull all `URLDecoder.decode` calls into one method and suppress the warning since changing it appears to have broken things before. --- .../forge/download/GuiDownloadService.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadService.java b/forge-gui/src/main/java/forge/download/GuiDownloadService.java index d6183fe207c..4e72ae2842d 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadService.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadService.java @@ -264,12 +264,8 @@ public abstract class GuiDownloadService implements Runnable { count++; cardSkipped = true; //assume skipped unless saved successfully String url = kv.getValue(); - /* - * decode URL Key, Reverted to old version, - * on Android 6.0 it throws an error - * when you download the card price - */ - String decodedKey = URLDecoder.decode(kv.getKey()); + + String decodedKey = decodeURL(kv.getKey()); final File fileDest = new File(decodedKey); final String filePath = fileDest.getPath(); final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : "\\db\\"; @@ -365,6 +361,16 @@ public abstract class GuiDownloadService implements Runnable { GuiBase.getInterface().preventSystemSleep(false); } + @SuppressWarnings("deprecation") + private static String decodeURL(String key) { + /* + * decode URL Key, Reverted to old version, + * on Android 6.0 it throws an error + * when you download the card price + */ + return URLDecoder.decode(key); + } + protected Proxy getProxy() { if (type == 0) { return Proxy.NO_PROXY; @@ -385,7 +391,7 @@ public abstract class GuiDownloadService implements Runnable { protected static void addMissingItems(Map list, String nameUrlFile, String dir) { for (Pair nameUrlPair : FileUtil.readNameUrlFile(nameUrlFile)) { - File f = new File(dir, URLDecoder.decode(nameUrlPair.getLeft())); + File f = new File(dir, decodeURL(nameUrlPair.getLeft())); //System.out.println(f.getAbsolutePath()); if (!f.exists()) { list.put(f.getAbsolutePath(), nameUrlPair.getRight()); From b010da744c07841cd86d4af9dbf8cbf1a26789ef Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Sun, 6 Oct 2019 02:13:37 -0600 Subject: [PATCH 11/21] progagate type information through to get rid of warning --- forge-gui-mobile/src/forge/animation/GifDecoder.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/forge-gui-mobile/src/forge/animation/GifDecoder.java b/forge-gui-mobile/src/forge/animation/GifDecoder.java index 258b8d7036c..5ff29d18df5 100644 --- a/forge-gui-mobile/src/forge/animation/GifDecoder.java +++ b/forge-gui-mobile/src/forge/animation/GifDecoder.java @@ -687,7 +687,7 @@ public class GifDecoder { } while ((blockSize > 0) && !err()); } - public Animation getAnimation(PlayMode playType) { + private Animation getAnimation(PlayMode playType) { int nrFrames = getFrameCount(); Pixmap frame = getFrame(0); int width = frame.getWidth(); @@ -725,12 +725,11 @@ public class GifDecoder { } float frameDuration = (float)getDelay(0); frameDuration /= 1000; // convert milliseconds into seconds - Animation result = new Animation(frameDuration, texReg, playType); - return result; + return new Animation<>(frameDuration, texReg, playType); } - public static Animation loadGIFAnimation(PlayMode playType, InputStream is) { + public static Animation loadGIFAnimation(PlayMode playType, InputStream is) { GifDecoder gdec = new GifDecoder(); gdec.read(is); return gdec.getAnimation(playType); From f15762b7465a2a3d7be53554c88d8a9f60814fb0 Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Thu, 7 Nov 2019 13:26:22 +0100 Subject: [PATCH 12/21] Introduction of "wild duels" in quest --- .../forge/quest/QuestEventDifficulty.java | 3 +- .../forge/quest/QuestEventDuelManager.java | 6 ++ .../java/forge/quest/io/QuestDuelReader.java | 73 +++++++++++++++++-- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java b/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java index cfdb7d2b65a..46a0f234123 100644 --- a/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java +++ b/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java @@ -10,7 +10,8 @@ public enum QuestEventDifficulty { EASY ("easy", 1. ), MEDIUM("medium", 1.5), HARD ("hard", 2. ), - EXPERT("very hard", 3. ); + EXPERT("very hard", 3. ), + WILD("wild", 2. ); private final String inFile; private final double multiplier; diff --git a/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java b/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java index 138176ed248..8628676404f 100644 --- a/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java +++ b/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java @@ -66,6 +66,7 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { private static List mediumOrder = Arrays.asList(QuestEventDifficulty.MEDIUM, QuestEventDifficulty.HARD, QuestEventDifficulty.EASY, QuestEventDifficulty.EXPERT); private static List hardOrder = Arrays.asList(QuestEventDifficulty.HARD, QuestEventDifficulty.MEDIUM, QuestEventDifficulty.EASY, QuestEventDifficulty.EXPERT); private static List expertOrder = Arrays.asList(QuestEventDifficulty.EXPERT, QuestEventDifficulty.HARD, QuestEventDifficulty.MEDIUM, QuestEventDifficulty.EASY); + private static List wildOrder = Arrays.asList(QuestEventDifficulty.WILD); private List getOrderForDifficulty(QuestEventDifficulty d) { final List difficultyOrder; @@ -83,6 +84,9 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { case EXPERT: difficultyOrder = expertOrder; break; + case WILD: + difficultyOrder = wildOrder; + break; default: throw new RuntimeException("unhandled difficulty: " + d); } @@ -203,6 +207,7 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { } } + addDuel(duelOpponents, QuestEventDifficulty.WILD, 1); addRandomDuel(duelOpponents, randomDuelDifficulty); return duelOpponents; @@ -222,6 +227,7 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { sortedDuels.put(QuestEventDifficulty.MEDIUM, new ArrayList<>()); sortedDuels.put(QuestEventDifficulty.HARD, new ArrayList<>()); sortedDuels.put(QuestEventDifficulty.EXPERT, new ArrayList<>()); + sortedDuels.put(QuestEventDifficulty.WILD, new ArrayList<>()); for (final QuestEventDuel qd : allDuels) { sortedDuels.add(qd.getDifficulty(), qd); diff --git a/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java b/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java index 616f4a0e0a3..ce4d4e554a6 100644 --- a/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java +++ b/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java @@ -3,23 +3,73 @@ package forge.quest.io; import forge.ImageKeys; import forge.deck.io.DeckSerializer; import forge.deck.io.DeckStorage; +import forge.properties.ForgeConstants; import forge.quest.QuestEvent; import forge.quest.QuestEventDifficulty; import forge.quest.QuestEventDuel; import forge.util.FileSection; import forge.util.FileUtil; +import forge.util.TextUtil; import forge.util.storage.StorageReaderFolder; import java.io.File; import java.io.FilenameFilter; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +import org.apache.commons.lang3.StringUtils; public class QuestDuelReader extends StorageReaderFolder { + + private static final String WILD_DEFAULT_ICON_NAME = "Wild.jpg"; + private static final String WILD_DIR_NAME = "wild"; + public QuestDuelReader(File deckDir0) { super(deckDir0, QuestEvent.FN_GET_NAME); // TODO Auto-generated constructor stub } + + @Override + public Map readAll() { + + List dirList = new ArrayList(); + dirList.add(directory); + + File directoryWild = new File(directory.getAbsolutePath() + ForgeConstants.PATH_SEPARATOR + WILD_DIR_NAME + ForgeConstants.PATH_SEPARATOR); + if(directoryWild.exists()) { + dirList.add(directoryWild); + } + + final Map result = new TreeMap<>(); + + for(File currDir : dirList) { + final File[] files = currDir.listFiles(this.getFileFilter()); + for (final File file : files) { + try { + final QuestEventDuel newDeck = this.read(file); + if (null == newDeck) { + final String msg = "An object stored in " + file.getPath() + " failed to load.\nPlease submit this as a bug with the mentioned file/directory attached."; + throw new RuntimeException(msg); + } + + String newKey = keySelector.apply(newDeck); + if (result.containsKey(newKey)) { + System.err.println("StorageReaderFolder: an object with key " + newKey + " is already present - skipping new entry"); + } else { + result.put(newKey, newDeck); + } + } catch (final NoSuchElementException ex) { + final String message = TextUtil.concatWithSpace( file.getName(),"failed to load because ----", ex.getMessage()); + objectsThatFailedToLoad.add(message); + } + } + } + + return result; + } @Override protected QuestEventDuel read(File file) { @@ -28,14 +78,23 @@ public class QuestDuelReader extends StorageReaderFolder { // Common properties FileSection sectionMeta = FileSection.parse(contents.get("metadata"), "="); - qc.setTitle(sectionMeta.get("Title")); qc.setName(sectionMeta.get("Name")); // Challenges have unique titles - qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty"))); - qc.setDescription(sectionMeta.get("Description", "").replace("\\n", "\n")); - qc.setCardReward(sectionMeta.get("Card Reward")); - qc.setIconImageKey(ImageKeys.ICON_PREFIX + sectionMeta.get("Icon")); - if (sectionMeta.contains("Profile")) { - qc.setProfile(sectionMeta.get("Profile")); + + boolean difficultySpecified = !StringUtils.isEmpty(sectionMeta.get("Difficulty")); + if(difficultySpecified) { + qc.setTitle(sectionMeta.get("Title")); + qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty"))); + qc.setDescription(sectionMeta.get("Description", "").replace("\\n", "\n")); + qc.setCardReward(sectionMeta.get("Card Reward")); + qc.setIconImageKey(ImageKeys.ICON_PREFIX + sectionMeta.get("Icon")); + if (sectionMeta.contains("Profile")) { + qc.setProfile(sectionMeta.get("Profile")); + } + } else { + qc.setDifficulty(QuestEventDifficulty.WILD); + qc.setTitle(sectionMeta.get("Title") != null ? sectionMeta.get("Title") : qc.getName()); + qc.setDescription(sectionMeta.get("Description") != null ? sectionMeta.get("Description") : "Wild opponent"); + qc.setIconImageKey(ImageKeys.ICON_PREFIX + (sectionMeta.get("Icon") != null ? sectionMeta.get("Icon") : WILD_DEFAULT_ICON_NAME)); } // Deck From cc3c631cade14df878ff35f71705cd079263094f Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Sun, 17 Nov 2019 17:26:31 +0100 Subject: [PATCH 13/21] Introduction of modal popup to notify when something is added to the stack, including images for cards, complete with sources and targets. --- .../game/event/GameEventSpellAbilityCast.java | 4 +- .../main/java/forge/game/zone/MagicStack.java | 3 +- .../home/settings/CSubmenuPreferences.java | 1 + .../home/settings/VSubmenuPreferences.java | 8 + .../java/forge/screens/match/CMatchUI.java | 189 +++++++++++++++++- .../main/java/forge/toolbox/FOptionPane.java | 12 ++ .../control/FControlGameEventHandler.java | 38 +++- .../main/java/forge/interfaces/IGuiGame.java | 4 + .../java/forge/match/AbstractGuiGame.java | 20 +- .../forge/properties/ForgePreferences.java | 1 + 10 files changed, 262 insertions(+), 18 deletions(-) diff --git a/forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java b/forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java index 1cc94a2c872..c65b6823a78 100644 --- a/forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java +++ b/forge-game/src/main/java/forge/game/event/GameEventSpellAbilityCast.java @@ -12,10 +12,12 @@ public class GameEventSpellAbilityCast extends GameEvent { public final SpellAbility sa; public final SpellAbilityStackInstance si; public final boolean replicate; + public final int stackIndex; - public GameEventSpellAbilityCast(SpellAbility sp, SpellAbilityStackInstance si, boolean replicate) { + public GameEventSpellAbilityCast(SpellAbility sp, SpellAbilityStackInstance si, int stackIndex, boolean replicate) { sa = sp; this.si = si; + this.stackIndex = stackIndex; this.replicate = replicate; } diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index d0040eb9745..56ceee1a273 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -414,6 +414,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable { private final JCheckBox cbEnableAICheats = new OptionsCheckBox(localizer.getMessage("cbEnableAICheats")); private final JCheckBox cbManaBurn = new OptionsCheckBox(localizer.getMessage("cbManaBurn")); private final JCheckBox cbManaLostPrompt = new OptionsCheckBox(localizer.getMessage("cbManaLostPrompt")); + private final JCheckBox cbStackAdditionPrompt = new OptionsCheckBox(localizer.getMessage("cbStackAdditionPrompt")); private final JCheckBox cbDevMode = new OptionsCheckBox(localizer.getMessage("cbDevMode")); private final JCheckBox cbLoadCardsLazily = new OptionsCheckBox(localizer.getMessage("cbLoadCardsLazily")); private final JCheckBox cbLoadHistoricFormats = new OptionsCheckBox(localizer.getMessage("cbLoadHistoricFormats")); @@ -194,6 +195,9 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbManaLostPrompt, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlManaLostPrompt")), descriptionConstraints); + pnlPrefs.add(cbStackAdditionPrompt, titleConstraints); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlStackAdditionPrompt")), descriptionConstraints); + pnlPrefs.add(cbEnforceDeckLegality, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnforceDeckLegality")), descriptionConstraints); @@ -767,6 +771,10 @@ public enum VSubmenuPreferences implements IVSubmenu { return cbManaLostPrompt; } + public final JCheckBox getCbStackAdditionPrompt() { + return cbStackAdditionPrompt; + } + public final JCheckBox getCbDetailedPaymentDesc() { return cbDetailedPaymentDesc; } 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 f6af4afd542..08735225c1b 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 @@ -18,7 +18,9 @@ package forge.screens.match; import java.awt.Component; +import java.awt.Dimension; import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -30,6 +32,8 @@ import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicReference; import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; @@ -41,9 +45,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import forge.FThreads; +import forge.GuiBase; import forge.ImageCache; import forge.LobbyPlayer; import forge.Singletons; +import forge.StaticData; import forge.assets.FSkinProp; import forge.control.KeyboardShortcuts; import forge.deck.CardPool; @@ -51,15 +57,28 @@ import forge.deck.Deck; import forge.deckchooser.FDeckViewer; import forge.game.GameEntityView; import forge.game.GameView; +import forge.game.ability.AbilityKey; +import forge.game.card.Card; import forge.game.card.CardView; +import forge.game.card.CardView.CardStateView; import forge.game.combat.CombatView; +import forge.game.event.GameEventSpellAbilityCast; +import forge.game.event.GameEventSpellRemovedFromStack; +import forge.game.keyword.Keyword; import forge.game.phase.PhaseType; import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; import forge.game.player.PlayerView; +import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityView; +import forge.game.spellability.StackItemView; +import forge.game.spellability.TargetChoices; import forge.game.zone.ZoneType; -import forge.gui.*; +import forge.gui.FNetOverlay; +import forge.gui.GuiChoose; +import forge.gui.GuiDialog; +import forge.gui.GuiUtils; +import forge.gui.SOverlayUtils; import forge.gui.framework.DragCell; import forge.gui.framework.EDocID; import forge.gui.framework.FScreen; @@ -88,19 +107,25 @@ import forge.screens.match.menus.CMatchUIMenus; import forge.screens.match.views.VField; import forge.screens.match.views.VHand; import forge.toolbox.FButton; +import forge.toolbox.FLabel; import forge.toolbox.FOptionPane; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinImage; +import forge.toolbox.FTextArea; +import forge.toolbox.imaging.FImagePanel; +import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode; +import forge.toolbox.imaging.FImageUtil; import forge.toolbox.special.PhaseIndicator; import forge.toolbox.special.PhaseLabel; import forge.trackable.TrackableCollection; +import forge.util.ITriggerEvent; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; -import forge.util.ITriggerEvent; import forge.util.gui.SOptionPane; import forge.view.FView; import forge.view.arcane.CardPanel; import forge.view.arcane.FloatingZone; +import net.miginfocom.swing.MigLayout; /** * Constructs instance of match UI controller, used as a single point of @@ -136,6 +161,7 @@ public final class CMatchUI private final CLog cLog = new CLog(this); private final CPrompt cPrompt = new CPrompt(this); private final CStack cStack = new CStack(this); + private int nextNotifiableStackIndex = 0; public CMatchUI() { this.view = new VMatchUI(this); @@ -1180,4 +1206,163 @@ public final class CMatchUI return reply == 0; } + @Override + public void notifyStackAddition(GameEventSpellAbilityCast event) { + if(FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_ADDITION_PROMPT)) { + int stackIndex = event.stackIndex; + if(stackIndex == nextNotifiableStackIndex) { + + // We can go and show the modal + StackItemView siv = event.si.getView(); + + String who = event.sa.getActivatingPlayer().getName(); + String action = event.sa.isSpell() ? " cast " : event.sa.isTrigger() ? " triggered " : " activated "; + String what = event.sa.getStackDescription().startsWith("Morph ") ? "Morph" : event.sa.getHostCard().toString(); + + StringBuilder sb = new StringBuilder(); + sb.append(who).append(action).append(what); + + if (event.sa.getTargetRestrictions() != null) { + sb.append(" targeting "); + TargetChoices targets = event.si.getTargetChoices(); + sb.append(targets.getTargetedString()); + } + sb.append("."); + String message1 = sb.toString(); + String message2 = event.si.getStackDescription(); + String messageTotal = message1 + "\n\n" + message2; + + + int rotation = getRotation(event.sa.getCardView()); + + FImagePanel imagePanel = new FImagePanel(); + BufferedImage bufferedImage = FImageUtil.getImage(siv.getSourceCard().getCurrentState()); + imagePanel.setImage(bufferedImage, rotation, AutoSizeImageMode.SOURCE); + int imageWidth = 433; + int imageHeight = 600; + Dimension imagePanelDimension = new Dimension(imageWidth,imageHeight); + imagePanel.setMinimumSize(imagePanelDimension); + + final Dimension parentSize = JOptionPane.getRootFrame().getSize(); + Dimension maxSize = new Dimension(1400, parentSize.height - 100); + + MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill"); + JPanel mainPanel = new JPanel(migLayout); + mainPanel.setMaximumSize(maxSize); + mainPanel.setOpaque(false); + + mainPanel.add(imagePanel, "cell 0 0, spany 3"); + + final FTextArea prompt1 = new FTextArea(messageTotal); + prompt1.setFont(FSkin.getFont(21)); + prompt1.setAutoSize(true); + prompt1.setMinimumSize(new Dimension(475,200)); + mainPanel.add(prompt1, "cell 1 0, aligny top"); + + SpellAbility sourceSA = (SpellAbility) event.sa.getTriggeringObject(AbilityKey.SourceSA); + if(sourceSA != null) { + // Current card is target of a triggered ability + CardView sourceCardView = sourceSA.getHostCard().getView(); + int currRotation = getRotation(sourceCardView); + FImagePanel targetPanel = new FImagePanel(); + bufferedImage = FImageUtil.getImage(sourceCardView.getCurrentState()); + targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); + imageWidth = 217; + imageHeight = 300; + Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); + targetPanel.setMinimumSize(targetPanelDimension); + mainPanel.add(targetPanel, "cell 1 1, aligny bottom"); + } else { + FCollectionView targetCards = CardView.getCollection(event.si.getTargetChoices().getTargetCards()); + + List targetSpellCards = new ArrayList(); + for(SpellAbility currSA : event.si.getTargetChoices().getTargetSpells()) { + CardView currCardView = currSA.getCardView(); + targetSpellCards.add(currCardView); + } + + FCollectionView targetPlayers = PlayerView.getCollection(event.si.getTargetChoices().getTargetPlayers()); + + int numTarget = targetCards.size() + targetSpellCards.size() + targetPlayers.size(); + + if(targetCards != null) { + for (CardView currCardView : targetCards) { + int currRotation = getRotation(currCardView); + FImagePanel targetPanel = new FImagePanel(); + bufferedImage = FImageUtil.getImage(currCardView.getCurrentState()); + targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); + imageWidth = 217; + imageHeight = 300; + Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); + targetPanel.setMinimumSize(targetPanelDimension); + mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); + } + } + if(targetSpellCards != null) { + for (CardView currCardView : targetSpellCards) { + int currRotation = getRotation(currCardView); + FImagePanel targetPanel = new FImagePanel(); + bufferedImage = FImageUtil.getImage(currCardView.getCurrentState()); + targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); + imageWidth = 217; + imageHeight = 300; + Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); + targetPanel.setMinimumSize(targetPanelDimension); + mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); + } + } + if(targetPlayers != null) { + for (PlayerView playerView : targetPlayers) { + SkinImage playerAvatar = getPlayerAvatar(playerView, 0); + final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build(); + Dimension dimension = playerAvatar.getSizeForPaint(JOptionPane.getRootFrame().getGraphics()); + mainPanel.add(lblIcon, "cell 1 1, split " + numTarget+ ", w " + dimension.getWidth() + ", h " + dimension.getHeight() + ", aligny bottom"); + } + } + } + + FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of("OK")); + // here the user closed the modal - time to update the next notifiable stack index + nextNotifiableStackIndex++; + + } else { + + // Not yet time to show the modal - schedule the method again, and try again later + Runnable tryAgainThread = new Runnable() { + @Override + public void run() { + notifyStackAddition(event); + } + }; + GuiBase.getInterface().invokeInEdtLater(tryAgainThread); + + } + } + } + + private int getRotation(CardView cardView) { + final int rotation; + if (cardView.isSplitCard()) { + String cardName = cardView.getName(); + if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getName(); } + + PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName); + boolean isAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH); + + rotation = cardView.isFaceDown() ? 0 : isAftermath ? 270 : 90; // rotate Aftermath splits the other way to correctly show the right split (graveyard) half + } else { + CardStateView cardStateView = cardView.getState(false); + rotation = cardStateView.isPlane() || cardStateView.isPhenomenon() ? 90 : 0; + } + + return rotation; + } + + @Override + public void notifyStackRemoval(GameEventSpellRemovedFromStack event) { + if(FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_ADDITION_PROMPT)) { + nextNotifiableStackIndex--; + } + } + } diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FOptionPane.java b/forge-gui-desktop/src/main/java/forge/toolbox/FOptionPane.java index 01872d6b5f7..bc6cf09a612 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FOptionPane.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FOptionPane.java @@ -78,6 +78,10 @@ public class FOptionPane extends FDialog { return showOptionDialog(message, title, icon, options, 0); } + public static int showOptionDialog(final String message, final String title, final SkinImage icon, Component comp, final List options) { + return showOptionDialog(message, title, icon, comp, options, 0); + } + public static int showOptionDialog(final String message, final String title, final SkinImage icon, final List options, final int defaultOption) { final FOptionPane optionPane = new FOptionPane(message, title, icon, null, options, defaultOption); optionPane.setVisible(true); @@ -86,6 +90,14 @@ public class FOptionPane extends FDialog { return dialogResult; } + public static int showOptionDialog(final String message, final String title, final SkinImage icon, final Component comp, final List options, final int defaultOption) { + final FOptionPane optionPane = new FOptionPane(message, title, icon, comp, options, defaultOption); + optionPane.setVisible(true); + final int dialogResult = optionPane.result; + optionPane.dispose(); + return dialogResult; + } + public static String showInputDialog(final String message, final String title) { return showInputDialog(message, title, null, "", null); } diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index d48cbcca2ce..3d943831f6b 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -239,8 +239,18 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public Void visit(final GameEventSpellAbilityCast event) { - needStackUpdate = true; - return processEvent(); + needStackUpdate = true; + processEvent(); + + final Runnable notifyStackAddition = new Runnable() { + @Override + public void run() { + matchController.notifyStackAddition(event); + } + }; + GuiBase.getInterface().invokeInEdtLater(notifyStackAddition); + + return null; } @Override @@ -252,7 +262,17 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public Void visit(final GameEventSpellRemovedFromStack event) { needStackUpdate = true; - return processEvent(); + processEvent(); + + final Runnable notifyStackAddition = new Runnable() { + @Override + public void run() { + matchController.notifyStackRemoval(event); + } + }; + GuiBase.getInterface().invokeInEdtLater(notifyStackAddition); + + return null; } @Override @@ -341,10 +361,10 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { if(event.to.getZoneType() == ZoneType.Battlefield) refreshFieldUpdate = true; //pfps the change to the zones have already been performed with add and remove calls - // this is only for playing a sound - // updateZone(event.from); + // this is only for playing a sound + // updateZone(event.from); //return updateZone(event.to); - return processEvent(); + return processEvent(); } @@ -374,9 +394,9 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public Void visit(final GameEventShuffle event) { //pfps the change to the library has already been performed by a setCards call - // this is only for playing a sound - // return updateZone(event.player.getZone(ZoneType.Library)); - return processEvent(); + // this is only for playing a sound + // return updateZone(event.player.getZone(ZoneType.Library)); + return processEvent(); } @Override diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java index 45841b6562a..e7057e243b8 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java @@ -12,6 +12,8 @@ import forge.deck.CardPool; import forge.game.GameEntityView; import forge.game.GameView; import forge.game.card.CardView; +import forge.game.event.GameEventSpellAbilityCast; +import forge.game.event.GameEventSpellRemovedFromStack; import forge.game.phase.PhaseType; import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; @@ -47,6 +49,8 @@ public interface IGuiGame { void showManaPool(PlayerView player); void hideManaPool(PlayerView player); void updateStack(); + void notifyStackAddition(final GameEventSpellAbilityCast event); + void notifyStackRemoval(final GameEventSpellRemovedFromStack event); Iterable tempShowZones(PlayerView controller, Iterable zonesToUpdate); void hideZones(PlayerView controller, Iterable zonesToUpdate); void updateZones(Iterable zonesToUpdate); diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index bd8bff93ddf..d0d46d6fba6 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -24,6 +24,8 @@ import forge.assets.FSkinProp; import forge.game.GameView; import forge.game.card.CardView; import forge.game.card.CardView.CardStateView; +import forge.game.event.GameEventSpellAbilityCast; +import forge.game.event.GameEventSpellRemovedFromStack; import forge.game.player.PlayerView; import forge.interfaces.IGameController; import forge.interfaces.IGuiGame; @@ -250,16 +252,16 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { private final Set selectableCards = Sets.newHashSet(); public void setSelectables(final Iterable cards) { - for ( CardView cv : cards ) { selectableCards.add(cv); } + for ( CardView cv : cards ) { selectableCards.add(cv); } } public void clearSelectables() { - selectableCards.clear(); + selectableCards.clear(); } public boolean isSelectable(final CardView card) { - return selectableCards.contains(card); + return selectableCards.contains(card); } public boolean isSelecting() { - return !selectableCards.isEmpty(); + return !selectableCards.isEmpty(); } /** Concede game, bring up WinLose UI. */ @@ -690,6 +692,14 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { final String yesButtonText, final String noButtonText) { return showConfirmDialog(message, title, yesButtonText, noButtonText, true); } - + + @Override + public void notifyStackAddition(GameEventSpellAbilityCast event) { + } + + @Override + public void notifyStackRemoval(GameEventSpellRemovedFromStack event) { + } + // End of Choice code } diff --git a/forge-gui/src/main/java/forge/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/properties/ForgePreferences.java index 941ac2a1ed0..b9ef0e5cc66 100644 --- a/forge-gui/src/main/java/forge/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/properties/ForgePreferences.java @@ -119,6 +119,7 @@ public class ForgePreferences extends PreferencesStore { UI_HIDE_GAME_TABS ("false"), // Visibility of tabs in match screen. UI_CLOSE_ACTION ("NONE"), UI_MANA_LOST_PROMPT ("false"), // Prompt on losing mana when passing priority + UI_STACK_ADDITION_PROMPT ("false"), UI_PAUSE_WHILE_MINIMIZED("false"), UI_TOKENS_IN_SEPARATE_ROW("false"), // Display tokens in their own battlefield row. UI_DISPLAY_CURRENT_COLORS(ForgeConstants.DISP_CURRENT_COLORS_NEVER), From f453c2af78bc336cf083f21d27db088deb7eb31d Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Sun, 17 Nov 2019 17:42:51 +0100 Subject: [PATCH 14/21] Labels for the preferences of the new stack addition modal popup --- forge-gui/res/languages/en-US.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 7a73d4ccfb4..c10e8a0f3ce 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -61,6 +61,7 @@ cbAnteMatchRarity=Match Ante Rarity cbEnableAICheats=Allow AI Cheating cbManaBurn=Mana Burn cbManaLostPrompt=Prompt Mana Pool Emptying +cbStackAdditionPrompt=Prompt Stack Addition cbDevMode=Developer Mode cbLoadCardsLazily=Load Card Scripts Lazily cbLoadHistoricFormats=Load Historic Formats @@ -121,6 +122,7 @@ nlAnteMatchRarity=Attempts to make antes the same rarity for all players. nlEnableAICheats=Allow the AI to cheat to gain advantage (for personalities that have cheat shuffling options set). nlManaBurn=Play with mana burn (from pre-Magic 2010 rules). nlManaLostPrompt=When enabled, you get a warning if passing priority would cause you to lose mana in your mana pool. +nlStackAdditionPrompt=When enabled, you get a notification popup each time a new effect is added to the stack nlEnforceDeckLegality=Enforces deck legality relevant to each environment (minimum deck sizes, max card count etc). nlSideboardForAI=Allows users to sideboard with the AIs deck and sideboard in constructed game formats. nlPerformanceMode=Disables additional static abilities checks to speed up the game engine. (Warning: breaks some 'as if had flash' scenarios when casting cards owned by opponents). From f0076117df426715d358f7243796b98b9d5abe08 Mon Sep 17 00:00:00 2001 From: Ryan1729 Date: Tue, 19 Nov 2019 19:38:51 -0700 Subject: [PATCH 15/21] respond to code review --- .../src/main/java/forge/gui/GuiChoose.java | 14 ++-- .../screens/home/quest/VSubmenuQuestData.java | 64 +------------------ .../src/main/java/forge/util/WordUtil.java | 62 ++++++++++++++++++ 3 files changed, 73 insertions(+), 67 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java b/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java index 56bbdc0ea13..f64ae7e077e 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java +++ b/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java @@ -288,11 +288,15 @@ public class GuiChoose { public static List manipulateCardList(final CMatchUI gui, final String title, final Iterable cards, final Iterable manipulable, final boolean toTop, final boolean toBottom, final boolean toAnywhere) { gui.setSelectables(manipulable); - final Callable> callable = () -> { - ListCardArea tempArea = ListCardArea.show(gui,title,cards,manipulable,toTop,toBottom,toAnywhere); - // tempArea.pack(); - tempArea.setVisible(true); - return tempArea.getCards(); + @SuppressWarnings("Convert2Lambda") // Avoid lambdas to maintain compatibility with Android 5 API + final Callable> callable = new Callable>() { + @Override + public List call() { + ListCardArea tempArea = ListCardArea.show(gui,title,cards,manipulable,toTop,toBottom,toAnywhere); + // tempArea.pack(); + tempArea.setVisible(true); + return tempArea.getCards(); + } }; final FutureTask> ft = new FutureTask<>(callable); FThreads.invokeInEdtAndWait(ft); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java index ff95b6cb0d0..c3a505d3d76 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/quest/VSubmenuQuestData.java @@ -18,6 +18,7 @@ import forge.screens.home.IVSubmenu; import forge.screens.home.VHomeUI; import forge.toolbox.*; import forge.util.Localizer; +import forge.util.WordUtil; import net.miginfocom.swing.MigLayout; import javax.swing.*; @@ -27,8 +28,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Assembles Swing components of quest data submenu singleton. @@ -251,7 +250,7 @@ public enum VSubmenuQuestData implements IVSubmenu { final String name = preconDeck.getName(); cbxPreconDeck.addItem(name); String description = preconDeck.getDescription(); - preconDescriptions.put(name, wordWrapAsHTML(description)); + preconDescriptions.put(name, WordUtil.wordWrapAsHTML(description)); } // The cbx needs strictly typed renderer @@ -361,65 +360,6 @@ public enum VSubmenuQuestData implements IVSubmenu { pnlOptions.add(btnEmbark, "w 300px!, h 30px!, ax center, span 2, gap 0 0 15px 30px"); } - final static Pattern patternToWrapOn = Pattern.compile(" "); - private static String wordWrapAsHTML(String str) { - String result = null; - int wrapLength = 40; - String newLineStr = "
"; - if (str != null) { - final int inputLineLength = str.length(); - int offset = 0; - final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); - while (offset < inputLineLength) { - int spaceToWrapAt = -1; - Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset + wrapLength + 1, inputLineLength))); - if (matcher.find()) { - if (matcher.start() == 0) { - offset += matcher.end(); - continue; - } - spaceToWrapAt = matcher.start() + offset; - } - - // only last line without leading spaces is left - if (inputLineLength - offset <= wrapLength) { - break; - } - - while (matcher.find()) { - spaceToWrapAt = matcher.start() + offset; - } - - if (spaceToWrapAt >= offset) { - // normal case - wrappedLine.append(str, offset, spaceToWrapAt); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; - - } else { - // do not wrap really long word, just extend beyond limit - matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength)); - if (matcher.find()) { - spaceToWrapAt = matcher.start() + offset + wrapLength; - } - - if (spaceToWrapAt >= 0) { - wrappedLine.append(str, offset, spaceToWrapAt); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; - } else { - wrappedLine.append(str, offset, str.length()); - offset = inputLineLength; - } - } - }// Whatever is left in line is short enough to just pass through - wrappedLine.append(str, offset, str.length()); - result = wrappedLine.toString(); - } - - return "" + result + ""; - } - /* (non-Javadoc) * @see forge.view.home.IViewSubmenu#populate() */ diff --git a/forge-gui/src/main/java/forge/util/WordUtil.java b/forge-gui/src/main/java/forge/util/WordUtil.java index e69a52475df..83251e58ec9 100644 --- a/forge-gui/src/main/java/forge/util/WordUtil.java +++ b/forge-gui/src/main/java/forge/util/WordUtil.java @@ -2,6 +2,9 @@ package forge.util; import org.apache.commons.lang3.StringUtils; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class WordUtil { public static String capitalize(String str) { if (StringUtils.isEmpty(str)) { @@ -21,5 +24,64 @@ public class WordUtil { return new String(buffer); } + private final static Pattern patternToWrapOn = Pattern.compile(" "); + public static String wordWrapAsHTML(String str) { + String result = null; + int wrapLength = 40; + String newLineStr = "
"; + if (str != null) { + final int inputLineLength = str.length(); + int offset = 0; + final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); + while (offset < inputLineLength) { + int spaceToWrapAt = -1; + Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset + wrapLength + 1, inputLineLength))); + if (matcher.find()) { + if (matcher.start() == 0) { + offset += matcher.end(); + continue; + } + spaceToWrapAt = matcher.start() + offset; + } + + // only last line without leading spaces is left + if (inputLineLength - offset <= wrapLength) { + break; + } + + while (matcher.find()) { + spaceToWrapAt = matcher.start() + offset; + } + + if (spaceToWrapAt >= offset) { + // normal case + wrappedLine.append(str, offset, spaceToWrapAt); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + + } else { + // do not wrap really long word, just extend beyond limit + matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength)); + if (matcher.find()) { + spaceToWrapAt = matcher.start() + offset + wrapLength; + } + + if (spaceToWrapAt >= 0) { + wrappedLine.append(str, offset, spaceToWrapAt); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + } else { + wrappedLine.append(str, offset, str.length()); + offset = inputLineLength; + } + } + }// Whatever is left in line is short enough to just pass through + wrappedLine.append(str, offset, str.length()); + result = wrappedLine.toString(); + } + + return "" + result + ""; + } + private WordUtil() {} } From 2c70b8b7e0f56bffca947682dc749b62e13337b9 Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Thu, 21 Nov 2019 16:49:50 +0100 Subject: [PATCH 16/21] End of the stack modal panel implementation, rollback about the Wild Quests implementation --- .../java/forge/screens/match/CMatchUI.java | 247 ++++++++++-------- .../forge/quest/QuestEventDifficulty.java | 3 +- .../forge/quest/QuestEventDuelManager.java | 6 - .../java/forge/quest/io/QuestDuelReader.java | 73 +----- 4 files changed, 152 insertions(+), 177 deletions(-) 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 08735225c1b..bf5ea0d7a8a 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 @@ -51,10 +51,12 @@ import forge.LobbyPlayer; import forge.Singletons; import forge.StaticData; import forge.assets.FSkinProp; +import forge.card.CardStateName; import forge.control.KeyboardShortcuts; import forge.deck.CardPool; import forge.deck.Deck; import forge.deckchooser.FDeckViewer; +import forge.game.GameEntity; import forge.game.GameEntityView; import forge.game.GameView; import forge.game.ability.AbilityKey; @@ -70,6 +72,7 @@ import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; import forge.game.player.PlayerView; import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityView; import forge.game.spellability.StackItemView; import forge.game.spellability.TargetChoices; @@ -1213,114 +1216,68 @@ public final class CMatchUI if(stackIndex == nextNotifiableStackIndex) { // We can go and show the modal - StackItemView siv = event.si.getView(); - - String who = event.sa.getActivatingPlayer().getName(); - String action = event.sa.isSpell() ? " cast " : event.sa.isTrigger() ? " triggered " : " activated "; - String what = event.sa.getStackDescription().startsWith("Morph ") ? "Morph" : event.sa.getHostCard().toString(); - - StringBuilder sb = new StringBuilder(); - sb.append(who).append(action).append(what); - - if (event.sa.getTargetRestrictions() != null) { - sb.append(" targeting "); - TargetChoices targets = event.si.getTargetChoices(); - sb.append(targets.getTargetedString()); - } - sb.append("."); - String message1 = sb.toString(); - String message2 = event.si.getStackDescription(); - String messageTotal = message1 + "\n\n" + message2; - - - int rotation = getRotation(event.sa.getCardView()); - - FImagePanel imagePanel = new FImagePanel(); - BufferedImage bufferedImage = FImageUtil.getImage(siv.getSourceCard().getCurrentState()); - imagePanel.setImage(bufferedImage, rotation, AutoSizeImageMode.SOURCE); - int imageWidth = 433; - int imageHeight = 600; - Dimension imagePanelDimension = new Dimension(imageWidth,imageHeight); - imagePanel.setMinimumSize(imagePanelDimension); - - final Dimension parentSize = JOptionPane.getRootFrame().getSize(); - Dimension maxSize = new Dimension(1400, parentSize.height - 100); + SpellAbility sa = event.sa; + SpellAbilityStackInstance si = event.si; MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill"); JPanel mainPanel = new JPanel(migLayout); + final Dimension parentSize = JOptionPane.getRootFrame().getSize(); + Dimension maxSize = new Dimension(1400, parentSize.height - 100); mainPanel.setMaximumSize(maxSize); - mainPanel.setOpaque(false); + mainPanel.setOpaque(false); + + // Big Image + addBigImageToStackModalPanel(mainPanel, si); - mainPanel.add(imagePanel, "cell 0 0, spany 3"); + // Text + addTextToStackModalPanel(mainPanel,sa,si); - final FTextArea prompt1 = new FTextArea(messageTotal); - prompt1.setFont(FSkin.getFont(21)); - prompt1.setAutoSize(true); - prompt1.setMinimumSize(new Dimension(475,200)); - mainPanel.add(prompt1, "cell 1 0, aligny top"); - - SpellAbility sourceSA = (SpellAbility) event.sa.getTriggeringObject(AbilityKey.SourceSA); + // Small images + int numSmallImages = 0; + + // If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card + GameEntityView enchantedEntityView = null; + Card hostCard = sa.getHostCard(); + if(hostCard.isEnchantment()) { + GameEntity enchantedEntity = hostCard.getEntityAttachedTo(); + if(enchantedEntity != null) { + enchantedEntityView = enchantedEntity.getView(); + numSmallImages++; + } else if ((sa.getRootAbility() != null) + && (sa.getRootAbility().getPaidList("Sacrificed") != null) + && !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) { + // If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace" + enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard(); + if(enchantedEntity != null) { + enchantedEntityView = enchantedEntity.getView(); + numSmallImages++; + } + } + } + + // If current effect is a triggered ability, I want to show the triggering card if present + SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA); + CardView sourceCardView = null; if(sourceSA != null) { - // Current card is target of a triggered ability - CardView sourceCardView = sourceSA.getHostCard().getView(); - int currRotation = getRotation(sourceCardView); - FImagePanel targetPanel = new FImagePanel(); - bufferedImage = FImageUtil.getImage(sourceCardView.getCurrentState()); - targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); - imageWidth = 217; - imageHeight = 300; - Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); - targetPanel.setMinimumSize(targetPanelDimension); - mainPanel.add(targetPanel, "cell 1 1, aligny bottom"); - } else { - FCollectionView targetCards = CardView.getCollection(event.si.getTargetChoices().getTargetCards()); - - List targetSpellCards = new ArrayList(); - for(SpellAbility currSA : event.si.getTargetChoices().getTargetSpells()) { - CardView currCardView = currSA.getCardView(); - targetSpellCards.add(currCardView); - } - - FCollectionView targetPlayers = PlayerView.getCollection(event.si.getTargetChoices().getTargetPlayers()); - - int numTarget = targetCards.size() + targetSpellCards.size() + targetPlayers.size(); - - if(targetCards != null) { - for (CardView currCardView : targetCards) { - int currRotation = getRotation(currCardView); - FImagePanel targetPanel = new FImagePanel(); - bufferedImage = FImageUtil.getImage(currCardView.getCurrentState()); - targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); - imageWidth = 217; - imageHeight = 300; - Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); - targetPanel.setMinimumSize(targetPanelDimension); - mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); - } - } - if(targetSpellCards != null) { - for (CardView currCardView : targetSpellCards) { - int currRotation = getRotation(currCardView); - FImagePanel targetPanel = new FImagePanel(); - bufferedImage = FImageUtil.getImage(currCardView.getCurrentState()); - targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); - imageWidth = 217; - imageHeight = 300; - Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); - targetPanel.setMinimumSize(targetPanelDimension); - mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); - } - } - if(targetPlayers != null) { - for (PlayerView playerView : targetPlayers) { - SkinImage playerAvatar = getPlayerAvatar(playerView, 0); - final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build(); - Dimension dimension = playerAvatar.getSizeForPaint(JOptionPane.getRootFrame().getGraphics()); - mainPanel.add(lblIcon, "cell 1 1, split " + numTarget+ ", w " + dimension.getWidth() + ", h " + dimension.getHeight() + ", aligny bottom"); - } - } - } - + sourceCardView = sourceSA.getHostCard().getView(); + numSmallImages++; + } + + // I also want to show each type of targets (both cards and players) + List targets = getTargets(si,new ArrayList()); + numSmallImages = numSmallImages + targets.size(); + + // Now I know how many small images - on to render them + if(enchantedEntityView != null) { + addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages); + } + if(sourceCardView != null) { + addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages); + } + for(GameEntityView gev : targets) { + addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages); + } + FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of("OK")); // here the user closed the modal - time to update the next notifiable stack index nextNotifiableStackIndex++; @@ -1340,6 +1297,90 @@ public final class CMatchUI } } + private List getTargets(SpellAbilityStackInstance si, List result){ + if(si == null) { + return result; + } else { + FCollectionView targetCards = CardView.getCollection(si.getTargetChoices().getTargetCards()); + for(CardView currCardView: targetCards) { + result.add(currCardView); + } + + for(SpellAbility currSA : si.getTargetChoices().getTargetSpells()) { + CardView currCardView = currSA.getCardView(); + result.add(currCardView); + } + + FCollectionView targetPlayers = PlayerView.getCollection(si.getTargetChoices().getTargetPlayers()); + for(PlayerView currPlayerView : targetPlayers) { + result.add(currPlayerView); + } + + return getTargets(si.getSubInstance(),result); + } + } + + private void addBigImageToStackModalPanel(JPanel mainPanel, SpellAbilityStackInstance si) { + StackItemView siv = si.getView(); + int rotation = getRotation(si.getCardView()); + + FImagePanel imagePanel = new FImagePanel(); + BufferedImage bufferedImage = FImageUtil.getImage(siv.getSourceCard().getCurrentState()); + imagePanel.setImage(bufferedImage, rotation, AutoSizeImageMode.SOURCE); + int imageWidth = 433; + int imageHeight = 600; + Dimension imagePanelDimension = new Dimension(imageWidth,imageHeight); + imagePanel.setMinimumSize(imagePanelDimension); + + mainPanel.add(imagePanel, "cell 0 0, spany 3"); + } + + private void addTextToStackModalPanel(JPanel mainPanel, SpellAbility sa, SpellAbilityStackInstance si) { + String who = sa.getActivatingPlayer().getName(); + String action = sa.isSpell() ? " cast " : sa.isTrigger() ? " triggered " : " activated "; + String what = sa.getStackDescription().startsWith("Morph ") ? "Morph" : sa.getHostCard().toString(); + + StringBuilder sb = new StringBuilder(); + sb.append(who).append(action).append(what); + + if (sa.getTargetRestrictions() != null) { + sb.append(" targeting "); + TargetChoices targets = si.getTargetChoices(); + sb.append(targets.getTargetedString()); + } + sb.append("."); + String message1 = sb.toString(); + String message2 = si.getStackDescription(); + String messageTotal = message1 + "\n\n" + message2; + + final FTextArea prompt1 = new FTextArea(messageTotal); + prompt1.setFont(FSkin.getFont(21)); + prompt1.setAutoSize(true); + prompt1.setMinimumSize(new Dimension(475,200)); + mainPanel.add(prompt1, "cell 1 0, aligny top"); + } + + private void addSmallImageToStackModalPanel(GameEntityView gameEntityView, JPanel mainPanel, int numTarget) { + if(gameEntityView instanceof CardView) { + CardView cardView = (CardView) gameEntityView; + int currRotation = getRotation(cardView); + FImagePanel targetPanel = new FImagePanel(); + BufferedImage bufferedImage = FImageUtil.getImage(cardView.getCurrentState()); + targetPanel.setImage(bufferedImage, currRotation, AutoSizeImageMode.SOURCE); + int imageWidth = 217; + int imageHeight = 300; + Dimension targetPanelDimension = new Dimension(imageWidth,imageHeight); + targetPanel.setMinimumSize(targetPanelDimension); + mainPanel.add(targetPanel, "cell 1 1, split " + numTarget+ ", aligny bottom"); + } else if(gameEntityView instanceof PlayerView) { + PlayerView playerView = (PlayerView) gameEntityView; + SkinImage playerAvatar = getPlayerAvatar(playerView, 0); + final FLabel lblIcon = new FLabel.Builder().icon(playerAvatar).build(); + Dimension dimension = playerAvatar.getSizeForPaint(JOptionPane.getRootFrame().getGraphics()); + mainPanel.add(lblIcon, "cell 1 1, split " + numTarget+ ", w " + dimension.getWidth() + ", h " + dimension.getHeight() + ", aligny bottom"); + } + } + private int getRotation(CardView cardView) { final int rotation; if (cardView.isSplitCard()) { @@ -1347,9 +1388,9 @@ public final class CMatchUI if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getName(); } PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName); - boolean isAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH); + boolean hasKeywordAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH); - rotation = cardView.isFaceDown() ? 0 : isAftermath ? 270 : 90; // rotate Aftermath splits the other way to correctly show the right split (graveyard) half + rotation = cardView.isFaceDown() ? 0 : hasKeywordAftermath ? (CardStateName.LeftSplit.equals(cardView.getState(false).getState()) ? 0 : 270) : 90; // rotate Aftermath splits the other way to correctly show the right split (graveyard) half } else { CardStateView cardStateView = cardView.getState(false); rotation = cardStateView.isPlane() || cardStateView.isPhenomenon() ? 90 : 0; diff --git a/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java b/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java index 46a0f234123..cfdb7d2b65a 100644 --- a/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java +++ b/forge-gui/src/main/java/forge/quest/QuestEventDifficulty.java @@ -10,8 +10,7 @@ public enum QuestEventDifficulty { EASY ("easy", 1. ), MEDIUM("medium", 1.5), HARD ("hard", 2. ), - EXPERT("very hard", 3. ), - WILD("wild", 2. ); + EXPERT("very hard", 3. ); private final String inFile; private final double multiplier; diff --git a/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java b/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java index 8628676404f..138176ed248 100644 --- a/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java +++ b/forge-gui/src/main/java/forge/quest/QuestEventDuelManager.java @@ -66,7 +66,6 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { private static List mediumOrder = Arrays.asList(QuestEventDifficulty.MEDIUM, QuestEventDifficulty.HARD, QuestEventDifficulty.EASY, QuestEventDifficulty.EXPERT); private static List hardOrder = Arrays.asList(QuestEventDifficulty.HARD, QuestEventDifficulty.MEDIUM, QuestEventDifficulty.EASY, QuestEventDifficulty.EXPERT); private static List expertOrder = Arrays.asList(QuestEventDifficulty.EXPERT, QuestEventDifficulty.HARD, QuestEventDifficulty.MEDIUM, QuestEventDifficulty.EASY); - private static List wildOrder = Arrays.asList(QuestEventDifficulty.WILD); private List getOrderForDifficulty(QuestEventDifficulty d) { final List difficultyOrder; @@ -84,9 +83,6 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { case EXPERT: difficultyOrder = expertOrder; break; - case WILD: - difficultyOrder = wildOrder; - break; default: throw new RuntimeException("unhandled difficulty: " + d); } @@ -207,7 +203,6 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { } } - addDuel(duelOpponents, QuestEventDifficulty.WILD, 1); addRandomDuel(duelOpponents, randomDuelDifficulty); return duelOpponents; @@ -227,7 +222,6 @@ public class QuestEventDuelManager implements QuestEventDuelManagerInterface { sortedDuels.put(QuestEventDifficulty.MEDIUM, new ArrayList<>()); sortedDuels.put(QuestEventDifficulty.HARD, new ArrayList<>()); sortedDuels.put(QuestEventDifficulty.EXPERT, new ArrayList<>()); - sortedDuels.put(QuestEventDifficulty.WILD, new ArrayList<>()); for (final QuestEventDuel qd : allDuels) { sortedDuels.add(qd.getDifficulty(), qd); diff --git a/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java b/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java index ce4d4e554a6..616f4a0e0a3 100644 --- a/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java +++ b/forge-gui/src/main/java/forge/quest/io/QuestDuelReader.java @@ -3,73 +3,23 @@ package forge.quest.io; import forge.ImageKeys; import forge.deck.io.DeckSerializer; import forge.deck.io.DeckStorage; -import forge.properties.ForgeConstants; import forge.quest.QuestEvent; import forge.quest.QuestEventDifficulty; import forge.quest.QuestEventDuel; import forge.util.FileSection; import forge.util.FileUtil; -import forge.util.TextUtil; import forge.util.storage.StorageReaderFolder; import java.io.File; import java.io.FilenameFilter; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; -import java.util.TreeMap; - -import org.apache.commons.lang3.StringUtils; public class QuestDuelReader extends StorageReaderFolder { - - private static final String WILD_DEFAULT_ICON_NAME = "Wild.jpg"; - private static final String WILD_DIR_NAME = "wild"; - public QuestDuelReader(File deckDir0) { super(deckDir0, QuestEvent.FN_GET_NAME); // TODO Auto-generated constructor stub } - - @Override - public Map readAll() { - - List dirList = new ArrayList(); - dirList.add(directory); - - File directoryWild = new File(directory.getAbsolutePath() + ForgeConstants.PATH_SEPARATOR + WILD_DIR_NAME + ForgeConstants.PATH_SEPARATOR); - if(directoryWild.exists()) { - dirList.add(directoryWild); - } - - final Map result = new TreeMap<>(); - - for(File currDir : dirList) { - final File[] files = currDir.listFiles(this.getFileFilter()); - for (final File file : files) { - try { - final QuestEventDuel newDeck = this.read(file); - if (null == newDeck) { - final String msg = "An object stored in " + file.getPath() + " failed to load.\nPlease submit this as a bug with the mentioned file/directory attached."; - throw new RuntimeException(msg); - } - - String newKey = keySelector.apply(newDeck); - if (result.containsKey(newKey)) { - System.err.println("StorageReaderFolder: an object with key " + newKey + " is already present - skipping new entry"); - } else { - result.put(newKey, newDeck); - } - } catch (final NoSuchElementException ex) { - final String message = TextUtil.concatWithSpace( file.getName(),"failed to load because ----", ex.getMessage()); - objectsThatFailedToLoad.add(message); - } - } - } - - return result; - } @Override protected QuestEventDuel read(File file) { @@ -78,23 +28,14 @@ public class QuestDuelReader extends StorageReaderFolder { // Common properties FileSection sectionMeta = FileSection.parse(contents.get("metadata"), "="); + qc.setTitle(sectionMeta.get("Title")); qc.setName(sectionMeta.get("Name")); // Challenges have unique titles - - boolean difficultySpecified = !StringUtils.isEmpty(sectionMeta.get("Difficulty")); - if(difficultySpecified) { - qc.setTitle(sectionMeta.get("Title")); - qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty"))); - qc.setDescription(sectionMeta.get("Description", "").replace("\\n", "\n")); - qc.setCardReward(sectionMeta.get("Card Reward")); - qc.setIconImageKey(ImageKeys.ICON_PREFIX + sectionMeta.get("Icon")); - if (sectionMeta.contains("Profile")) { - qc.setProfile(sectionMeta.get("Profile")); - } - } else { - qc.setDifficulty(QuestEventDifficulty.WILD); - qc.setTitle(sectionMeta.get("Title") != null ? sectionMeta.get("Title") : qc.getName()); - qc.setDescription(sectionMeta.get("Description") != null ? sectionMeta.get("Description") : "Wild opponent"); - qc.setIconImageKey(ImageKeys.ICON_PREFIX + (sectionMeta.get("Icon") != null ? sectionMeta.get("Icon") : WILD_DEFAULT_ICON_NAME)); + qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty"))); + qc.setDescription(sectionMeta.get("Description", "").replace("\\n", "\n")); + qc.setCardReward(sectionMeta.get("Card Reward")); + qc.setIconImageKey(ImageKeys.ICON_PREFIX + sectionMeta.get("Icon")); + if (sectionMeta.contains("Profile")) { + qc.setProfile(sectionMeta.get("Profile")); } // Deck From 78a582797601ca0ec18ac51e663095ffb26996a1 Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Fri, 22 Nov 2019 08:17:45 +0100 Subject: [PATCH 17/21] Fixed some code indentations --- forge-gui/src/main/java/forge/match/AbstractGuiGame.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index a3b6b8f6e47..d155759e9c7 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -253,16 +253,16 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { private final Set selectableCards = Sets.newHashSet(); public void setSelectables(final Iterable cards) { - for ( CardView cv : cards ) { selectableCards.add(cv); } + for ( CardView cv : cards ) { selectableCards.add(cv); } } public void clearSelectables() { - selectableCards.clear(); + selectableCards.clear(); } public boolean isSelectable(final CardView card) { - return selectableCards.contains(card); + return selectableCards.contains(card); } public boolean isSelecting() { - return !selectableCards.isEmpty(); + return !selectableCards.isEmpty(); } public boolean isGamePaused() { return gamePause; } public void setgamePause(boolean pause) { gamePause = pause; } From 831edab34b903c4c98fdd3f33486dc94ed7a781b Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 23 Nov 2019 01:25:30 +0000 Subject: [PATCH 18/21] Syr Konrad, the Grim: The Leave the Graveyard Trigger is only for your Graveyard --- forge-gui/res/cardsfolder/s/syr_konrad_the_grim.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/s/syr_konrad_the_grim.txt b/forge-gui/res/cardsfolder/s/syr_konrad_the_grim.txt index 3d7b61f5af9..eb62eaeb821 100644 --- a/forge-gui/res/cardsfolder/s/syr_konrad_the_grim.txt +++ b/forge-gui/res/cardsfolder/s/syr_konrad_the_grim.txt @@ -3,7 +3,7 @@ ManaCost:3 B B Types:Legendary Creature Human Knight PT:5/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature.Other | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever another creature dies, or a creature card is put into a graveyard from anywhere than the battlefield, or a creature card leaves your graveyard, CARDNAME deals 1 damage to each opponent. -T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.Creature+Other | TriggerZones$ Battlefield | Execute$ TrigDmg | Secondary$ True | TriggerDescription$ Whenever another creature dies, or a creature card is put into a graveyard from anywhere than the battlefield, or a creature card leaves your graveyard, CARDNAME deals 1 damage to each opponent. +T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.Creature+Other+YouOwn | TriggerZones$ Battlefield | Execute$ TrigDmg | Secondary$ True | TriggerDescription$ Whenever another creature dies, or a creature card is put into a graveyard from anywhere than the battlefield, or a creature card leaves your graveyard, CARDNAME deals 1 damage to each opponent. SVar:TrigDmg:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1 A:AB$ Mill | Cost$ 1 B | NumCards$ 1 | Defined$ Player | SpellDescription$ Each player puts the top card of their library into their graveyard. AI:RemoveDeck:All From a9d9c800b8a4600874ea3b52a891813abd4fe750 Mon Sep 17 00:00:00 2001 From: Chris H Date: Fri, 22 Nov 2019 21:02:02 -0500 Subject: [PATCH 19/21] Fix start crash --- .../toolbox/special/PlayerDetailsPanel.java | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java index 59afc0532b9..4a37d331223 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java @@ -1,31 +1,24 @@ package forge.toolbox.special; -import java.awt.Component; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JPanel; -import javax.swing.SwingConstants; - -import forge.card.mana.ManaAtom; -import forge.trackable.TrackableProperty; -import net.miginfocom.swing.MigLayout; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; - import com.google.common.base.Function; - import forge.assets.FSkinProp; +import forge.card.mana.ManaAtom; import forge.game.player.PlayerView; import forge.toolbox.FLabel; import forge.toolbox.FMouseAdapter; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinFont; import forge.toolbox.FSkin.SkinnedPanel; +import forge.trackable.TrackableProperty; +import net.miginfocom.swing.MigLayout; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; public class PlayerDetailsPanel extends JPanel { private static final long serialVersionUID = -6531759554646891983L; @@ -213,7 +206,7 @@ public class PlayerDetailsPanel extends JPanel { } public void setToolTip(final String... args) { - super.setToolTipText(String.format(tooltip, (Object) args)); + super.setToolTipText(String.format(tooltip, args)); } @Override From f3ff4194620e3e79267eed884f930394c0939109 Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Sat, 23 Nov 2019 12:05:08 +0100 Subject: [PATCH 20/21] Changes for the stack effect notifications - request by Michael Kamensky @Agetian in merge request https://git.cardforge.org/core-developers/forge/merge_requests/2294 --- .../home/settings/CSubmenuPreferences.java | 12 +- .../home/settings/VSubmenuPreferences.java | 14 +- .../java/forge/screens/match/CMatchUI.java | 176 +++++++++--------- .../java/forge/properties/ForgeConstants.java | 5 + .../forge/properties/ForgePreferences.java | 2 +- 5 files changed, 114 insertions(+), 95 deletions(-) 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 28bfad75dbc..96080f2a500 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 @@ -132,7 +132,6 @@ public enum CSubmenuPreferences implements ICDoc { lstControls.add(Pair.of(view.getCbTokensInSeparateRow(), FPref.UI_TOKENS_IN_SEPARATE_ROW)); lstControls.add(Pair.of(view.getCbStackCreatures(), FPref.UI_STACK_CREATURES)); lstControls.add(Pair.of(view.getCbManaLostPrompt(), FPref.UI_MANA_LOST_PROMPT)); - lstControls.add(Pair.of(view.getCbStackAdditionPrompt(), FPref.UI_STACK_ADDITION_PROMPT)); lstControls.add(Pair.of(view.getCbEscapeEndsTurn(), FPref.UI_ALLOW_ESC_TO_END_TURN)); lstControls.add(Pair.of(view.getCbDetailedPaymentDesc(), FPref.UI_DETAILED_SPELLDESC_IN_PROMPT)); lstControls.add(Pair.of(view.getCbPreselectPrevAbOrder(), FPref.UI_PRESELECT_PREVIOUS_ABILITY_ORDER)); @@ -228,6 +227,7 @@ public enum CSubmenuPreferences implements ICDoc { initializeDefaultFontSizeComboBox(); initializeMulliganRuleComboBox(); initializeAiProfilesComboBox(); + initializeStackAdditionsComboBox(); initializeColorIdentityCombobox(); initializeAutoYieldModeComboBox(); initializeCounterDisplayTypeComboBox(); @@ -409,6 +409,16 @@ public enum CSubmenuPreferences implements ICDoc { panel.setComboBox(comboBox, selectedItem); } + private void initializeStackAdditionsComboBox() { + final String[] elems = {ForgeConstants.STACK_EFFECT_NOTIFICATION_NEVER, ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS, + ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED}; + final FPref userSetting = FPref.UI_STACK_EFFECT_NOTIFICATION_POLICY; + final FComboBoxPanel panel = this.view.getCbpStackAdditionsComboBoxPanel(); + final FComboBox comboBox = createComboBox(elems, userSetting); + final String selectedItem = this.prefs.getPref(userSetting); + panel.setComboBox(comboBox, selectedItem); + } + private void initializeColorIdentityCombobox() { final String[] elems = {ForgeConstants.DISP_CURRENT_COLORS_NEVER, ForgeConstants.DISP_CURRENT_COLORS_CHANGED, ForgeConstants.DISP_CURRENT_COLORS_MULTICOLOR, ForgeConstants.DISP_CURRENT_COLORS_MULTI_OR_CHANGED, 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 6c5f4be9cf0..e5d6c8658cf 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 @@ -70,7 +70,6 @@ public enum VSubmenuPreferences implements IVSubmenu { private final JCheckBox cbEnableAICheats = new OptionsCheckBox(localizer.getMessage("cbEnableAICheats")); private final JCheckBox cbManaBurn = new OptionsCheckBox(localizer.getMessage("cbManaBurn")); private final JCheckBox cbManaLostPrompt = new OptionsCheckBox(localizer.getMessage("cbManaLostPrompt")); - private final JCheckBox cbStackAdditionPrompt = new OptionsCheckBox(localizer.getMessage("cbStackAdditionPrompt")); private final JCheckBox cbDevMode = new OptionsCheckBox(localizer.getMessage("cbDevMode")); private final JCheckBox cbLoadCardsLazily = new OptionsCheckBox(localizer.getMessage("cbLoadCardsLazily")); private final JCheckBox cbLoadHistoricFormats = new OptionsCheckBox(localizer.getMessage("cbLoadHistoricFormats")); @@ -117,6 +116,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final FComboBoxPanel cbpDefaultFontSize = new FComboBoxPanel<>(localizer.getMessage("cbpDefaultFontSize")+":"); private final FComboBoxPanel cbpMulliganRule = new FComboBoxPanel<>(localizer.getMessage("cbpMulliganRule")+":"); private final FComboBoxPanel cbpAiProfiles = new FComboBoxPanel<>(localizer.getMessage("cbpAiProfiles")+":"); + private final FComboBoxPanel cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":"); private final FComboBoxPanel cbpDisplayCurrentCardColors = new FComboBoxPanel<>(localizer.getMessage("cbpDisplayCurrentCardColors")+":"); private final FComboBoxPanel cbpAutoYieldMode = new FComboBoxPanel<>(localizer.getMessage("cbpAutoYieldMode")+":"); private final FComboBoxPanel cbpCounterDisplayType = new FComboBoxPanel<>(localizer.getMessage("cbpCounterDisplayType")+":"); @@ -195,8 +195,8 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbManaLostPrompt, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlManaLostPrompt")), descriptionConstraints); - pnlPrefs.add(cbStackAdditionPrompt, titleConstraints); - pnlPrefs.add(new NoteLabel(localizer.getMessage("nlStackAdditionPrompt")), descriptionConstraints); + pnlPrefs.add(cbpStackAdditions, comboBoxConstraints); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpStackAdditions")), descriptionConstraints); pnlPrefs.add(cbEnforceDeckLegality, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnforceDeckLegality")), descriptionConstraints); @@ -653,6 +653,10 @@ public enum VSubmenuPreferences implements IVSubmenu { return cbpAiProfiles; } + public FComboBoxPanel getCbpStackAdditionsComboBoxPanel() { + return cbpStackAdditions; + } + public FComboBoxPanel getGameLogVerbosityComboBoxPanel() { return cbpGameLogEntryType; } @@ -770,10 +774,6 @@ public enum VSubmenuPreferences implements IVSubmenu { public final JCheckBox getCbManaLostPrompt() { return cbManaLostPrompt; } - - public final JCheckBox getCbStackAdditionPrompt() { - return cbStackAdditionPrompt; - } public final JCheckBox getCbDetailedPaymentDesc() { return cbDetailedPaymentDesc; 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 bf5ea0d7a8a..5619e695d94 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 @@ -96,6 +96,7 @@ import forge.match.AbstractGuiGame; import forge.menus.IMenuProvider; import forge.model.FModel; import forge.player.PlayerZoneUpdate; +import forge.properties.ForgeConstants; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.match.controllers.CAntes; @@ -1210,93 +1211,97 @@ public final class CMatchUI } @Override - public void notifyStackAddition(GameEventSpellAbilityCast event) { - if(FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_ADDITION_PROMPT)) { - int stackIndex = event.stackIndex; - if(stackIndex == nextNotifiableStackIndex) { - - // We can go and show the modal - SpellAbility sa = event.sa; - SpellAbilityStackInstance si = event.si; - - MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill"); - JPanel mainPanel = new JPanel(migLayout); - final Dimension parentSize = JOptionPane.getRootFrame().getSize(); - Dimension maxSize = new Dimension(1400, parentSize.height - 100); - mainPanel.setMaximumSize(maxSize); - mainPanel.setOpaque(false); - - // Big Image - addBigImageToStackModalPanel(mainPanel, si); - - // Text - addTextToStackModalPanel(mainPanel,sa,si); - - // Small images - int numSmallImages = 0; - - // If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card - GameEntityView enchantedEntityView = null; - Card hostCard = sa.getHostCard(); - if(hostCard.isEnchantment()) { - GameEntity enchantedEntity = hostCard.getEntityAttachedTo(); - if(enchantedEntity != null) { - enchantedEntityView = enchantedEntity.getView(); - numSmallImages++; - } else if ((sa.getRootAbility() != null) - && (sa.getRootAbility().getPaidList("Sacrificed") != null) - && !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) { - // If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace" - enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard(); - if(enchantedEntity != null) { - enchantedEntityView = enchantedEntity.getView(); - numSmallImages++; - } - } - } - - // If current effect is a triggered ability, I want to show the triggering card if present - SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA); - CardView sourceCardView = null; - if(sourceSA != null) { - sourceCardView = sourceSA.getHostCard().getView(); + public void notifyStackAddition(GameEventSpellAbilityCast event) { + SpellAbility sa = event.sa; + String stackNotificationPolicy = FModel.getPreferences().getPref(FPref.UI_STACK_EFFECT_NOTIFICATION_POLICY); + boolean isAi = sa.getActivatingPlayer().isAI(); + boolean isTrigger = sa.isTrigger(); + int stackIndex = event.stackIndex; + if(stackIndex == nextNotifiableStackIndex) { + if(ForgeConstants.STACK_EFFECT_NOTIFICATION_ALWAYS.equals(stackNotificationPolicy) || (ForgeConstants.STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED.equals(stackNotificationPolicy) && (isAi || isTrigger))) { + // We can go and show the modal + SpellAbilityStackInstance si = event.si; + + MigLayout migLayout = new MigLayout("insets 15, left, gap 30, fill"); + JPanel mainPanel = new JPanel(migLayout); + final Dimension parentSize = JOptionPane.getRootFrame().getSize(); + Dimension maxSize = new Dimension(1400, parentSize.height - 100); + mainPanel.setMaximumSize(maxSize); + mainPanel.setOpaque(false); + + // Big Image + addBigImageToStackModalPanel(mainPanel, si); + + // Text + addTextToStackModalPanel(mainPanel,sa,si); + + // Small images + int numSmallImages = 0; + + // If current effect is a triggered/activated ability of an enchantment card, I want to show the enchanted card + GameEntityView enchantedEntityView = null; + Card hostCard = sa.getHostCard(); + if(hostCard.isEnchantment()) { + GameEntity enchantedEntity = hostCard.getEntityAttachedTo(); + if(enchantedEntity != null) { + enchantedEntityView = enchantedEntity.getView(); numSmallImages++; - } - - // I also want to show each type of targets (both cards and players) - List targets = getTargets(si,new ArrayList()); - numSmallImages = numSmallImages + targets.size(); - - // Now I know how many small images - on to render them - if(enchantedEntityView != null) { - addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages); - } - if(sourceCardView != null) { - addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages); - } - for(GameEntityView gev : targets) { - addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages); - } - - FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of("OK")); - // here the user closed the modal - time to update the next notifiable stack index - nextNotifiableStackIndex++; - - } else { - - // Not yet time to show the modal - schedule the method again, and try again later - Runnable tryAgainThread = new Runnable() { - @Override - public void run() { - notifyStackAddition(event); + } else if ((sa.getRootAbility() != null) + && (sa.getRootAbility().getPaidList("Sacrificed") != null) + && !sa.getRootAbility().getPaidList("Sacrificed").isEmpty()) { + // If the player activated its ability by sacrificing the enchantment, the enchantment has not anything attached anymore and the ex-enchanted card has to be searched in other ways.. for example, the green enchantment "Carapace" + enchantedEntity = sa.getRootAbility().getPaidList("Sacrificed").get(0).getEnchantingCard(); + if(enchantedEntity != null) { + enchantedEntityView = enchantedEntity.getView(); + numSmallImages++; } - }; - GuiBase.getInterface().invokeInEdtLater(tryAgainThread); + } + } - } - } + // If current effect is a triggered ability, I want to show the triggering card if present + SpellAbility sourceSA = (SpellAbility) si.getTriggeringObject(AbilityKey.SourceSA); + CardView sourceCardView = null; + if(sourceSA != null) { + sourceCardView = sourceSA.getHostCard().getView(); + numSmallImages++; + } + + // I also want to show each type of targets (both cards and players) + List targets = getTargets(si,new ArrayList()); + numSmallImages = numSmallImages + targets.size(); + + // Now I know how many small images - on to render them + if(enchantedEntityView != null) { + addSmallImageToStackModalPanel(enchantedEntityView,mainPanel,numSmallImages); + } + if(sourceCardView != null) { + addSmallImageToStackModalPanel(sourceCardView,mainPanel,numSmallImages); + } + for(GameEntityView gev : targets) { + addSmallImageToStackModalPanel(gev, mainPanel, numSmallImages); + } + + FOptionPane.showOptionDialog(null, "Forge", null, mainPanel, ImmutableList.of("OK")); + // here the user closed the modal - time to update the next notifiable stack index + + } + // In any case, I have to increase the counter + nextNotifiableStackIndex++; + + } else { + + // Not yet time to show the modal - schedule the method again, and try again later + Runnable tryAgainThread = new Runnable() { + @Override + public void run() { + notifyStackAddition(event); + } + }; + GuiBase.getInterface().invokeInEdtLater(tryAgainThread); + + } } - + private List getTargets(SpellAbilityStackInstance si, List result){ if(si == null) { return result; @@ -1401,9 +1406,8 @@ public final class CMatchUI @Override public void notifyStackRemoval(GameEventSpellRemovedFromStack event) { - if(FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_ADDITION_PROMPT)) { - nextNotifiableStackIndex--; - } + // I always decrease the counter + nextNotifiableStackIndex--; } } diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index 42d07add4e3..ec4be9400c4 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -310,6 +310,11 @@ public final class ForgeConstants { public static final String GRAVEYARD_ORDERING_OWN_CARDS = "With Relevant Cards"; public static final String GRAVEYARD_ORDERING_ALWAYS = "Always"; + // Constants for Stack effect addition notification policy + public static final String STACK_EFFECT_NOTIFICATION_NEVER = "Never"; + public static final String STACK_EFFECT_NOTIFICATION_ALWAYS = "Always"; + public static final String STACK_EFFECT_NOTIFICATION_AI_AND_TRIGGERED = "AI cast/activated, or triggered by any player"; + // Set boolean constant for landscape mode for gdx port public static final boolean isGdxPortLandscape = FileUtil.doesFileExist(ASSETS_DIR + "switch_orientation.ini"); diff --git a/forge-gui/src/main/java/forge/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/properties/ForgePreferences.java index b9ef0e5cc66..f9b7c3329a4 100644 --- a/forge-gui/src/main/java/forge/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/properties/ForgePreferences.java @@ -119,7 +119,7 @@ public class ForgePreferences extends PreferencesStore { UI_HIDE_GAME_TABS ("false"), // Visibility of tabs in match screen. UI_CLOSE_ACTION ("NONE"), UI_MANA_LOST_PROMPT ("false"), // Prompt on losing mana when passing priority - UI_STACK_ADDITION_PROMPT ("false"), + UI_STACK_EFFECT_NOTIFICATION_POLICY ("Never"), UI_PAUSE_WHILE_MINIMIZED("false"), UI_TOKENS_IN_SEPARATE_ROW("false"), // Display tokens in their own battlefield row. UI_DISPLAY_CURRENT_COLORS(ForgeConstants.DISP_CURRENT_COLORS_NEVER), From b99dea604d3b7586908842ec9fcd572cc2b6dc9a Mon Sep 17 00:00:00 2001 From: Alessandro Coli Date: Sat, 23 Nov 2019 12:07:36 +0100 Subject: [PATCH 21/21] Labels for changes to stack effect notification panel --- forge-gui/res/languages/en-US.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 52d0625e078..b80a6055556 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -61,7 +61,6 @@ cbAnteMatchRarity=Match Ante Rarity cbEnableAICheats=Allow AI Cheating cbManaBurn=Mana Burn cbManaLostPrompt=Prompt Mana Pool Emptying -cbStackAdditionPrompt=Prompt Stack Addition cbDevMode=Developer Mode cbLoadCardsLazily=Load Card Scripts Lazily cbLoadHistoricFormats=Load Historic Formats @@ -103,6 +102,7 @@ cbpGameLogEntryType=Game Log Verbosity cbpCloseAction=Close Action cbpDefaultFontSize=Default Font Size cbpAiProfiles=AI Personality +cbpStackAdditions=Stack effect notifications cbpDisplayCurrentCardColors=Show Detailed Card Color cbpAutoYieldMode=Auto-Yield cbpCounterDisplayType=Counter Display Type @@ -117,12 +117,12 @@ nlUseSentry=When enabled, automatically submits bug reports to developers. GamePlay=Gameplay nlpMulliganRule=Choose the version of the Mulligan rule nlpAiProfiles=Choose your AI opponent +nlpStackAdditions=Choose when you want to get visual notifications for an effect added to the stack: Never, always, or only for the effects cast/activated by a AI player or triggered by any player nlAnte=Determines whether or not the game is played for ante. nlAnteMatchRarity=Attempts to make antes the same rarity for all players. nlEnableAICheats=Allow the AI to cheat to gain advantage (for personalities that have cheat shuffling options set). nlManaBurn=Play with mana burn (from pre-Magic 2010 rules). nlManaLostPrompt=When enabled, you get a warning if passing priority would cause you to lose mana in your mana pool. -nlStackAdditionPrompt=When enabled, you get a notification popup each time a new effect is added to the stack nlEnforceDeckLegality=Enforces deck legality relevant to each environment (minimum deck sizes, max card count etc). nlSideboardForAI=Allows users to sideboard with the AIs deck and sideboard in constructed game formats. nlPerformanceMode=Disables additional static abilities checks to speed up the game engine. (Warning: breaks some 'as if had flash' scenarios when casting cards owned by opponents).