diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index e1cd8b7da92..97dc8521d78 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -137,6 +137,23 @@ public class PlayerControllerAi extends PlayerController { return new HashMap<>(); } + @Override + public Map specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) { + Map result = new HashMap<>(); + for (int i = 0; i < manaAmount; ++i) { + Byte chosen = chooseColor("", sa, colorSet); + if (result.containsKey(chosen)) { + result.put(chosen, result.get(chosen) + 1); + } else { + result.put(chosen, 1); + } + if (different) { + colorSet = ColorSet.fromMask(colorSet.getColor() - chosen); + } + } + return result; + } + @Override public Integer announceRequirements(SpellAbility ability, String announce) { // For now, these "announcements" are made within the AI classes of the appropriate SA effects diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 73f1edd0db6..5e8e1df2f36 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -3,6 +3,7 @@ package forge.game.ability.effects; import static forge.util.TextUtil.toManaString; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -61,34 +62,54 @@ public class ManaEffect extends SpellAbilityEffect { String[] colorsNeeded = express.isEmpty() ? null : express.split(" "); boolean differentChoice = abMana.getOrigProduced().contains("Different"); ColorSet fullOptions = colorOptions; - for (int nMana = 0; nMana < amount; nMana++) { - String choice = ""; - if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible - colorOptions = ColorSet - .fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana])); - } - if (colorOptions.isColorless() && colorsProduced.length > 0) { - // If we just need generic mana, no reason to ask the controller for a choice, - // just use the first possible color. - choice = colorsProduced[differentChoice ? nMana : 0]; - } else { - byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, - differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions); - if (chosenColor == 0) - throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName()); - - if (differentChoice) { - fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor); + // Use specifyManaCombo if possible + if (colorsNeeded == null && amount > 1) { + Map choices = p.getController().specifyManaCombo(sa, colorOptions, amount, differentChoice); + for (Map.Entry e : choices.entrySet()) { + Byte chosenColor = e.getKey(); + String choice = MagicColor.toShortString(chosenColor); + Integer count = e.getValue(); + while (count > 0) { + if (choiceString.length() > 0) { + choiceString.append(" "); + } + choiceString.append(choice); + if (sa.hasParam("TwoEach")) { + choiceString.append(" ").append(choice); + } + --count; } - choice = MagicColor.toShortString(chosenColor); } + } else { + for (int nMana = 0; nMana < amount; nMana++) { + String choice = ""; + if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible + colorOptions = ColorSet + .fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana])); + } + if (colorOptions.isColorless() && colorsProduced.length > 0) { + // If we just need generic mana, no reason to ask the controller for a choice, + // just use the first possible color. + choice = colorsProduced[differentChoice ? nMana : 0]; + } else { + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, + differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions); + if (chosenColor == 0) + throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName()); - if (nMana > 0) { - choiceString.append(" "); - } - choiceString.append(choice); - if (sa.hasParam("TwoEach")) { - choiceString.append(" ").append(choice); + if (differentChoice) { + fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor); + } + choice = MagicColor.toShortString(chosenColor); + } + + if (nMana > 0) { + choiceString.append(" "); + } + choiceString.append(choice); + if (sa.hasParam("TwoEach")) { + choiceString.append(" ").append(choice); + } } } @@ -128,7 +149,7 @@ public class ManaEffect extends SpellAbilityEffect { if (type.equals("EnchantedManaCost")) { Card enchanted = card.getEnchantingCard(); - if (enchanted == null ) + if (enchanted == null ) continue; StringBuilder sb = new StringBuilder(); @@ -234,7 +255,7 @@ public class ManaEffect extends SpellAbilityEffect { * a {@link forge.card.spellability.AbilityMana} object. * @param af * a {@link forge.game.ability.AbilityFactory} object. - * + * * @return a {@link java.lang.String} object. */ diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 6a94b3e4ccb..a11be5b4fdd 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -48,9 +48,9 @@ import forge.item.PaperCard; import forge.util.ITriggerEvent; import forge.util.collect.FCollectionView; -/** +/** * A prototype for player controller class - * + * * Handles phase skips for now. */ public abstract class PlayerController { @@ -109,21 +109,22 @@ public abstract class PlayerController { public abstract Map assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder); public abstract Map divideShield(Card effectSource, Map affected, int shieldAmount); + public abstract Map specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different); public abstract Integer announceRequirements(SpellAbility ability, String announce); public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message); public abstract CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message); public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate filter, boolean optional); - public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body + public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body // Specify a target of a spell (Spellskite) public abstract Pair chooseTarget(SpellAbility sa, List> allTargets); - // Q: why is there min/max and optional at once? A: This is to handle cases like 'choose 3 to 5 cards or none at all' + // Q: why is there min/max and optional at once? A: This is to handle cases like 'choose 3 to 5 cards or none at all' public abstract CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional, Map params); - + public final T chooseSingleEntityForEffect(FCollectionView optionList, SpellAbility sa, String title, Map params) { return chooseSingleEntityForEffect(optionList, null, sa, title, false, null, params); } - public final T chooseSingleEntityForEffect(FCollectionView optionList, SpellAbility sa, String title, boolean isOptional, Map params) { return chooseSingleEntityForEffect(optionList, null, sa, title, isOptional, null, params); } + public final T chooseSingleEntityForEffect(FCollectionView optionList, SpellAbility sa, String title, boolean isOptional, Map params) { return chooseSingleEntityForEffect(optionList, null, sa, title, isOptional, null, params); } public abstract T chooseSingleEntityForEffect(FCollectionView optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player relatedPlayer, Map params); public abstract List chooseSpellAbilitiesForEffect(List spells, SpellAbility sa, String title, int num, Map params); @@ -209,7 +210,7 @@ public abstract class PlayerController { public final boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice) { return chooseBinary(sa, question, kindOfChoice, (Boolean) null); } public abstract boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultChioce); public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Map params) { return chooseBinary(sa, question, kindOfChoice); } - + public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call); public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List options, Map choiceMap); @@ -260,7 +261,7 @@ public abstract class PlayerController { public abstract String chooseCardName(SpellAbility sa, Predicate cpp, String valid, String message); public abstract String chooseCardName(SpellAbility sa, List faces, String message); - // better to have this odd method than those if playerType comparison in ChangeZone + // better to have this odd method than those if playerType comparison in ChangeZone public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider); public abstract List chooseCardsForZoneChange(ZoneType destination, List origin, SpellAbility sa, CardCollection fetchList, int min, int max, DelayedReveal delayedReveal, String selectPrompt, Player decider); 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 08e5e8497ec..e798031de07 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 @@ -1029,13 +1029,13 @@ public final class CMatchUI } @Override - public Map assignGenericAmount(final CardView effectSource, final Map target, + public Map assignGenericAmount(final CardView effectSource, final Map target, final int amount, final boolean atLeastOne, final String amountLabel) { if (amount <= 0) { return Collections.emptyMap(); } - final AtomicReference> result = new AtomicReference<>(); + final AtomicReference> result = new AtomicReference<>(); FThreads.invokeInEdtAndWait(new Runnable() { @Override public void run() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java index c628c8a89d9..6e140c7575c 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java @@ -34,10 +34,11 @@ import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.Border; -import forge.game.GameEntityView; +import forge.card.MagicColor; import forge.game.card.CardView; import forge.game.player.PlayerView; import forge.gui.SOverlayUtils; +import forge.localinstance.skin.FSkinProp; import forge.toolbox.FButton; import forge.toolbox.FLabel; import forge.toolbox.FScrollPane; @@ -52,11 +53,7 @@ import forge.view.arcane.MiscCardPanel; import net.miginfocom.swing.MigLayout; /** - * Assembles Swing components of assign damage dialog. - * - * This needs a JDialog to maintain a modal state. - * Without the modal state, the PhaseHandler automatically - * moves forward to phase Main2 without assigning damage. + * Assembles Swing components of assign generic amount dialog. * *

(V at beginning of class name denotes a view class.) */ @@ -80,12 +77,12 @@ public class VAssignGenericAmount { private final FButton btnReset = new FButton(localizer.getMessage("lblReset")); private static class AssignTarget { - public final GameEntityView entity; + public final Object entity; public final JLabel label; public final int max; public int amount; - public AssignTarget(final GameEntityView e, final JLabel lbl, int max0) { + public AssignTarget(final Object e, final JLabel lbl, int max0) { entity = e; label = lbl; max = max0; @@ -122,7 +119,7 @@ public class VAssignGenericAmount { } }; - public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { + public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { this.matchUI = matchUI; dlg.setTitle(localizer.getMessage("lbLAssignAmountForEffect", amountLabel, effectSource.toString())); @@ -158,7 +155,7 @@ public class VAssignGenericAmount { final FScrollPane scrTargets = new FScrollPane(pnlTargets, false); // Top row of cards... - for (final Map.Entry e : targets.entrySet()) { + for (final Map.Entry e : targets.entrySet()) { int maxAmount = e.getValue() != null ? e.getValue() : amount; final AssignTarget at = new AssignTarget(e.getKey(), new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build(), maxAmount); addPanelForTarget(pnlTargets, at); @@ -166,7 +163,11 @@ public class VAssignGenericAmount { // ... bottom row of labels. for (final AssignTarget l : targetsList) { - pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px"); + if (l.entity instanceof Byte) { + pnlTargets.add(l.label, "w 100px!, h 30px!, gap 5px 5px 0 5px"); + } else { + pnlTargets.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px"); + } } btnOK.addActionListener(new ActionListener() { @@ -217,6 +218,27 @@ public class VAssignGenericAmount { pnlTargets.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center"); mp.addMouseListener(mad); targetsMap.put(mp, at); + } else if (at.entity instanceof Byte) { + SkinImage manaSymbol; + Byte color = (Byte) at.entity; + if (color == MagicColor.WHITE) { + manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_W); + } else if (color == MagicColor.BLUE) { + manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_U); + } else if (color == MagicColor.BLACK) { + manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_B); + } else if (color == MagicColor.RED) { + manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_R); + } else if (color == MagicColor.GREEN) { + manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_G); + } else { // Should never come here, but add this to avoid compile error + manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_COLORLESS); + } + final MiscCardPanel mp = new MiscCardPanel(matchUI, "", manaSymbol); + mp.setCardBounds(0, 0, 70, 70); + pnlTargets.add(mp, "w 100px!, h 150px!, gap 5px 5px 3px 3px, ax center"); + mp.addMouseListener(mad); + targetsMap.put(mp, at); } targetsList.add(at); } @@ -313,8 +335,8 @@ public class VAssignGenericAmount { SOverlayUtils.hideOverlay(); } - public Map getAssignedMap() { - Map result = new HashMap<>(targetsList.size()); + public Map getAssignedMap() { + Map result = new HashMap<>(targetsList.size()); for (AssignTarget at : targetsList) result.put(at.entity, at.amount); return result; diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index f13bbf7c2ea..0bbd3db2aaf 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -80,7 +80,7 @@ import forge.util.collect.FCollectionView; /** * Default harmless implementation for tests. * Test-specific behaviour can easily be added by mocking (parts of) this class. - * + * * Note that the current PlayerController implementations seem to be responsible for handling some game logic, * and even aside from that, they are theoretically capable of making illegal choices (which are then not blocked by the real game logic). * Test cases that need to override the default behaviour of this class should make sure to do so in a way that does not invalidate their correctness. @@ -151,6 +151,10 @@ public class PlayerControllerForTests extends PlayerController { throw new IllegalStateException("Erring on the side of caution here..."); } + @Override + public Map specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) { + throw new IllegalStateException("Erring on the side of caution here..."); + } @Override public Integer announceRequirements(SpellAbility ability, String announce) { @@ -422,7 +426,7 @@ public class PlayerControllerForTests extends PlayerController { @Override public List chooseSpellAbilityToPlay() { //TODO: This method has to return the spellability chosen by player - // It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface + // It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface if (playerActions != null) { CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable(player, getGame(), CastSpellFromHandAction.class); if (castSpellFromHand != null) { @@ -476,7 +480,7 @@ public class PlayerControllerForTests extends PlayerController { public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { return Iterables.getFirst(colors, MagicColor.WHITE); } - + @Override public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) { return Iterables.getFirst(colors, (byte)0); @@ -551,7 +555,7 @@ public class PlayerControllerForTests extends PlayerController { ComputerUtil.playStack(sa, player, getGame()); } } - + private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){ if (sa.hasParam("TargetingPlayer")) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); @@ -583,7 +587,7 @@ public class PlayerControllerForTests extends PlayerController { } else { ComputerUtil.playStack(tgtSA, player, getGame()); } - } else + } else return false; // didn't play spell } return true; diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index 0ea00c3c30e..8ac97cdda22 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -397,9 +397,9 @@ public class MatchController extends AbstractGuiGame { } @Override - public Map assignGenericAmount(final CardView effectSource, final Map targets, + public Map assignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { - return new WaitCallback>() { + return new WaitCallback>() { @Override public void run() { final VAssignGenericAmount v = new VAssignGenericAmount(effectSource, targets, amount, atLeastOne, amountLabel, this); diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java index 629ab4ebc9d..09e6f70df8d 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java @@ -1,6 +1,6 @@ /* * Forge: Play Magic: the Gathering. - * Copyright (C) 2011 Forge Team + * Copyright (C) 2021 Forge Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,7 +32,7 @@ import forge.assets.FSkinColor.Colors; import forge.assets.FSkinFont; import forge.assets.FSkinImage; import forge.card.CardZoom; -import forge.game.GameEntityView; +import forge.card.MagicColor; import forge.game.card.CardView; import forge.game.player.PlayerView; import forge.screens.match.MatchController; @@ -56,7 +56,7 @@ public class VAssignGenericAmount extends FDialog { private static final float CARD_GAP_X = Utils.scale(10); private static final float ADD_BTN_HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.75f; - private final Callback> callback; + private final Callback> callback; private final int totalAmountToAssign; private final String lblAmount; @@ -67,7 +67,7 @@ public class VAssignGenericAmount extends FDialog { private final TargetsPanel pnlTargets; private final List targetsList = new ArrayList<>(); - private final Map targetsMap = new HashMap<>(); + private final Map targetsMap = new HashMap<>(); /** Constructor. * @@ -76,7 +76,7 @@ public class VAssignGenericAmount extends FDialog { * @param amount Total amount to be assigned * @param atLeastOne Must assign at least one amount to each target */ - public VAssignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback> waitCallback) { + public VAssignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback> waitCallback) { super(Localizer.getInstance().getMessage("lbLAssignAmountForEffect", amountLabel, CardTranslation.getTranslatedName(effectSource.getName())) , 2); callback = waitCallback; @@ -132,13 +132,13 @@ public class VAssignGenericAmount extends FDialog { } private class TargetsPanel extends FScrollPane { - private TargetsPanel(final Map targets) { - for (final Map.Entry e : targets.entrySet()) { + private TargetsPanel(final Map targets) { + for (final Map.Entry e : targets.entrySet()) { addDamageTarget(e.getKey(), e.getValue()); } } - private void addDamageTarget(GameEntityView entity, int max) { + private void addDamageTarget(Object entity, int max) { AssignTarget at = add(new AssignTarget(entity, max)); targetsMap.put(entity, at); targetsList.add(at); @@ -164,23 +164,38 @@ public class VAssignGenericAmount extends FDialog { } private class AssignTarget extends FContainer { - private final GameEntityView entity; + private final Object entity; private final FDisplayObject obj; private final FLabel label, btnSubtract, btnAdd; private final int max; private int amount; - public AssignTarget(GameEntityView entity0, int max0) { + public AssignTarget(Object entity0, int max0) { entity = entity0; max = max0; if (entity instanceof CardView) { obj = add(new EffectSourcePanel((CardView)entity)); - } - else if (entity instanceof PlayerView) { + } else if (entity instanceof PlayerView) { PlayerView player = (PlayerView)entity; obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player))); - } - else { + } else if (entity instanceof Byte) { + FSkinImage manaSymbol; + Byte color = (Byte) entity; + if (color == MagicColor.WHITE) { + manaSymbol = FSkinImage.MANA_W; + } else if (color == MagicColor.BLUE) { + manaSymbol = FSkinImage.MANA_U; + } else if (color == MagicColor.BLACK) { + manaSymbol = FSkinImage.MANA_B; + } else if (color == MagicColor.RED) { + manaSymbol = FSkinImage.MANA_R; + } else if (color == MagicColor.GREEN) { + manaSymbol = FSkinImage.MANA_G; + } else { // Should never come here, but add this to avoid compile error + manaSymbol = FSkinImage.MANA_COLORLESS; + } + obj = add(new MiscTargetPanel("", manaSymbol)); + } else { obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN)); } label = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build()); @@ -256,7 +271,7 @@ public class VAssignGenericAmount extends FDialog { } } - private void assignAmountTo(GameEntityView source, boolean isAdding) { + private void assignAmountTo(Object source, boolean isAdding) { AssignTarget at = targetsMap.get(source); int assigned = at.amount; int leftToAssign = Math.max(0, at.max - assigned); @@ -344,8 +359,8 @@ public class VAssignGenericAmount extends FDialog { callback.run(getAssignedMap()); } - public Map getAssignedMap() { - Map result = new HashMap<>(targetsList.size()); + public Map getAssignedMap() { + Map result = new HashMap<>(targetsList.size()); for (AssignTarget at : targetsList) result.put(at.entity, at.amount); return result; diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java index 8fcd1ec696f..499b79083eb 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java @@ -207,7 +207,7 @@ public class NetGuiGame extends AbstractGuiGame { } @Override - public Map assignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { + public Map assignGenericAmount(final CardView effectSource, final Map targets, final int amount, final boolean atLeastOne, final String amountLabel) { return sendAndWait(ProtocolMethod.divideShield, effectSource, targets, amount, atLeastOne, amountLabel); } diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java index 18fdf3d07ac..0594d3ee8e3 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java @@ -69,7 +69,8 @@ public interface IGuiGame { void setPanelSelection(CardView hostCard); SpellAbilityView getAbilityToPlay(CardView hostCard, List abilities, ITriggerEvent triggerEvent); Map assignCombatDamage(CardView attacker, List blockers, int damage, GameEntityView defender, boolean overrideOrder); - Map assignGenericAmount(CardView effectSource, Map target, int amount, final boolean atLeastOne, final String amountLabel); + // The Object passed should be GameEntityView for most case. Can be Byte for "generate mana of any combination" effect + Map assignGenericAmount(CardView effectSource, Map target, int amount, final boolean atLeastOne, final String amountLabel); void message(String message); void message(String message, String title); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 94e79fc78dc..b4c8bb118f0 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -12,6 +12,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -394,11 +396,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont @Override public Map divideShield(Card effectSource, Map affected, int shieldAmount) { final CardView vSource = CardView.get(effectSource); - final Map vAffected = new HashMap<>(affected.size()); + final Map vAffected = new HashMap<>(affected.size()); for (Map.Entry e : affected.entrySet()) { vAffected.put(GameEntityView.get(e.getKey()), e.getValue()); } - final Map vResult = getGui().assignGenericAmount(vSource, vAffected, shieldAmount, false, + final Map vResult = getGui().assignGenericAmount(vSource, vAffected, shieldAmount, false, localizer.getMessage("lblShield")); Map result = new HashMap<>(vResult.size()); for (Map.Entry e : affected.entrySet()) { @@ -409,6 +411,28 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return result; } + @Override + public Map specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) { + final CardView vSource = CardView.get(sa.getHostCard()); + final Map vAffected = new LinkedHashMap<>(manaAmount); + Integer maxAmount = different ? 1 : manaAmount; + Iterator it = colorSet.iterator(); + while (it.hasNext()) { + vAffected.put(it.next(), maxAmount); + } + final Map vResult = getGui().assignGenericAmount(vSource, vAffected, manaAmount, false, + localizer.getMessage("lblMana").toLowerCase()); + Map result = new HashMap<>(vResult.size()); + it = colorSet.iterator(); + while (it.hasNext()) { + Byte color = it.next(); + if (vResult.containsKey(color)) { + result.put(color, vResult.get(color)); + } + } + return result; + } + @Override public Integer announceRequirements(final SpellAbility ability, final String announce) { int max = Integer.MAX_VALUE; @@ -1974,11 +1998,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont } label = localizer.getMessage(label).toLowerCase(); final CardView vSource = CardView.get(currentAbility.getHostCard()); - final Map vTargets = new HashMap<>(targets.size()); + final Map vTargets = new HashMap<>(targets.size()); for (GameEntity e : targets) { vTargets.put(GameEntityView.get(e), amount); } - final Map vResult = getGui().assignGenericAmount(vSource, vTargets, amount, true, label); + final Map vResult = getGui().assignGenericAmount(vSource, vTargets, amount, true, label); for (GameEntity e : targets) { currentAbility.addDividedAllocation(e, vResult.get(GameEntityView.get(e))); }