diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java index 14d643c8257..92096b1d502 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java @@ -1,6 +1,10 @@ package forge.ai.ability; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import forge.ai.*; +import forge.card.mana.ManaCostShard; +import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; @@ -10,6 +14,7 @@ import forge.game.card.CardPredicates; import forge.game.card.CardPredicates.Presets; import forge.game.cost.Cost; import forge.game.cost.CostTap; +import forge.game.mana.ManaCostBeingPaid; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -24,9 +29,11 @@ public class UntapAi extends SpellAbilityAi { @Override protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { final Card source = sa.getHostCard(); - if ("EOT".equals(sa.getParam("AILogic")) && (source.getGame().getPhaseHandler().getNextTurn() != ai + if ("EOT".equals(aiLogic) && (source.getGame().getPhaseHandler().getNextTurn() != ai || !source.getGame().getPhaseHandler().getPhase().equals(PhaseType.END_OF_TURN))) { return false; + } else if ("PoolExtraMana".equals(aiLogic)) { + return doPoolExtraManaLogic(ai, sa); } return !("Never".equals(aiLogic)); @@ -65,22 +72,6 @@ public class UntapAi extends SpellAbilityAi { } } - if (source != null && source.isCreature() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) { - if (sa.getTargetRestrictions() != null && !sa.getTargetRestrictions().canTgtCreature()) { - // Voyaging Satyr and friends: only do it after attacking/blocking and not when in immediate danger - PhaseHandler ph = source.getGame().getPhaseHandler(); - if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - return false; - } - - if (ai.getLife() < ai.getStartingLife() / 4 - && (ai.getLifeLostLastTurn() > 0 || ai.getLifeLostThisTurn() > 0 || - (ph.getPlayerTurn().isOpponentOf(ai)) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) { - return false; - } - } - } - return true; } @@ -364,4 +355,95 @@ public class UntapAi extends SpellAbilityAi { return null; } + + private boolean doPoolExtraManaLogic(Player ai, SpellAbility sa) { + final Card source = sa.getHostCard(); + final PhaseHandler ph = source.getGame().getPhaseHandler(); + final Game game = ai.getGame(); + + if (sa.getHostCard().isTapped()) { + return true; + } + + // Check if something is playable if we untap for an additional mana with this, then proceed + CardCollection inHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS)); + // The AI is not very good at timing instants this way, so filter them out + CardCollection playable = CardLists.filter(inHand, Predicates.not(CardPredicates.isType("Instant"))); + + CardCollection untappingCards = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate() { + @Override + public boolean apply(Card card) { + boolean hasUntapLandLogic = false; + for (SpellAbility sa : card.getSpellAbilities()) { + if ("PoolExtraMana".equals(sa.getParam("AILogic"))) { + hasUntapLandLogic = true; + break; + } + } + return hasUntapLandLogic && card.isUntapped(); + } + }); + + // TODO: currently limited to Main 2, somehow improve to let the AI use this SA at other time? + if (ph.is(PhaseType.MAIN2, ai)) { + for (Card c : playable) { + for (SpellAbility ab : c.getBasicSpells()) { + if (!ComputerUtilMana.hasEnoughManaSourcesToCast(ab, ai)) { + // TODO: Currently limited to predicting something that can be paid with any color, + // can ideally be improved to work by color. + ManaCostBeingPaid reduced = new ManaCostBeingPaid(ab.getPayCosts().getCostMana().getManaCostFor(ab), ab.getPayCosts().getCostMana().getRestiction()); + reduced.decreaseShard(ManaCostShard.GENERIC, untappingCards.size()); + if (ComputerUtilMana.canPayManaCost(reduced, ab, ai)) { + CardCollection manaLandsTapped = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), + Predicates.and(Presets.LANDS_PRODUCING_MANA, Presets.TAPPED)); + manaLandsTapped = CardLists.filter(manaLandsTapped, new Predicate() { + @Override + public boolean apply(Card card) { + return card.isValid(sa.getParam("ValidTgts"), ai, source, null); + } + }); + + if (!manaLandsTapped.isEmpty()) { + // already have a tapped land, so agree to proceed with untapping it + return true; + } + + // pool one additional mana by tapping a land to try to ramp to something + CardCollection manaLands = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), + Predicates.and(Presets.LANDS_PRODUCING_MANA, Presets.UNTAPPED)); + manaLands = CardLists.filter(manaLands, new Predicate() { + @Override + public boolean apply(Card card) { + return card.isValid(sa.getParam("ValidTgts"), ai, source, null); + } + }); + + if (manaLands.isEmpty()) { + // nothing to untap + return false; + } + + Card landToPool = manaLands.getFirst(); + SpellAbility manaAb = landToPool.getManaAbilities().getFirst(); + + ComputerUtil.playNoStack(ai, manaAb, game); + + return true; + } + } + } + } + } + + // no harm in doing this past declare blockers during the opponent's turn and right before our turn, + // maybe we'll serendipitously untap into something like a removal spell or burn spell that'll help + if (ph.getNextTurn() == ai + && (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) { + return true; + } + + // haven't found any immediate playable options + return false; + } + } diff --git a/forge-gui/res/cardsfolder/a/arbor_elf.txt b/forge-gui/res/cardsfolder/a/arbor_elf.txt index 2d141106634..5c69d79921c 100644 --- a/forge-gui/res/cardsfolder/a/arbor_elf.txt +++ b/forge-gui/res/cardsfolder/a/arbor_elf.txt @@ -2,6 +2,6 @@ Name:Arbor Elf ManaCost:G Types:Creature Elf Druid PT:1/1 -A:AB$ Untap | Cost$ T | ValidTgts$ Forest | TgtPrompt$ Select target forest | SpellDescription$ Untap target Forest. +A:AB$ Untap | Cost$ T | ValidTgts$ Forest | TgtPrompt$ Select target forest | AILogic$ PoolExtraMana | SpellDescription$ Untap target Forest. SVar:Picture:http://www.wizards.com/global/images/magic/general/arbor_elf.jpg Oracle:{T}: Untap target Forest. diff --git a/forge-gui/res/cardsfolder/b/blossom_dryad.txt b/forge-gui/res/cardsfolder/b/blossom_dryad.txt index 803341f7910..224d2b686e4 100644 --- a/forge-gui/res/cardsfolder/b/blossom_dryad.txt +++ b/forge-gui/res/cardsfolder/b/blossom_dryad.txt @@ -2,6 +2,6 @@ Name:Blossom Dryad ManaCost:2 G Types:Creature Dryad PT:2/2 -A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | SpellDescription$ Untap target land. +A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ PoolExtraMana | SpellDescription$ Untap target land. SVar:Picture:http://www.wizards.com/global/images/magic/general/blossom_dryad.jpg Oracle:{T}: Untap target land. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/g/greenside_watcher.txt b/forge-gui/res/cardsfolder/g/greenside_watcher.txt index bd475b78c83..ed5900a19b8 100644 --- a/forge-gui/res/cardsfolder/g/greenside_watcher.txt +++ b/forge-gui/res/cardsfolder/g/greenside_watcher.txt @@ -2,7 +2,7 @@ Name:Greenside Watcher ManaCost:1 G Types:Creature Elf Druid PT:2/1 -A:AB$ Untap | Cost$ T | ValidTgts$ Gate | TgtPrompt$ Select target gate | SpellDescription$ Untap target Gate. +A:AB$ Untap | Cost$ T | ValidTgts$ Gate | TgtPrompt$ Select target gate | AILogic$ PoolExtraMana | SpellDescription$ Untap target Gate. DeckNeeds:Type$Gate SVar:Picture:http://www.wizards.com/global/images/magic/general/greenside_watcher.jpg Oracle:{T}: Untap target Gate. diff --git a/forge-gui/res/cardsfolder/j/juniper_order_druid.txt b/forge-gui/res/cardsfolder/j/juniper_order_druid.txt index 749819d7b7b..9a71933e76f 100644 --- a/forge-gui/res/cardsfolder/j/juniper_order_druid.txt +++ b/forge-gui/res/cardsfolder/j/juniper_order_druid.txt @@ -2,6 +2,6 @@ Name:Juniper Order Druid ManaCost:2 G Types:Creature Human Cleric Druid PT:1/1 -A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | SpellDescription$ Untap target land. +A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ PoolExtraMana | SpellDescription$ Untap target land. SVar:Picture:http://www.wizards.com/global/images/magic/general/juniper_order_druid.jpg Oracle:{T}: Untap target land. diff --git a/forge-gui/res/cardsfolder/l/ley_druid.txt b/forge-gui/res/cardsfolder/l/ley_druid.txt index 2b69dbc2a64..ba68da4e772 100644 --- a/forge-gui/res/cardsfolder/l/ley_druid.txt +++ b/forge-gui/res/cardsfolder/l/ley_druid.txt @@ -2,6 +2,6 @@ Name:Ley Druid ManaCost:2 G Types:Creature Human Druid PT:1/1 -A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | SpellDescription$ Untap target land. +A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ PoolExtraMana | SpellDescription$ Untap target land. SVar:Picture:http://www.wizards.com/global/images/magic/general/ley_druid.jpg Oracle:{T}: Untap target land. diff --git a/forge-gui/res/cardsfolder/s/stone_seeder_hierophant.txt b/forge-gui/res/cardsfolder/s/stone_seeder_hierophant.txt index 9c3023ae809..05b0623e95a 100644 --- a/forge-gui/res/cardsfolder/s/stone_seeder_hierophant.txt +++ b/forge-gui/res/cardsfolder/s/stone_seeder_hierophant.txt @@ -3,7 +3,7 @@ ManaCost:2 G G Types:Creature Human Druid PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | Execute$ TrigUntap | TriggerZones$ Battlefield | TriggerDescription$ Whenever a land enters the battlefield under your control, untap CARDNAME. -A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | SpellDescription$ Untap target land. +A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ PoolExtraMana | SpellDescription$ Untap target land. SVar:TrigUntap:DB$Untap | Defined$ Self SVar:Picture:http://www.wizards.com/global/images/magic/general/stone_seeder_hierophant.jpg Oracle:Whenever a land enters the battlefield under your control, untap Stone-Seeder Hierophant.\n{T}: Untap target land. diff --git a/forge-gui/res/cardsfolder/v/voyaging_satyr.txt b/forge-gui/res/cardsfolder/v/voyaging_satyr.txt index 2c380fa864c..244ff6676eb 100644 --- a/forge-gui/res/cardsfolder/v/voyaging_satyr.txt +++ b/forge-gui/res/cardsfolder/v/voyaging_satyr.txt @@ -2,6 +2,6 @@ Name:Voyaging Satyr ManaCost:1 G Types:Creature Satyr Druid PT:1/2 -A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | SpellDescription$ Untap target land. +A:AB$ Untap | Cost$ T | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ PoolExtraMana | SpellDescription$ Untap target land. SVar:Picture:http://www.wizards.com/global/images/magic/general/voyaging_satyr.jpg Oracle:{T}: Untap target land.