package mage.util;
import mage.MageObject;
import mage.Mana;
import mage.ManaSymbol;
import mage.abilities.Ability;
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaSymbols;
import mage.abilities.mana.*;
import mage.cards.Card;
import mage.choices.Choice;
import mage.constants.ColoredManaSymbol;
import mage.game.Game;
import java.util.*;
/**
* @author noxx
*/
public final class ManaUtil {
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 List of mana abilities permanent may produce and are reasonable
* for unpaid mana
*/
public static LinkedHashMap<UUID, ActivatedManaAbilityImpl> tryToAutoPay(ManaCost unpaid, LinkedHashMap<UUID, ActivatedManaAbilityImpl> 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 LinkedHashMap<UUID, ActivatedManaAbilityImpl> getManaAbilitiesUsingManaSymbols(LinkedHashMap<UUID, ActivatedManaAbilityImpl> useableAbilities, ManaSymbols symbols, Mana unpaidMana) {
Set<ManaSymbol> 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<ManaSymbol> 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<ManaSymbol> 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<ManaSymbol> symbols) {
int count = 0;
for (ManaSymbol symbol : symbols) {
if (symbol.isHybrid()) {
count++;
}
}
return count;
}
private static ActivatedManaAbilityImpl checkBlackMana(ManaSymbols symbols, Set<ManaSymbol> 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);
}
}
return chosenManaAbilityForHybrid;
}
private static ActivatedManaAbilityImpl checkRedMana(ManaSymbols symbols, Set<ManaSymbol> 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);
}
}
return chosenManaAbilityForHybrid;
}
private static ActivatedManaAbilityImpl checkBlueMana(ManaSymbols symbols, Set<ManaSymbol> 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);
}
}
return chosenManaAbilityForHybrid;
}
private static ActivatedManaAbilityImpl checkWhiteMana(ManaSymbols symbols, Set<ManaSymbol> 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);
}
}
return chosenManaAbilityForHybrid;
}
private static ActivatedManaAbilityImpl checkGreenMana(ManaSymbols symbols, Set<ManaSymbol> 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);
}
}
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 LinkedHashMap<UUID, ActivatedManaAbilityImpl> getManaAbilitiesUsingMana(ManaCost unpaid, LinkedHashMap<UUID, ActivatedManaAbilityImpl> 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) {
// use any (lets choose first)
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 LinkedHashMap<UUID, ActivatedManaAbilityImpl> replace(LinkedHashMap<UUID, ActivatedManaAbilityImpl> 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 inthe 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() + "<div style='font-size:11pt'>" + baseObject.getLogName() + "</div>";
} 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}
*
*/
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();
}
}