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 4ef999e53c8..38bc51e2348 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 @@ -106,6 +106,7 @@ public enum CSubmenuPreferences implements ICDoc { lstControls.add(Pair.of(view.getCbAltSoundSystem(), FPref.UI_ALT_SOUND_SYSTEM)); lstControls.add(Pair.of(view.getCbUiForTouchScreen(), FPref.UI_FOR_TOUCHSCREN)); lstControls.add(Pair.of(view.getCbTimedTargOverlay(), FPref.UI_TIMED_TARGETING_OVERLAY_UPDATES)); + lstControls.add(Pair.of(view.getCbTargOverlayDarkArrows(), FPref.UI_TARGETING_DARKER_PW_ARROWS)); lstControls.add(Pair.of(view.getCbCompactMainMenu(), FPref.UI_COMPACT_MAIN_MENU)); lstControls.add(Pair.of(view.getCbPromptFreeBlocks(), FPref.MATCHPREF_PROMPT_FREE_BLOCKS)); lstControls.add(Pair.of(view.getCbPauseWhileMinimized(), FPref.UI_PAUSE_WHILE_MINIMIZED)); 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 eafffe9b29e..062de320e11 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 @@ -80,6 +80,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final JCheckBox cbAltSoundSystem = new OptionsCheckBox("Use Alternate Sound System"); private final JCheckBox cbUiForTouchScreen = new OptionsCheckBox("Enhance UI for Touchscreens"); private final JCheckBox cbTimedTargOverlay = new OptionsCheckBox("Enable Targeting Overlay Optimization"); + private final JCheckBox cbTargOverlayDarkArrows = new OptionsCheckBox("Darker Arrows for Planeswalker Attackers"); private final JCheckBox cbCompactMainMenu = new OptionsCheckBox("Use Compact Main Sidebar Menu"); private final JCheckBox cbDetailedPaymentDesc = new OptionsCheckBox("Spell Description in Payment Prompt"); private final JCheckBox cbPromptFreeBlocks = new OptionsCheckBox("Free Block Handling"); @@ -276,7 +277,10 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(new NoteLabel("Stacks identical creatures on the battlefield like lands, artifacts, and enchantments."), descriptionConstraints); pnlPrefs.add(cbTimedTargOverlay, titleConstraints); - pnlPrefs.add(new NoteLabel("Enables throttling-based optimization of targeting overlay to reduce CPU use (only disable if you experience choppiness on older hardware, requires starting a new match)"), descriptionConstraints); + pnlPrefs.add(new NoteLabel("Enables throttling-based optimization of targeting overlay to reduce CPU use (only disable if you experience choppiness on older hardware, requires starting a new match)."), descriptionConstraints); + + pnlPrefs.add(cbTargOverlayDarkArrows, titleConstraints); + pnlPrefs.add(new NoteLabel("Makes the targeting overlay arrows darker for creatures attacking planeswalkers, to make those arrows easier to distinguish from the blocker arrows."), descriptionConstraints); pnlPrefs.add(cbpCounterDisplayType, comboBoxConstraints); pnlPrefs.add(new NoteLabel("Selects the style of the in-game counter display for cards. Text-based is a new tab-like display on the cards. Image-based is the old counter image. Hybrid displays both at once."), descriptionConstraints); @@ -640,6 +644,11 @@ public enum VSubmenuPreferences implements IVSubmenu { return cbTimedTargOverlay; } + /** @return {@link javax.swing.JCheckBox} */ + public JCheckBox getCbTargOverlayDarkArrows() { + return cbTargOverlayDarkArrows; + } + public final JCheckBox getCbUiForTouchScreen() { return cbUiForTouchScreen; } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/TargetingOverlay.java b/forge-gui-desktop/src/main/java/forge/screens/match/TargetingOverlay.java index a1bfcc291bd..b52da1b0650 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/TargetingOverlay.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/TargetingOverlay.java @@ -64,7 +64,8 @@ public class TargetingOverlay { private final CMatchUI matchUI; private final OverlayPanel pnl = new OverlayPanel(); private final List cardPanels = new ArrayList(); - private final List arcsFoe = new ArrayList(); + private final List arcsFoeAtk = new ArrayList(); + private final List arcsFoeDef = new ArrayList(); private final List arcsFriend = new ArrayList(); private final ArcAssembler assembler = new ArcAssembler(); private final Set stackItemIDs = new HashSet(); @@ -87,6 +88,14 @@ public class TargetingOverlay { private int allowedUpdates = 0; private final int MAX_CONSECUTIVE_UPDATES = 1; + private enum ArcConnection { + Friends, + FoesAttacking, + FoesBlocking, + FriendsStackTargeting, + FoesStackTargeting + } + /** * Semi-transparent overlay panel. Should be used with layered panes. */ @@ -107,7 +116,8 @@ public class TargetingOverlay { // Re-added as the new version was causing issues for at least one user. private void assembleArcs(final CombatView combat) { //List fields = VMatchUI.SINGLETON_INSTANCE.getFieldViews(); - arcsFoe.clear(); + arcsFoeAtk.clear(); + arcsFoeDef.clear(); arcsFriend.clear(); cardPanels.clear(); cardsVisualized.clear(); @@ -183,12 +193,14 @@ public class TargetingOverlay { PlayerView activator = instance.getActivatingPlayer(); while (instance != null) { for (CardView c : instance.getTargetCards()) { - addArc(endpoints.get(c.getId()), itemLocOnScreen, activator.isOpponentOf(c.getController())); + addArc(endpoints.get(c.getId()), itemLocOnScreen, activator.isOpponentOf(c.getController()) ? + ArcConnection.FoesStackTargeting : ArcConnection.FriendsStackTargeting); } for (PlayerView p : instance.getTargetPlayers()) { Point point = getPlayerTargetingArrowPoint(p, locOnScreen); if(point != null) { - addArc(point, itemLocOnScreen, activator.isOpponentOf(p)); + addArc(point, itemLocOnScreen, activator.isOpponentOf(p) ? + ArcConnection.FoesStackTargeting : ArcConnection.FriendsStackTargeting); } } instance = instance.getSubInstance(); @@ -232,7 +244,8 @@ public class TargetingOverlay { } } //List fields = VMatchUI.SINGLETON_INSTANCE.getFieldViews(); - arcsFoe.clear(); + arcsFoeAtk.clear(); + arcsFoeDef.clear(); arcsFriend.clear(); cardPanels.clear(); cardsVisualized.clear(); @@ -339,12 +352,14 @@ public class TargetingOverlay { PlayerView activator = instance.getActivatingPlayer(); while (instance != null) { for (CardView c : instance.getTargetCards()) { - addArc(endpoints.get(c.getId()), itemLocOnScreen, activator.isOpponentOf(c.getController())); + addArc(endpoints.get(c.getId()), itemLocOnScreen, activator.isOpponentOf(c.getController()) ? + ArcConnection.FoesStackTargeting : ArcConnection.FriendsStackTargeting); } for (PlayerView p : instance.getTargetPlayers()) { Point point = getPlayerTargetingArrowPoint(p, locOnScreen); if (point != null) { - addArc(point, itemLocOnScreen, activator.isOpponentOf(p)); + addArc(point, itemLocOnScreen, activator.isOpponentOf(p) ? + ArcConnection.FoesStackTargeting : ArcConnection.FriendsStackTargeting); } } instance = instance.getSubInstance(); @@ -365,16 +380,22 @@ public class TargetingOverlay { return point; } - private void addArc(Point end, Point start, boolean connectsFoes) { + private void addArc(Point end, Point start, ArcConnection connects) { if (start == null || end == null) { return; } - if (connectsFoes) { - arcsFoe.add(new Arc(end, start)); - } - else { - arcsFriend.add(new Arc(end, start)); + switch (connects) { + case Friends: + case FriendsStackTargeting: + arcsFriend.add(new Arc(end, start)); + break; + case FoesAttacking: + arcsFoeAtk.add(new Arc(end, start)); + break; + case FoesBlocking: + case FoesStackTargeting: + arcsFoeDef.add(new Arc(end, start)); } } @@ -393,26 +414,26 @@ public class TargetingOverlay { if (null != enchanting) { if (enchanting.getController() != null && !enchanting.getController().equals(c.getController())) { - addArc(endpoints.get(enchanting.getId()), endpoints.get(c.getId()), false); + addArc(endpoints.get(enchanting.getId()), endpoints.get(c.getId()), ArcConnection.Friends); cardsVisualized.add(enchanting); } } if (null != equipping) { if (equipping.getController() != null && !equipping.getController().equals(c.getController())) { - addArc(endpoints.get(equipping.getId()), endpoints.get(c.getId()), false); + addArc(endpoints.get(equipping.getId()), endpoints.get(c.getId()), ArcConnection.Friends); cardsVisualized.add(equipping); } } if (null != fortifying) { if (fortifying.getController() != null && !fortifying.getController().equals(c.getController())) { - addArc(endpoints.get(fortifying.getId()), endpoints.get(c.getId()), false); + addArc(endpoints.get(fortifying.getId()), endpoints.get(c.getId()), ArcConnection.Friends); cardsVisualized.add(fortifying); } } if (null != enchantedBy) { for (final CardView enc : enchantedBy) { if (enc.getController() != null && !enc.getController().equals(c.getController())) { - addArc(endpoints.get(c.getId()), endpoints.get(enc.getId()), false); + addArc(endpoints.get(c.getId()), endpoints.get(enc.getId()), ArcConnection.Friends); cardsVisualized.add(enc); } } @@ -420,7 +441,7 @@ public class TargetingOverlay { if (null != equippedBy) { for (final CardView eq : equippedBy) { if (eq.getController() != null && !eq.getController().equals(c.getController())) { - addArc(endpoints.get(c.getId()), endpoints.get(eq.getId()), false); + addArc(endpoints.get(c.getId()), endpoints.get(eq.getId()), ArcConnection.Friends); cardsVisualized.add(eq); } } @@ -428,31 +449,31 @@ public class TargetingOverlay { if (null != fortifiedBy) { for (final CardView eq : fortifiedBy) { if (eq.getController() != null && !eq.getController().equals(c.getController())) { - addArc(endpoints.get(c.getId()), endpoints.get(eq.getId()), false); + addArc(endpoints.get(c.getId()), endpoints.get(eq.getId()), ArcConnection.Friends); cardsVisualized.add(eq); } } } if (null != paired) { - addArc(endpoints.get(paired.getId()), endpoints.get(c.getId()), false); + addArc(endpoints.get(paired.getId()), endpoints.get(c.getId()), ArcConnection.Friends); cardsVisualized.add(paired); } if (null != combat) { final GameEntityView defender = combat.getDefender(c); // if c is attacking a planeswalker if (defender instanceof CardView) { - addArc(endpoints.get(defender.getId()), endpoints.get(c.getId()), true); + addArc(endpoints.get(defender.getId()), endpoints.get(c.getId()), ArcConnection.FoesAttacking); } // if c is a planeswalker that's being attacked for (final CardView pwAttacker : combat.getAttackersOf(c)) { - addArc(endpoints.get(c.getId()), endpoints.get(pwAttacker.getId()), true); + addArc(endpoints.get(c.getId()), endpoints.get(pwAttacker.getId()), ArcConnection.FoesAttacking); } for (final CardView attackingCard : combat.getAttackers()) { final Iterable cards = combat.getPlannedBlockers(attackingCard); if (cards == null) continue; for (final CardView blockingCard : cards) { if (!attackingCard.equals(c) && !blockingCard.equals(c)) { continue; } - addArc(endpoints.get(attackingCard.getId()), endpoints.get(blockingCard.getId()), true); + addArc(endpoints.get(attackingCard.getId()), endpoints.get(blockingCard.getId()), ArcConnection.FoesBlocking); cardsVisualized.add(blockingCard); } cardsVisualized.add(attackingCard); @@ -552,6 +573,7 @@ public class TargetingOverlay { super.paintComponent(g); final ArcState overlaystate = matchUI.getCDock().getArcState(); + final boolean darkerPWArrows = FModel.getPreferences().getPrefBoolean(FPref.UI_TARGETING_DARKER_PW_ARROWS); // Arcs are off if (overlaystate == ArcState.OFF) { return; } @@ -568,7 +590,7 @@ public class TargetingOverlay { } } - if (arcsFoe.isEmpty() && arcsFriend.isEmpty()) { + if (arcsFoeAtk.isEmpty() && arcsFoeDef.isEmpty() && arcsFriend.isEmpty()) { if (assembled) { // We still need to repaint to get rid of visual artifacts // The original (non-throttled) code did not do this repaint. @@ -589,9 +611,12 @@ public class TargetingOverlay { if (colorCombat.getAlpha() == 0) { colorCombat = new Color(255, 0, 0, 153); } + // For planeswalker attackers, use a somewhat darker shade if the player opts in + Color colorCombatAtk = darkerPWArrows ? colorCombat.darker() : colorCombat; drawArcs(g2d, colorOther, arcsFriend); - drawArcs(g2d, colorCombat, arcsFoe); + drawArcs(g2d, colorCombatAtk, arcsFoeAtk); + drawArcs(g2d, colorCombat, arcsFoeDef); if (assembled || !useThrottling) { FView.SINGLETON_INSTANCE.getFrame().repaint(); // repaint the match UI diff --git a/forge-gui-mobile/src/forge/screens/match/TargetingOverlay.java b/forge-gui-mobile/src/forge/screens/match/TargetingOverlay.java index 07517845378..5792072c219 100644 --- a/forge-gui-mobile/src/forge/screens/match/TargetingOverlay.java +++ b/forge-gui-mobile/src/forge/screens/match/TargetingOverlay.java @@ -22,6 +22,8 @@ import forge.assets.FSkinColor; import forge.assets.FSkinColor.Colors; import forge.game.card.CardView; import forge.game.player.PlayerView; +import forge.model.FModel; +import forge.properties.ForgePreferences; import forge.screens.match.views.VCardDisplayArea.CardAreaPanel; import forge.toolbox.FDisplayObject; import forge.util.Utils; @@ -33,47 +35,84 @@ public class TargetingOverlay { private static final float BORDER_THICKNESS = Utils.scale(1); private static final float ARROW_THICKNESS = Utils.scale(5); private static final float ARROW_SIZE = 3 * ARROW_THICKNESS; - private static FSkinColor friendColor, foeColor; + private static FSkinColor friendColor, foeAtkColor, foeDefColor; + + public enum ArcConnection { + Friends, + FoesAttacking, + FoesBlocking, + FriendsStackTargeting, + FoesStackTargeting + } public static void updateColors() { + final boolean darkerPWArrows = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_TARGETING_DARKER_PW_ARROWS); + friendColor = FSkinColor.get(Colors.CLR_NORMAL_TARGETING_ARROW); if (friendColor.getAlpha() == 0) { friendColor = FSkinColor.get(Colors.CLR_ACTIVE).alphaColor(153f / 255f); } - foeColor = FSkinColor.get(Colors.CLR_COMBAT_TARGETING_ARROW); - if (foeColor.getAlpha() == 0) { - foeColor = FSkinColor.getStandardColor(new Color(1, 0, 0, 153 / 255f)); + foeDefColor = FSkinColor.get(Colors.CLR_COMBAT_TARGETING_ARROW); + if (foeDefColor.getAlpha() == 0) { + foeDefColor = FSkinColor.getStandardColor(new Color(1, 0, 0, 153 / 255f)); } + + foeAtkColor = darkerPWArrows ? foeDefColor.darker().stepColor(-60) : foeDefColor; } private TargetingOverlay() { } public static void drawArrow(Graphics g, CardView startCard, CardView endCard) { + ArcConnection connects; + if (startCard.getOwner().isOpponentOf((endCard.getOwner()))) { + if (startCard.isAttacking()) { + connects = ArcConnection.FoesAttacking; + } else { + connects = ArcConnection.FoesBlocking; + } + } else { + connects = ArcConnection.Friends; + } + drawArrow(g, CardAreaPanel.get(startCard).getTargetingArrowOrigin(), CardAreaPanel.get(endCard).getTargetingArrowOrigin(), - startCard.getOwner().isOpponentOf(endCard.getOwner())); + connects); } - public static void drawArrow(Graphics g, Vector2 start, CardView targetCard, boolean connectsFoes) { + public static void drawArrow(Graphics g, Vector2 start, CardView targetCard, ArcConnection connects) { drawArrow(g, start, CardAreaPanel.get(targetCard).getTargetingArrowOrigin(), - connectsFoes); + connects); } - public static void drawArrow(Graphics g, Vector2 start, PlayerView targetPlayer, boolean connectsFoes) { + public static void drawArrow(Graphics g, Vector2 start, PlayerView targetPlayer, ArcConnection connects) { drawArrow(g, start, MatchController.getView().getPlayerPanel(targetPlayer).getAvatar().getTargetingArrowOrigin(), - connectsFoes); + connects); } - public static void drawArrow(Graphics g, FDisplayObject startCardDisplay, FDisplayObject endCardDisplay, boolean connectsFoes) { + public static void drawArrow(Graphics g, FDisplayObject startCardDisplay, FDisplayObject endCardDisplay, ArcConnection connects) { drawArrow(g, CardAreaPanel.getTargetingArrowOrigin(startCardDisplay, false), CardAreaPanel.getTargetingArrowOrigin(endCardDisplay, false), - connectsFoes); + connects); } - public static void drawArrow(Graphics g, Vector2 start, Vector2 end, boolean connectsFoes) { + public static void drawArrow(Graphics g, Vector2 start, Vector2 end, ArcConnection connects) { if (start == null || end == null) { return; } - FSkinColor color = connectsFoes ? foeColor : friendColor; + FSkinColor color = foeDefColor; + + switch (connects) { + case Friends: + case FriendsStackTargeting: + color = friendColor; + break; + case FoesAttacking: + color = foeAtkColor; + break; + case FoesBlocking: + case FoesStackTargeting: + color = foeDefColor; + } + g.drawArrow(BORDER_THICKNESS, ARROW_THICKNESS, ARROW_SIZE, color, start.x, start.y, end.x, end.y); } } diff --git a/forge-gui-mobile/src/forge/screens/match/views/VStack.java b/forge-gui-mobile/src/forge/screens/match/views/VStack.java index 9e14bd1679f..2b688b6581d 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VStack.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VStack.java @@ -213,10 +213,12 @@ public class VStack extends FDropDown { StackItemView instance = activeStackInstance; while (instance != null) { for (CardView c : instance.getTargetCards()) { - TargetingOverlay.drawArrow(g, arrowOrigin, c, activator.isOpponentOf(c.getController())); + TargetingOverlay.ArcConnection conn = activator.isOpponentOf(c.getController()) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting; + TargetingOverlay.drawArrow(g, arrowOrigin, c, conn); } for (PlayerView p : instance.getTargetPlayers()) { - TargetingOverlay.drawArrow(g, arrowOrigin, p, activator.isOpponentOf(p)); + TargetingOverlay.ArcConnection conn = activator.isOpponentOf(p) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting; + TargetingOverlay.drawArrow(g, arrowOrigin, p, conn); } instance = instance.getSubInstance(); } diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index a918bfb03ef..d6273243d07 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -139,6 +139,10 @@ public class SettingsPage extends TabPage { "Use Escape Key To End Turn", "Allows to use Esc keyboard shortcut to end turn prematurely"), 1); + lstSettings.addItem(new BooleanSetting(FPref.UI_TARGETING_DARKER_PW_ARROWS, + "Darker Arrows for Planeswalker Attackers", + "Makes targeting arrows darker for creatures attacking planeswalkers."), + 1); //Random Deck Generation lstSettings.addItem(new BooleanSetting(FPref.DECKGEN_NOSMALL, diff --git a/forge-gui/src/main/java/forge/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/properties/ForgePreferences.java index 1961c3acf05..eb18fdfda7d 100644 --- a/forge-gui/src/main/java/forge/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/properties/ForgePreferences.java @@ -72,6 +72,7 @@ public class ForgePreferences extends PreferencesStore { UI_PREFERRED_AVATARS_ONLY ("false"), UI_TARGETING_OVERLAY ("0"), UI_TIMED_TARGETING_OVERLAY_UPDATES ("true"), + UI_TARGETING_DARKER_PW_ARROWS ("true"), UI_ENABLE_SOUNDS ("true"), UI_ENABLE_MUSIC ("true"), UI_ALT_SOUND_SYSTEM ("false"),