- A more advanced untap logic for cards like Voyaging Satyr that allows the AI to pool mana and untap lands for more in order to cast a bigger spell.

This commit is contained in:
Agetian
2017-10-16 06:21:29 +00:00
parent 6ec8b15a34
commit 70e4d80965
8 changed files with 106 additions and 24 deletions

View File

@@ -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<Card>() {
@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<Card>() {
@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<Card>() {
@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;
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.