package mage.util; import mage.MageObject; import mage.Mana; import mage.ManaSymbol; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.VariableCostType; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.*; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.Effect; import mage.abilities.mana.*; import mage.cards.AdventureCard; import mage.cards.Card; import mage.cards.ModalDoubleFacedCard; import mage.cards.SplitCard; import mage.choices.Choice; import mage.constants.ColoredManaSymbol; import mage.constants.ManaType; import mage.filter.FilterMana; import mage.game.Game; import mage.game.permanent.token.Token; import mage.players.Player; import java.util.*; /** * @author noxx, JayDi85 */ public final class ManaUtil { private static final String regexBlack = ".*\\x7b.{0,2}B.{0,2}\\x7d.*"; private static final String regexBlue = ".*\\x7b.{0,2}U.{0,2}\\x7d.*"; private static final String regexRed = ".*\\x7b.{0,2}R.{0,2}\\x7d.*"; private static final String regexGreen = ".*\\x7b.{0,2}G.{0,2}\\x7d.*"; private static final String regexWhite = ".*\\x7b.{0,2}W.{0,2}\\x7d.*"; private ManaUtil() { } /** * In case the choice of mana to be produced is obvious, let's discard all * other abilities. *

* Example: Pay {W}{R} *

* Land produces {W} or {G}. *

* No need to ask what player wants to choose. {W} mana ability should be * left only. *

* But we CAN do auto choice only in case we have basic mana abilities. * Example: we should pay {1} and we have Cavern of Souls that can produce * {1} or any mana of creature type choice. We can't simply auto choose {1} * as the second mana ability also makes spell uncounterable. *

* In case we can't auto choose we'll simply return the useableAbilities map * back to caller without any modification. * * @param unpaid Mana we need to pay. Can be null (it is for X costs now). * @param useableAbilities List of mana abilities permanent may produce * @return Map of mana abilities permanent may produce and are reasonable * for unpaid mana */ public static Map tryToAutoPay(ManaCost unpaid, Map useableAbilities) { // first check if we have only basic mana abilities for (ActivatedManaAbilityImpl ability : useableAbilities.values()) { if (!(ability instanceof BasicManaAbility)) { // return map as-is without any modification if (!(ability instanceof AnyColorManaAbility)) { return useableAbilities; } } } if (unpaid != null) { ManaSymbols symbols = ManaSymbols.buildFromManaCost(unpaid); Mana unpaidMana = unpaid.getMana(); if (!symbols.isEmpty()) { return getManaAbilitiesUsingManaSymbols(useableAbilities, symbols, unpaidMana); } else { return getManaAbilitiesUsingMana(unpaid, useableAbilities); } } return useableAbilities; } /** * For Human players this is called before a player is asked to select a * mana color to pay a specific cost. If the choice obvious, the color is * auto picked by this method without bothering the human player * * @param choice the color choice to do * @param unpaid the mana still to pay * @return */ public static boolean tryToAutoSelectAManaColor(Choice choice, ManaCost unpaid) { String colorToAutoPay = null; if (unpaid.containsColor(ColoredManaSymbol.W) && choice.getChoices().contains("White")) { colorToAutoPay = "White"; } if (unpaid.containsColor(ColoredManaSymbol.R) && choice.getChoices().contains("Red")) { if (colorToAutoPay != null) { return false; } colorToAutoPay = "Red"; } if (unpaid.containsColor(ColoredManaSymbol.G) && choice.getChoices().contains("Green")) { if (colorToAutoPay != null) { return false; } colorToAutoPay = "Green"; } if (unpaid.containsColor(ColoredManaSymbol.U) && choice.getChoices().contains("Blue")) { if (colorToAutoPay != null) { return false; } colorToAutoPay = "Blue"; } if (unpaid.containsColor(ColoredManaSymbol.B) && choice.getChoices().contains("Black")) { if (colorToAutoPay != null) { return false; } colorToAutoPay = "Black"; } // only colorless to pay so take first choice if (unpaid.getMana().getDifferentColors() == 0) { colorToAutoPay = choice.getChoices().iterator().next(); } // one possible useful option found if (colorToAutoPay != null) { choice.setChoice(colorToAutoPay); return true; } return false; } private static Map getManaAbilitiesUsingManaSymbols(Map useableAbilities, ManaSymbols symbols, Mana unpaidMana) { Set countColored = new HashSet<>(); ActivatedManaAbilityImpl chosenManaAbility = null; ActivatedManaAbilityImpl chosenManaAbilityForHybrid; for (ActivatedManaAbilityImpl ability : useableAbilities.values()) { chosenManaAbility = getManaAbility(symbols, countColored, chosenManaAbility, ability); chosenManaAbilityForHybrid = checkRedMana(symbols, countColored, ability); chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; chosenManaAbilityForHybrid = checkBlackMana(symbols, countColored, ability); chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; chosenManaAbilityForHybrid = checkBlueMana(symbols, countColored, ability); chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; chosenManaAbilityForHybrid = checkWhiteMana(symbols, countColored, ability); chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; chosenManaAbilityForHybrid = checkGreenMana(symbols, countColored, ability); chosenManaAbility = chosenManaAbilityForHybrid != null ? chosenManaAbilityForHybrid : chosenManaAbility; } if (countColored.isEmpty()) { // seems there is no colorful mana we can pay for // try to pay {1} if (unpaidMana.getGeneric() > 0) { // use any (lets choose first) return replace(useableAbilities, useableAbilities.values().iterator().next()); } // return map as-is without any modification return useableAbilities; } if (countColored.size() > 1) { // we may try to pay for hybrid mana symbol Set temp = new HashSet<>(); temp.addAll(countColored); for (ManaSymbol manaSymbol : countColored) { // idea: if we have {W/R} symbol then we can remove it if symbols contain {W} or {R} // but only if it doesn't contain both of them if (manaSymbol.isHybrid()) { boolean found1 = countColored.contains(manaSymbol.getManaSymbol1()); boolean found2 = countColored.contains(manaSymbol.getManaSymbol2()); if (found1 && !found2) { temp.remove(manaSymbol); } else if (!found1 && found2) { temp.remove(manaSymbol); } } } // we got another chance for auto pay if (temp.size() == 1) { for (ActivatedManaAbilityImpl ability : useableAbilities.values()) { chosenManaAbility = getManaAbility(symbols, countColored, chosenManaAbility, ability); } return replace(useableAbilities, chosenManaAbility); } // we can't auto choose as there are variants of mana payment // return map as-is without any modification return useableAbilities; } return replace(useableAbilities, chosenManaAbility); } private static ActivatedManaAbilityImpl getManaAbility(ManaSymbols symbols, Set countColored, ActivatedManaAbilityImpl chosenManaAbility, ActivatedManaAbilityImpl ability) { if (ability instanceof RedManaAbility && symbols.contains(ManaSymbol.R)) { chosenManaAbility = ability; countColored.add(ManaSymbol.R); } if (ability instanceof BlackManaAbility && symbols.contains(ManaSymbol.B)) { chosenManaAbility = ability; countColored.add(ManaSymbol.B); } if (ability instanceof BlueManaAbility && symbols.contains(ManaSymbol.U)) { chosenManaAbility = ability; countColored.add(ManaSymbol.U); } if (ability instanceof WhiteManaAbility && symbols.contains(ManaSymbol.W)) { chosenManaAbility = ability; countColored.add(ManaSymbol.W); } if (ability instanceof GreenManaAbility && symbols.contains(ManaSymbol.G)) { chosenManaAbility = ability; countColored.add(ManaSymbol.G); } return chosenManaAbility; } /** * Counts DIFFERENT hybrid mana symbols. * * @param symbols * @return */ private static int countUniqueHybridSymbols(Set symbols) { int count = 0; for (ManaSymbol symbol : symbols) { if (symbol.isHybrid()) { count++; } } return count; } private static ActivatedManaAbilityImpl checkBlackMana(ManaSymbols symbols, Set countColored, ActivatedManaAbilityImpl ability) { ActivatedManaAbilityImpl chosenManaAbilityForHybrid = null; if (ability instanceof BlackManaAbility) { if (symbols.contains(ManaSymbol.HYBRID_BR)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_BR); } else if (symbols.contains(ManaSymbol.HYBRID_BG)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_BG); } else if (symbols.contains(ManaSymbol.HYBRID_UB)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_UB); } else if (symbols.contains(ManaSymbol.HYBRID_WB)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_WB); } else if (symbols.contains(ManaSymbol.HYBRID_CB)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_CB); } } return chosenManaAbilityForHybrid; } private static ActivatedManaAbilityImpl checkRedMana(ManaSymbols symbols, Set countColored, ActivatedManaAbilityImpl ability) { ActivatedManaAbilityImpl chosenManaAbilityForHybrid = null; if (ability instanceof RedManaAbility) { if (symbols.contains(ManaSymbol.HYBRID_BR)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_BR); } else if (symbols.contains(ManaSymbol.HYBRID_RG)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_RG); } else if (symbols.contains(ManaSymbol.HYBRID_RW)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_RW); } else if (symbols.contains(ManaSymbol.HYBRID_UR)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_UR); } else if (symbols.contains(ManaSymbol.HYBRID_CR)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_CR); } } return chosenManaAbilityForHybrid; } private static ActivatedManaAbilityImpl checkBlueMana(ManaSymbols symbols, Set countColored, ActivatedManaAbilityImpl ability) { ActivatedManaAbilityImpl chosenManaAbilityForHybrid = null; if (ability instanceof BlueManaAbility) { if (symbols.contains(ManaSymbol.HYBRID_UB)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_UB); } else if (symbols.contains(ManaSymbol.HYBRID_UR)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_UR); } else if (symbols.contains(ManaSymbol.HYBRID_WU)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_WU); } else if (symbols.contains(ManaSymbol.HYBRID_GU)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_GU); } else if (symbols.contains(ManaSymbol.HYBRID_CU)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_CU); } } return chosenManaAbilityForHybrid; } private static ActivatedManaAbilityImpl checkWhiteMana(ManaSymbols symbols, Set countColored, ActivatedManaAbilityImpl ability) { ActivatedManaAbilityImpl chosenManaAbilityForHybrid = null; if (ability instanceof WhiteManaAbility) { if (symbols.contains(ManaSymbol.HYBRID_WU)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_WU); } else if (symbols.contains(ManaSymbol.HYBRID_WB)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_WB); } else if (symbols.contains(ManaSymbol.HYBRID_GW)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_GW); } else if (symbols.contains(ManaSymbol.HYBRID_RW)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_RW); } else if (symbols.contains(ManaSymbol.HYBRID_CW)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_CW); } } return chosenManaAbilityForHybrid; } private static ActivatedManaAbilityImpl checkGreenMana(ManaSymbols symbols, Set countColored, ActivatedManaAbilityImpl ability) { ActivatedManaAbilityImpl chosenManaAbilityForHybrid = null; if (ability instanceof GreenManaAbility) { if (symbols.contains(ManaSymbol.HYBRID_GW)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_GW); } else if (symbols.contains(ManaSymbol.HYBRID_GU)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_GU); } else if (symbols.contains(ManaSymbol.HYBRID_BG)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_BG); } else if (symbols.contains(ManaSymbol.HYBRID_RG)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_RG); } else if (symbols.contains(ManaSymbol.HYBRID_CG)) { chosenManaAbilityForHybrid = ability; countColored.add(ManaSymbol.HYBRID_CG); } } return chosenManaAbilityForHybrid; } /** * This is old method that uses unpaid mana to filter out some abilities. * The only disadvantage is that it can't handle hybrid mana correctly. * * @param unpaid * @param useableAbilities * @return */ private static Map getManaAbilitiesUsingMana(ManaCost unpaid, Map useableAbilities) { Mana mana = unpaid.getMana(); int countColorfull = 0; int countColorless = 0; ActivatedManaAbilityImpl chosenManaAbility = null; for (ActivatedManaAbilityImpl ability : useableAbilities.values()) { if (ability instanceof RedManaAbility && mana.contains(Mana.RedMana(1))) { chosenManaAbility = ability; countColorfull++; } if (ability instanceof BlackManaAbility && mana.contains(Mana.BlackMana(1))) { chosenManaAbility = ability; countColorfull++; } if (ability instanceof BlueManaAbility && mana.contains(Mana.BlueMana(1))) { chosenManaAbility = ability; countColorfull++; } if (ability instanceof WhiteManaAbility && mana.contains(Mana.WhiteMana(1))) { chosenManaAbility = ability; countColorfull++; } if (ability instanceof GreenManaAbility && mana.contains(Mana.GreenMana(1))) { chosenManaAbility = ability; countColorfull++; } } if (countColorfull == 0) { // seems there is no colorful mana we can use // try to pay {1} if (mana.getGeneric() > 0) { // choose first without addional costs if all have addional costs take the first for (ActivatedManaAbilityImpl manaAbility : useableAbilities.values()) { if (manaAbility.getCosts().size() == 1 && manaAbility.getCosts().get(0).getClass().equals(TapSourceCost.class)) { return replace(useableAbilities, manaAbility); } } return replace(useableAbilities, useableAbilities.values().iterator().next()); } // return map as-is without any modification return useableAbilities; } if (countColorfull > 1) { // we can't auto choose as there are variants of mana payment // return map as-is without any modification return useableAbilities; } return replace(useableAbilities, chosenManaAbility); } private static Map replace(Map useableAbilities, ActivatedManaAbilityImpl chosenManaAbility) { // modify the map with the chosen mana ability useableAbilities.clear(); useableAbilities.put(chosenManaAbility.getId(), chosenManaAbility); return useableAbilities; } /** * This activates the special button in the feedback panel of the client if * there exists special ways to pay the mana (e.g. Delve, Convoke) * * @param source ability the mana costs have to be paid for * @param game * @param unpaid mana that has still to be paid * @return message to be shown in human players feedback area */ public static String addSpecialManaPayAbilities(Ability source, Game game, ManaCost unpaid) { MageObject baseObject = game.getPermanent(source.getSourceId()); if (baseObject == null) { baseObject = game.getCard(source.getSourceId()); } // check for special mana payment possibilities MageObject mageObject = source.getSourceObject(game); if (mageObject instanceof Card) { for (Ability ability : ((Card) mageObject).getAbilities(game)) { if (ability instanceof AlternateManaPaymentAbility) { ((AlternateManaPaymentAbility) ability).addSpecialAction(source, game, unpaid); } } if (baseObject == null) { baseObject = mageObject; } } if (baseObject != null) { return unpaid.getText() + "

" + baseObject.getLogName() + "
"; } else { return unpaid.getText(); } } /** * Converts a collection of mana symbols into a single condensed string e.g: * {1}{1}{1}{1}{1}{W} = {5}{W} * {2}{B}{2}{B}{2}{B} = {6}{B}{B}{B} * {1}{2}{R}{U}{1}{1} = {5}{R}{U} * {B}{G}{R} = {B}{G}{R} * * @param rawCost the uncondensed version of the mana String. * @return the condensed version of the mana String. */ public static String condenseManaCostString(String rawCost) { int total = 0; int index = 0; // Split the string in to an array of numbers and colored mana symbols String[] splitCost = rawCost.replace("{", "").replace("}", " ").split(" "); // Sort alphabetically which will push1 the numbers to the front before the colored mana symbols Arrays.sort(splitCost); for (String c : splitCost) { // If the string is a representation of a number if (c.matches("\\d+")) { total += Integer.parseInt(c); } else { // First non-number we see we can finish as they are sorted break; } index++; } int splitCostLength = splitCost.length; // No need to add {total} to the mana cost if total == 0 int shift = (total > 0) ? 1 : 0; String[] finalCost = new String[shift + splitCostLength - index]; // Account for no colourless mana symbols seen if (total > 0) { finalCost[0] = String.valueOf(total); } System.arraycopy(splitCost, index, finalCost, shift, splitCostLength - index); // Combine the cost back as a mana string StringBuilder sb = new StringBuilder(); for (String s : finalCost) { sb.append('{').append(s).append('}'); } // Return the condensed string return sb.toString(); } public static boolean isColorIdentityCompatible(FilterMana needColors, FilterMana cardColors) { // colorless can be used with any color return needColors != null && !(cardColors.isBlack() && !needColors.isBlack() || cardColors.isBlue() && !needColors.isBlue() || cardColors.isGreen() && !needColors.isGreen() || cardColors.isRed() && !needColors.isRed() || cardColors.isWhite() && !needColors.isWhite()); } public static void collectColorIdentity(FilterMana destColors, FilterMana newColors) { if (newColors.isWhite()) { destColors.setWhite(true); } if (newColors.isBlue()) { destColors.setBlue(true); } if (newColors.isBlack()) { destColors.setBlack(true); } if (newColors.isRed()) { destColors.setRed(true); } if (newColors.isGreen()) { destColors.setGreen(true); } } /** * Find full card's color identity (from mana cost and rules) * * @param cardColor color indicator * @param cardManaSymbols mana cost * @param cardRules rules list * @param secondSideCard second side of double faces card * @return */ public static FilterMana getColorIdentity(ObjectColor cardColor, String cardManaSymbols, List cardRules, Card secondSideCard) { // 20210121 // 903.4 // The Commander variant uses color identity to determine what cards can be in a deck with a certain // commander. The color identity of a card is the color or colors of any mana symbols in that card’s mana // cost or rules text, plus any colors defined by its characteristic-defining abilities (see rule 604.3) // or color indicator (see rule 204). // 903.4d // The back face of a double-faced card (see rule 711) is included when determining a card’s color identity. // This is an exception to rule 711.4a. FilterMana res = new FilterMana(); // from object (color indicator) ObjectColor color = (cardColor != null ? new ObjectColor(cardColor) : new ObjectColor()); // from mana res.setWhite(color.isWhite() || containsManaSymbol(cardManaSymbols, "W")); res.setBlue(color.isBlue() || containsManaSymbol(cardManaSymbols, "U")); res.setBlack(color.isBlack() || containsManaSymbol(cardManaSymbols, "B")); res.setRed(color.isRed() || containsManaSymbol(cardManaSymbols, "R")); res.setGreen(color.isGreen() || containsManaSymbol(cardManaSymbols, "G")); // from rules for (String rule : cardRules) { rule = rule.replaceAll("(?i)", ""); // Ignoring reminder text in italic if (!res.isWhite() && (rule.matches(regexWhite))) { res.setWhite(true); } if (!res.isBlue() && (rule.matches(regexBlue))) { res.setBlue(true); } if (!res.isBlack() && rule.matches(regexBlack)) { res.setBlack(true); } if (!res.isRed() && (rule.matches(regexRed))) { res.setRed(true); } if (!res.isGreen() && (rule.matches(regexGreen))) { res.setGreen(true); } } // SECOND SIDE CARD if (secondSideCard != null) { // from object (color indicator) ObjectColor secondColor = secondSideCard.getColor(null); res.setBlack(res.isBlack() || secondColor.isBlack()); res.setGreen(res.isGreen() || secondColor.isGreen()); res.setRed(res.isRed() || secondColor.isRed()); res.setBlue(res.isBlue() || secondColor.isBlue()); res.setWhite(res.isWhite() || secondColor.isWhite()); // from mana List secondManaSymbols = secondSideCard.getManaCostSymbols(); res.setWhite(res.isWhite() || containsManaSymbol(secondManaSymbols, "W")); res.setBlue(res.isBlue() || containsManaSymbol(secondManaSymbols, "U")); res.setBlack(res.isBlack() || containsManaSymbol(secondManaSymbols, "B")); res.setRed(res.isRed() || containsManaSymbol(secondManaSymbols, "R")); res.setGreen(res.isGreen() || containsManaSymbol(secondManaSymbols, "G")); // from rules for (String rule : secondSideCard.getRules()) { rule = rule.replaceAll("(?i)", ""); // Ignoring reminder text in italic if (!res.isWhite() && rule.matches(regexWhite)) { res.setWhite(true); } if (!res.isBlue() && rule.matches(regexBlue)) { res.setBlue(true); } if (!res.isBlack() && rule.matches(regexBlack)) { res.setBlack(true); } if (!res.isRed() && rule.matches(regexRed)) { res.setRed(true); } if (!res.isGreen() && rule.matches(regexGreen)) { res.setGreen(true); } } } return res; } private static boolean containsManaSymbol(List cardManaSymbols, String needSymbol) { // search R in {R/B} return cardManaSymbols.stream().anyMatch(s -> s.contains(needSymbol)); } private static boolean containsManaSymbol(String cardManaSymbols, String needSymbol) { // search R in {R/B} return cardManaSymbols.contains(needSymbol); } public static FilterMana getColorIdentity(Card card) { Card secondSide; if (card instanceof SplitCard) { secondSide = ((SplitCard) card).getRightHalfCard(); } else if (card instanceof AdventureCard) { secondSide = ((AdventureCard) card).getSpellCard(); } else if (card instanceof ModalDoubleFacedCard) { secondSide = ((ModalDoubleFacedCard) card).getRightHalfCard(); } else { secondSide = card.getSecondCardFace(); } return getColorIdentity(card.getColor(), String.join("", card.getManaCostSymbols()), card.getRules(), secondSide); } public static FilterMana getColorIdentity(Token token) { return getColorIdentity( token.getColor(), String.join("", token.getManaCostSymbols()), token.getAbilities().getRules(), token.getBackFace() == null ? null : token.getBackFace().getCopySourceCard() ); } public static int getColorIdentityHash(FilterMana colorIdentity) { int hash = 3; hash = 23 * hash + (colorIdentity.isWhite() ? 1 : 0); hash = 23 * hash + (colorIdentity.isBlue() ? 1 : 0); hash = 23 * hash + (colorIdentity.isBlack() ? 1 : 0); hash = 23 * hash + (colorIdentity.isRed() ? 1 : 0); hash = 23 * hash + (colorIdentity.isGreen() ? 1 : 0); return hash; } /** * all ability/effect code with "= new GenericManaCost" must be replaced by * createManaCost call */ public static ManaCost createManaCost(int genericManaCount, boolean payAsX) { if (payAsX) { VariableManaCost xCost = new VariableManaCost(VariableCostType.NORMAL); xCost.setAmount(genericManaCount, genericManaCount, false); return xCost; } else { return new GenericManaCost(genericManaCount); } } public static ManaCost createManaCost(DynamicValue genericManaCount, Game game, Ability sourceAbility, Effect effect) { int costValue = genericManaCount.calculate(game, sourceAbility, effect); if (genericManaCount instanceof GetXValue) { // variable (X must be final value after all events and effects) return createManaCost(costValue, true); } else { // static/generic return createManaCost(costValue, false); } } public static int playerPaysXGenericMana(boolean payAsX, String restoreContextName, Player player, Ability source, Game game) { return playerPaysXGenericMana(payAsX, restoreContextName, player, source, game, Integer.MAX_VALUE); } public static int playerPaysXGenericMana(boolean payAsX, String restoreContextName, Player player, Ability source, Game game, int maxValue) { // payAsX - if your cost is X value (some mana can be used for X cost only) // false: "you may pay any amount of mana" // true: "counter that spell unless that player pays {X}" int wantToPay = 0; boolean payed = false; if (player.canRespond()) { int bookmark = game.bookmarkState(); player.resetStoredBookmark(game); wantToPay = player.announceXMana(0, maxValue, "Choose how much mana to pay", game, source); if (wantToPay > 0) { Cost cost = ManaUtil.createManaCost(wantToPay, payAsX); payed = cost.pay(source, game, source, player.getId(), false, null); } else { payed = true; } if (!payed) { player.restoreState(bookmark, restoreContextName, game); game.fireUpdatePlayersEvent(); } else { game.removeBookmark(bookmark); } } if (payed) { game.informPlayers(player.getLogName() + " pays {" + wantToPay + "}."); return wantToPay; } else { return 0; } } /** * Find all used mana types in mana cost (wubrg + colorless) * * @return */ public static List getManaTypesInCost(ManaCost cost) { List res = new ArrayList<>(); for (Mana mana : cost.getManaOptions()) { if (mana.getWhite() > 0) res.add(ManaType.WHITE); if (mana.getBlue() > 0) res.add(ManaType.BLUE); if (mana.getBlack() > 0) res.add(ManaType.BLACK); if (mana.getRed() > 0) res.add(ManaType.RED); if (mana.getGreen() > 0) res.add(ManaType.GREEN); if (mana.getColorless() > 0 || mana.getGeneric() > 0 || mana.getAny() > 0) res.add(ManaType.COLORLESS); } return res; } }