diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 7b55dcd72a8..621a5bc03ef 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -24,6 +24,7 @@ public enum SpellApiToAi { .put(ApiType.AddPhase, AddPhaseAi.class) .put(ApiType.AddTurn, AddTurnAi.class) .put(ApiType.AdvanceCrank, AdvanceCrankAi.class) + .put(ApiType.Airbend, AirbendAi.class) .put(ApiType.AlterAttribute, AlterAttributeAi.class) .put(ApiType.Amass, AmassAi.class) .put(ApiType.Animate, AnimateAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/AirbendAi.java b/forge-ai/src/main/java/forge/ai/ability/AirbendAi.java new file mode 100644 index 00000000000..ccc62f0a239 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/AirbendAi.java @@ -0,0 +1,53 @@ +package forge.ai.ability; + +import forge.ai.*; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; +import forge.game.combat.Combat; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +public class AirbendAi extends SpellAbilityAi { + @Override + protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) { + // Check own cards that need saving, non-token, above CMC 2 so that it's hopefully worth saving this one + final Combat combat = aiPlayer.getGame().getCombat(); + final CardCollection threatenedTgts = CardLists.filter(aiPlayer.getCreaturesInPlay(), + card -> !card.isToken() && card.getCMC() > 2 && + (ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(card) + || (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(aiPlayer, card, combat)))); + if (!threatenedTgts.isEmpty()) { + Card bestSaved = ComputerUtilCard.getBestAI(threatenedTgts); + sa.getTargets().add(bestSaved); + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); + } + + // Check opponent's cards that need bouncing (only in the AI's own turn, main phase 1, or at the end of opponent's + // turn, to get rid of potential blockers) + PhaseHandler ph = aiPlayer.getGame().getPhaseHandler(); + if (ph.is(PhaseType.MAIN1, aiPlayer) || (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer)) { + final CardCollection opposingThreats = aiPlayer.getOpponents().getCreaturesInPlay(); + if (!opposingThreats.isEmpty()) { + sa.getTargets().add(ComputerUtilCard.getBestAI(opposingThreats)); + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); + } + } + + // TODO: add logic to use it to remove threatening spells when the ability allows to target spells? + + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + } + + @Override + protected AiAbilityDecision doTriggerNoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { + AiAbilityDecision decision = canPlay(aiPlayer, sa); + if (decision.willingToPlay() || mandatory) { + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); + } + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + } + +} diff --git a/forge-ai/src/main/java/forge/ai/ability/EarthbendAi.java b/forge-ai/src/main/java/forge/ai/ability/EarthbendAi.java index 4b8c91d0ed9..cc5fb96b78a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EarthbendAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EarthbendAi.java @@ -1,27 +1,50 @@ package forge.ai.ability; -import forge.ai.AiAbilityDecision; -import forge.ai.AiPlayDecision; -import forge.ai.ComputerUtilCard; -import forge.ai.SpellAbilityAi; +import forge.ai.*; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; -import forge.game.card.CardPredicates; +import forge.game.cost.Cost; +import forge.game.cost.CostPart; +import forge.game.cost.CostSacrifice; import forge.game.player.Player; import forge.game.spellability.SpellAbility; public class EarthbendAi extends SpellAbilityAi { @Override protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) { - CardCollection nonAnimatedLands = CardLists.filter(aiPlayer.getLandsInPlay(), CardPredicates.NON_CREATURES); - - if (nonAnimatedLands.isEmpty()) { + CardCollection lands = aiPlayer.getLandsInPlay(); + if (lands.isEmpty()) { return new AiAbilityDecision(0, AiPlayDecision.AnotherTime); } + CardCollection fetchLands = CardLists.filter(lands, c -> { + for (final SpellAbility ability : c.getAllSpellAbilities()) { + if (ability.isActivatedAbility()) { + final Cost cost = ability.getPayCosts(); + for (final CostPart part : cost.getCostParts()) { + if (!(part instanceof CostSacrifice)) { + continue; + } + CostSacrifice sacCost = (CostSacrifice) part; + if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) { + return true; + } + } + } + } + return false; + }); - Card bestToAnimate = ComputerUtilCard.getBestLandToAnimate(nonAnimatedLands); - sa.getTargets().add(bestToAnimate); + Card tgtLand = null; + + if (!fetchLands.isEmpty()) { + // Prioritize fetchlands as they can be reused later + tgtLand = ComputerUtilCard.getBestLandToAnimate(fetchLands); + } else { + tgtLand = ComputerUtilCard.getBestLandToAnimate(lands); + } + + sa.getTargets().add(tgtLand); return new AiAbilityDecision(100, AiPlayDecision.WillPlay); }