diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 6f3297566a0..38613989d60 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1942,4 +1942,44 @@ public class ComputerUtil { return bestBoardPosition; } + public static boolean hasGoodTargetForAura(final Player ai, final Card aura) { + // This is currently used by ComputerUtilCost.willPayUnlessCost to determine if there's a viable target for a spell + // that can be paid for with an untapped shockland. + + if (ai == null || aura == null) { + return false; + } + + if (aura.getFirstAttachSpell() == null) { + // Something went majorly wrong here, should never happen + System.err.println("Unexpected behavior: first attach spell for Aura card " + aura.getName() + " was null in ComputerUtil::hasGoodTargetForAura."); + return false; + } + + boolean hasTarget = false; + boolean aiHasTargets = false, oppHasTargets = false; + + aiHasTargets = !CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(aura.getFirstAttachSpell())).isEmpty(); + for (Player p : ai.getOpponents()) { + if (!CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(aura.getFirstAttachSpell())).isEmpty()) { + oppHasTargets = true; + break; + } + } + + boolean isCurse = false; + for (SpellAbility ability : aura.getAllSpellAbilities()) { + if (ability.isCurse() || (ability.hasParam("AILogic") && ability.getParam("AILogic").equals("Curse"))) { + isCurse = true; + break; + } + } + if (isCurse && oppHasTargets) { + hasTarget = true; + } else if (!isCurse && aiHasTargets) { + hasTarget = true; + } + + return hasTarget; + } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index d5c979ec672..2bd2184a6ab 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -1,8 +1,12 @@ package forge.ai; +import com.google.common.collect.Sets; +import forge.card.ColorSet; +import forge.game.GameActionUtil; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; +import forge.game.card.CardFactory; import forge.game.card.CardLists; import forge.game.card.CounterType; import forge.game.combat.Combat; @@ -14,6 +18,10 @@ import forge.game.zone.ZoneType; import forge.util.collect.FCollectionView; import forge.util.MyRandom; import forge.util.TextUtil; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -414,7 +422,15 @@ public class ComputerUtilCost { // If the new land size would equal the CMC of a card in AIs hand, play it untapped final int landsize = payer.getLandsInPlay().size() + 1; for (Card c : payer.getCardsIn(ZoneType.Hand)) { - if (landsize == c.getCMC()) { + int cmc = c.getCMC(); + // try to determine if the mana shards provided by the lands would be applicable to pay the mana cost + boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor()); + // try to determine that there is a valid target for a spell (very basic, consider expanding) + boolean requirementsMet = true; + if (c.isAura()) { + requirementsMet = ComputerUtil.hasGoodTargetForAura(payer, c); + } + if (landsize == cmc && canPay && requirementsMet) { return true; } } @@ -466,4 +482,29 @@ public class ComputerUtilCost { && (!source.getName().equals("Chain of Vapor") || (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3)); } + public static Set getAvailableManaColors(Player ai, Card additionalLand) { + return getAvailableManaColors(ai, Sets.newHashSet(additionalLand)); + } + + public static Set getAvailableManaColors(Player ai, Set additionalLands) { + CardCollection lands = ai.getLandsInPlay(); + Set colorsAvailable = new HashSet<>(); + + if (additionalLands != null) { + List additionalLandCopies = new ArrayList<>(); + for (Card c : additionalLands) { + additionalLandCopies.add(CardFactory.copyCard(c, true)); + } + GameActionUtil.grantBasicLandsManaAbilities(additionalLandCopies); + lands.addAll(additionalLandCopies); + } + + for (Card c : lands) { + for (SpellAbility sa : c.getManaAbilities()) { + colorsAvailable.add(sa.getManaPart().getOrigProduced()); + } + } + + return colorsAvailable; + } }