Use VAssignGenericAmount dialog to assign mana combo

This commit is contained in:
Lyu Zong-Hong
2021-06-18 17:58:52 +09:00
parent c7e489b477
commit afa6c9986a
11 changed files with 185 additions and 80 deletions

View File

@@ -137,6 +137,23 @@ public class PlayerControllerAi extends PlayerController {
return new HashMap<>(); return new HashMap<>();
} }
@Override
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
Map<Byte, Integer> 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 @Override
public Integer announceRequirements(SpellAbility ability, String announce) { public Integer announceRequirements(SpellAbility ability, String announce) {
// For now, these "announcements" are made within the AI classes of the appropriate SA effects // For now, these "announcements" are made within the AI classes of the appropriate SA effects

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import static forge.util.TextUtil.toManaString; import static forge.util.TextUtil.toManaString;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -61,34 +62,54 @@ public class ManaEffect extends SpellAbilityEffect {
String[] colorsNeeded = express.isEmpty() ? null : express.split(" "); String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
boolean differentChoice = abMana.getOrigProduced().contains("Different"); boolean differentChoice = abMana.getOrigProduced().contains("Different");
ColorSet fullOptions = colorOptions; ColorSet fullOptions = colorOptions;
for (int nMana = 0; nMana < amount; nMana++) { // Use specifyManaCombo if possible
String choice = ""; if (colorsNeeded == null && amount > 1) {
if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible Map<Byte, Integer> choices = p.getController().specifyManaCombo(sa, colorOptions, amount, differentChoice);
colorOptions = ColorSet for (Map.Entry<Byte, Integer> e : choices.entrySet()) {
.fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana])); Byte chosenColor = e.getKey();
} String choice = MagicColor.toShortString(chosenColor);
if (colorOptions.isColorless() && colorsProduced.length > 0) { Integer count = e.getValue();
// If we just need generic mana, no reason to ask the controller for a choice, while (count > 0) {
// just use the first possible color. if (choiceString.length() > 0) {
choice = colorsProduced[differentChoice ? nMana : 0]; choiceString.append(" ");
} else { }
byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, choiceString.append(choice);
differentChoice && (colorsNeeded == null || colorsNeeded.length <= nMana) ? fullOptions : colorOptions); if (sa.hasParam("TwoEach")) {
if (chosenColor == 0) choiceString.append(" ").append(choice);
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName()); }
--count;
if (differentChoice) {
fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
} }
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) { if (differentChoice) {
choiceString.append(" "); fullOptions = ColorSet.fromMask(fullOptions.getColor() - chosenColor);
} }
choiceString.append(choice); choice = MagicColor.toShortString(chosenColor);
if (sa.hasParam("TwoEach")) { }
choiceString.append(" ").append(choice);
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")) { if (type.equals("EnchantedManaCost")) {
Card enchanted = card.getEnchantingCard(); Card enchanted = card.getEnchantingCard();
if (enchanted == null ) if (enchanted == null )
continue; continue;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -234,7 +255,7 @@ public class ManaEffect extends SpellAbilityEffect {
* a {@link forge.card.spellability.AbilityMana} object. * a {@link forge.card.spellability.AbilityMana} object.
* @param af * @param af
* a {@link forge.game.ability.AbilityFactory} object. * a {@link forge.game.ability.AbilityFactory} object.
* *
* @return a {@link java.lang.String} object. * @return a {@link java.lang.String} object.
*/ */

View File

@@ -48,9 +48,9 @@ import forge.item.PaperCard;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
/** /**
* A prototype for player controller class * A prototype for player controller class
* *
* Handles phase skips for now. * Handles phase skips for now.
*/ */
public abstract class PlayerController { public abstract class PlayerController {
@@ -109,21 +109,22 @@ public abstract class PlayerController {
public abstract Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder); public abstract Map<Card, Integer> assignCombatDamage(Card attacker, CardCollectionView blockers, int damageDealt, GameEntity defender, boolean overrideOrder);
public abstract Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount); public abstract Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount);
public abstract Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different);
public abstract Integer announceRequirements(SpellAbility ability, String announce); 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 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 CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> filter, boolean optional); public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> 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) // Specify a target of a spell (Spellskite)
public abstract Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility sa, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets); public abstract Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility sa, List<Pair<SpellAbilityStackInstance, GameObject>> 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<String, Object> params); public abstract CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional, Map<String, Object> params);
public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, false, null, params); } public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, false, null, params); }
public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, boolean isOptional, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, isOptional, null, params); } public final <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, SpellAbility sa, String title, boolean isOptional, Map<String, Object> params) { return chooseSingleEntityForEffect(optionList, null, sa, title, isOptional, null, params); }
public abstract <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player relatedPlayer, Map<String, Object> params); public abstract <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player relatedPlayer, Map<String, Object> params);
public abstract List<SpellAbility> chooseSpellAbilitiesForEffect(List<SpellAbility> spells, SpellAbility sa, String title, int num, Map<String, Object> params); public abstract List<SpellAbility> chooseSpellAbilitiesForEffect(List<SpellAbility> spells, SpellAbility sa, String title, int num, Map<String, Object> 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 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 abstract boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultChioce);
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Map<String, Object> params) { return chooseBinary(sa, question, kindOfChoice); } public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Map<String, Object> params) { return chooseBinary(sa, question, kindOfChoice); }
public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call); public abstract boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call);
public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap); public abstract Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap);
@@ -260,7 +261,7 @@ public abstract class PlayerController {
public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message); public abstract String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message);
public abstract String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message); public abstract String chooseCardName(SpellAbility sa, List<ICardFace> 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<ZoneType> origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider); public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider);
public abstract List<Card> chooseCardsForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, int min, int max, DelayedReveal delayedReveal, String selectPrompt, Player decider); public abstract List<Card> chooseCardsForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, int min, int max, DelayedReveal delayedReveal, String selectPrompt, Player decider);

View File

@@ -1029,13 +1029,13 @@ public final class CMatchUI
} }
@Override @Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> target, public Map<Object, Integer> assignGenericAmount(final CardView effectSource, final Map<Object, Integer> target,
final int amount, final boolean atLeastOne, final String amountLabel) { final int amount, final boolean atLeastOne, final String amountLabel) {
if (amount <= 0) { if (amount <= 0) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
final AtomicReference<Map<GameEntityView, Integer>> result = new AtomicReference<>(); final AtomicReference<Map<Object, Integer>> result = new AtomicReference<>();
FThreads.invokeInEdtAndWait(new Runnable() { FThreads.invokeInEdtAndWait(new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@@ -34,10 +34,11 @@ import javax.swing.SwingConstants;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.border.Border; import javax.swing.border.Border;
import forge.game.GameEntityView; import forge.card.MagicColor;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.gui.SOverlayUtils; import forge.gui.SOverlayUtils;
import forge.localinstance.skin.FSkinProp;
import forge.toolbox.FButton; import forge.toolbox.FButton;
import forge.toolbox.FLabel; import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane; import forge.toolbox.FScrollPane;
@@ -52,11 +53,7 @@ import forge.view.arcane.MiscCardPanel;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
/** /**
* Assembles Swing components of assign damage dialog. * Assembles Swing components of assign generic amount 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.
* *
* <br><br><i>(V at beginning of class name denotes a view class.)</i> * <br><br><i>(V at beginning of class name denotes a view class.)</i>
*/ */
@@ -80,12 +77,12 @@ public class VAssignGenericAmount {
private final FButton btnReset = new FButton(localizer.getMessage("lblReset")); private final FButton btnReset = new FButton(localizer.getMessage("lblReset"));
private static class AssignTarget { private static class AssignTarget {
public final GameEntityView entity; public final Object entity;
public final JLabel label; public final JLabel label;
public final int max; public final int max;
public int amount; 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; entity = e;
label = lbl; label = lbl;
max = max0; max = max0;
@@ -122,7 +119,7 @@ public class VAssignGenericAmount {
} }
}; };
public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) { public VAssignGenericAmount(final CMatchUI matchUI, final CardView effectSource, final Map<Object, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
this.matchUI = matchUI; this.matchUI = matchUI;
dlg.setTitle(localizer.getMessage("lbLAssignAmountForEffect", amountLabel, effectSource.toString())); dlg.setTitle(localizer.getMessage("lbLAssignAmountForEffect", amountLabel, effectSource.toString()));
@@ -158,7 +155,7 @@ public class VAssignGenericAmount {
final FScrollPane scrTargets = new FScrollPane(pnlTargets, false); final FScrollPane scrTargets = new FScrollPane(pnlTargets, false);
// Top row of cards... // Top row of cards...
for (final Map.Entry<GameEntityView, Integer> e : targets.entrySet()) { for (final Map.Entry<Object, Integer> e : targets.entrySet()) {
int maxAmount = e.getValue() != null ? e.getValue() : amount; 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); final AssignTarget at = new AssignTarget(e.getKey(), new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build(), maxAmount);
addPanelForTarget(pnlTargets, at); addPanelForTarget(pnlTargets, at);
@@ -166,7 +163,11 @@ public class VAssignGenericAmount {
// ... bottom row of labels. // ... bottom row of labels.
for (final AssignTarget l : targetsList) { 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() { 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"); pnlTargets.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center");
mp.addMouseListener(mad); mp.addMouseListener(mad);
targetsMap.put(mp, at); 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); targetsList.add(at);
} }
@@ -313,8 +335,8 @@ public class VAssignGenericAmount {
SOverlayUtils.hideOverlay(); SOverlayUtils.hideOverlay();
} }
public Map<GameEntityView, Integer> getAssignedMap() { public Map<Object, Integer> getAssignedMap() {
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size()); Map<Object, Integer> result = new HashMap<>(targetsList.size());
for (AssignTarget at : targetsList) for (AssignTarget at : targetsList)
result.put(at.entity, at.amount); result.put(at.entity, at.amount);
return result; return result;

View File

@@ -80,7 +80,7 @@ import forge.util.collect.FCollectionView;
/** /**
* Default harmless implementation for tests. * Default harmless implementation for tests.
* Test-specific behaviour can easily be added by mocking (parts of) this class. * 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, * 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). * 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. * 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..."); throw new IllegalStateException("Erring on the side of caution here...");
} }
@Override
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
throw new IllegalStateException("Erring on the side of caution here...");
}
@Override @Override
public Integer announceRequirements(SpellAbility ability, String announce) { public Integer announceRequirements(SpellAbility ability, String announce) {
@@ -422,7 +426,7 @@ public class PlayerControllerForTests extends PlayerController {
@Override @Override
public List<SpellAbility> chooseSpellAbilityToPlay() { public List<SpellAbility> chooseSpellAbilityToPlay() {
//TODO: This method has to return the spellability chosen by player //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) { if (playerActions != null) {
CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable(player, getGame(), CastSpellFromHandAction.class); CastSpellFromHandAction castSpellFromHand = playerActions.getNextActionIfApplicable(player, getGame(), CastSpellFromHandAction.class);
if (castSpellFromHand != null) { if (castSpellFromHand != null) {
@@ -476,7 +480,7 @@ public class PlayerControllerForTests extends PlayerController {
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
return Iterables.getFirst(colors, MagicColor.WHITE); return Iterables.getFirst(colors, MagicColor.WHITE);
} }
@Override @Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) { public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
return Iterables.getFirst(colors, (byte)0); return Iterables.getFirst(colors, (byte)0);
@@ -551,7 +555,7 @@ public class PlayerControllerForTests extends PlayerController {
ComputerUtil.playStack(sa, player, getGame()); ComputerUtil.playStack(sa, player, getGame());
} }
} }
private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){ private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){
if (sa.hasParam("TargetingPlayer")) { if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
@@ -583,7 +587,7 @@ public class PlayerControllerForTests extends PlayerController {
} else { } else {
ComputerUtil.playStack(tgtSA, player, getGame()); ComputerUtil.playStack(tgtSA, player, getGame());
} }
} else } else
return false; // didn't play spell return false; // didn't play spell
} }
return true; return true;

View File

@@ -397,9 +397,9 @@ public class MatchController extends AbstractGuiGame {
} }
@Override @Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets, public Map<Object, Integer> assignGenericAmount(final CardView effectSource, final Map<Object, Integer> targets,
final int amount, final boolean atLeastOne, final String amountLabel) { final int amount, final boolean atLeastOne, final String amountLabel) {
return new WaitCallback<Map<GameEntityView, Integer>>() { return new WaitCallback<Map<Object, Integer>>() {
@Override @Override
public void run() { public void run() {
final VAssignGenericAmount v = new VAssignGenericAmount(effectSource, targets, amount, atLeastOne, amountLabel, this); final VAssignGenericAmount v = new VAssignGenericAmount(effectSource, targets, amount, atLeastOne, amountLabel, this);

View File

@@ -1,6 +1,6 @@
/* /*
* Forge: Play Magic: the Gathering. * 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 * 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 * 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.FSkinFont;
import forge.assets.FSkinImage; import forge.assets.FSkinImage;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.game.GameEntityView; import forge.card.MagicColor;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.screens.match.MatchController; 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 CARD_GAP_X = Utils.scale(10);
private static final float ADD_BTN_HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.75f; private static final float ADD_BTN_HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.75f;
private final Callback<Map<GameEntityView, Integer>> callback; private final Callback<Map<Object, Integer>> callback;
private final int totalAmountToAssign; private final int totalAmountToAssign;
private final String lblAmount; private final String lblAmount;
@@ -67,7 +67,7 @@ public class VAssignGenericAmount extends FDialog {
private final TargetsPanel pnlTargets; private final TargetsPanel pnlTargets;
private final List<AssignTarget> targetsList = new ArrayList<>(); private final List<AssignTarget> targetsList = new ArrayList<>();
private final Map<GameEntityView, AssignTarget> targetsMap = new HashMap<>(); private final Map<Object, AssignTarget> targetsMap = new HashMap<>();
/** Constructor. /** Constructor.
* *
@@ -76,7 +76,7 @@ public class VAssignGenericAmount extends FDialog {
* @param amount Total amount to be assigned * @param amount Total amount to be assigned
* @param atLeastOne Must assign at least one amount to each target * @param atLeastOne Must assign at least one amount to each target
*/ */
public VAssignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback<Map<GameEntityView, Integer>> waitCallback) { public VAssignGenericAmount(final CardView effectSource, final Map<Object, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel, final WaitCallback<Map<Object, Integer>> waitCallback) {
super(Localizer.getInstance().getMessage("lbLAssignAmountForEffect", amountLabel, CardTranslation.getTranslatedName(effectSource.getName())) , 2); super(Localizer.getInstance().getMessage("lbLAssignAmountForEffect", amountLabel, CardTranslation.getTranslatedName(effectSource.getName())) , 2);
callback = waitCallback; callback = waitCallback;
@@ -132,13 +132,13 @@ public class VAssignGenericAmount extends FDialog {
} }
private class TargetsPanel extends FScrollPane { private class TargetsPanel extends FScrollPane {
private TargetsPanel(final Map<GameEntityView, Integer> targets) { private TargetsPanel(final Map<Object, Integer> targets) {
for (final Map.Entry<GameEntityView, Integer> e : targets.entrySet()) { for (final Map.Entry<Object, Integer> e : targets.entrySet()) {
addDamageTarget(e.getKey(), e.getValue()); 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)); AssignTarget at = add(new AssignTarget(entity, max));
targetsMap.put(entity, at); targetsMap.put(entity, at);
targetsList.add(at); targetsList.add(at);
@@ -164,23 +164,38 @@ public class VAssignGenericAmount extends FDialog {
} }
private class AssignTarget extends FContainer { private class AssignTarget extends FContainer {
private final GameEntityView entity; private final Object entity;
private final FDisplayObject obj; private final FDisplayObject obj;
private final FLabel label, btnSubtract, btnAdd; private final FLabel label, btnSubtract, btnAdd;
private final int max; private final int max;
private int amount; private int amount;
public AssignTarget(GameEntityView entity0, int max0) { public AssignTarget(Object entity0, int max0) {
entity = entity0; entity = entity0;
max = max0; max = max0;
if (entity instanceof CardView) { if (entity instanceof CardView) {
obj = add(new EffectSourcePanel((CardView)entity)); obj = add(new EffectSourcePanel((CardView)entity));
} } else if (entity instanceof PlayerView) {
else if (entity instanceof PlayerView) {
PlayerView player = (PlayerView)entity; PlayerView player = (PlayerView)entity;
obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player))); obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player)));
} } else if (entity instanceof Byte) {
else { 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)); obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN));
} }
label = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build()); 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); AssignTarget at = targetsMap.get(source);
int assigned = at.amount; int assigned = at.amount;
int leftToAssign = Math.max(0, at.max - assigned); int leftToAssign = Math.max(0, at.max - assigned);
@@ -344,8 +359,8 @@ public class VAssignGenericAmount extends FDialog {
callback.run(getAssignedMap()); callback.run(getAssignedMap());
} }
public Map<GameEntityView, Integer> getAssignedMap() { public Map<Object, Integer> getAssignedMap() {
Map<GameEntityView, Integer> result = new HashMap<>(targetsList.size()); Map<Object, Integer> result = new HashMap<>(targetsList.size());
for (AssignTarget at : targetsList) for (AssignTarget at : targetsList)
result.put(at.entity, at.amount); result.put(at.entity, at.amount);
return result; return result;

View File

@@ -207,7 +207,7 @@ public class NetGuiGame extends AbstractGuiGame {
} }
@Override @Override
public Map<GameEntityView, Integer> assignGenericAmount(final CardView effectSource, final Map<GameEntityView, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) { public Map<Object, Integer> assignGenericAmount(final CardView effectSource, final Map<Object, Integer> targets, final int amount, final boolean atLeastOne, final String amountLabel) {
return sendAndWait(ProtocolMethod.divideShield, effectSource, targets, amount, atLeastOne, amountLabel); return sendAndWait(ProtocolMethod.divideShield, effectSource, targets, amount, atLeastOne, amountLabel);
} }

View File

@@ -69,7 +69,8 @@ public interface IGuiGame {
void setPanelSelection(CardView hostCard); void setPanelSelection(CardView hostCard);
SpellAbilityView getAbilityToPlay(CardView hostCard, List<SpellAbilityView> abilities, ITriggerEvent triggerEvent); SpellAbilityView getAbilityToPlay(CardView hostCard, List<SpellAbilityView> abilities, ITriggerEvent triggerEvent);
Map<CardView, Integer> assignCombatDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder); Map<CardView, Integer> assignCombatDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder);
Map<GameEntityView, Integer> assignGenericAmount(CardView effectSource, Map<GameEntityView, Integer> 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<Object, Integer> assignGenericAmount(CardView effectSource, Map<Object, Integer> target, int amount, final boolean atLeastOne, final String amountLabel);
void message(String message); void message(String message);
void message(String message, String title); void message(String message, String title);

View File

@@ -12,6 +12,8 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -394,11 +396,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override @Override
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) { public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
final CardView vSource = CardView.get(effectSource); final CardView vSource = CardView.get(effectSource);
final Map<GameEntityView, Integer> vAffected = new HashMap<>(affected.size()); final Map<Object, Integer> vAffected = new HashMap<>(affected.size());
for (Map.Entry<GameEntity, Integer> e : affected.entrySet()) { for (Map.Entry<GameEntity, Integer> e : affected.entrySet()) {
vAffected.put(GameEntityView.get(e.getKey()), e.getValue()); vAffected.put(GameEntityView.get(e.getKey()), e.getValue());
} }
final Map<GameEntityView, Integer> vResult = getGui().assignGenericAmount(vSource, vAffected, shieldAmount, false, final Map<Object, Integer> vResult = getGui().assignGenericAmount(vSource, vAffected, shieldAmount, false,
localizer.getMessage("lblShield")); localizer.getMessage("lblShield"));
Map<GameEntity, Integer> result = new HashMap<>(vResult.size()); Map<GameEntity, Integer> result = new HashMap<>(vResult.size());
for (Map.Entry<GameEntity, Integer> e : affected.entrySet()) { for (Map.Entry<GameEntity, Integer> e : affected.entrySet()) {
@@ -409,6 +411,28 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return result; return result;
} }
@Override
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
final CardView vSource = CardView.get(sa.getHostCard());
final Map<Object, Integer> vAffected = new LinkedHashMap<>(manaAmount);
Integer maxAmount = different ? 1 : manaAmount;
Iterator<Byte> it = colorSet.iterator();
while (it.hasNext()) {
vAffected.put(it.next(), maxAmount);
}
final Map<Object, Integer> vResult = getGui().assignGenericAmount(vSource, vAffected, manaAmount, false,
localizer.getMessage("lblMana").toLowerCase());
Map<Byte, Integer> 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 @Override
public Integer announceRequirements(final SpellAbility ability, final String announce) { public Integer announceRequirements(final SpellAbility ability, final String announce) {
int max = Integer.MAX_VALUE; int max = Integer.MAX_VALUE;
@@ -1974,11 +1998,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
} }
label = localizer.getMessage(label).toLowerCase(); label = localizer.getMessage(label).toLowerCase();
final CardView vSource = CardView.get(currentAbility.getHostCard()); final CardView vSource = CardView.get(currentAbility.getHostCard());
final Map<GameEntityView, Integer> vTargets = new HashMap<>(targets.size()); final Map<Object, Integer> vTargets = new HashMap<>(targets.size());
for (GameEntity e : targets) { for (GameEntity e : targets) {
vTargets.put(GameEntityView.get(e), amount); vTargets.put(GameEntityView.get(e), amount);
} }
final Map<GameEntityView, Integer> vResult = getGui().assignGenericAmount(vSource, vTargets, amount, true, label); final Map<Object, Integer> vResult = getGui().assignGenericAmount(vSource, vTargets, amount, true, label);
for (GameEntity e : targets) { for (GameEntity e : targets) {
currentAbility.addDividedAllocation(e, vResult.get(GameEntityView.get(e))); currentAbility.addDividedAllocation(e, vResult.get(GameEntityView.get(e)));
} }