mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
- 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:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user