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;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
|
import forge.card.mana.ManaCostShard;
|
||||||
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -10,6 +14,7 @@ import forge.game.card.CardPredicates;
|
|||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostTap;
|
import forge.game.cost.CostTap;
|
||||||
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -24,9 +29,11 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
final Card source = sa.getHostCard();
|
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))) {
|
|| !source.getGame().getPhaseHandler().getPhase().equals(PhaseType.END_OF_TURN))) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if ("PoolExtraMana".equals(aiLogic)) {
|
||||||
|
return doPoolExtraManaLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !("Never".equals(aiLogic));
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,4 +355,95 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return null;
|
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
|
ManaCost:G
|
||||||
Types:Creature Elf Druid
|
Types:Creature Elf Druid
|
||||||
PT:1/1
|
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
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/arbor_elf.jpg
|
||||||
Oracle:{T}: Untap target Forest.
|
Oracle:{T}: Untap target Forest.
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Blossom Dryad
|
|||||||
ManaCost:2 G
|
ManaCost:2 G
|
||||||
Types:Creature Dryad
|
Types:Creature Dryad
|
||||||
PT:2/2
|
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
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/blossom_dryad.jpg
|
||||||
Oracle:{T}: Untap target land.
|
Oracle:{T}: Untap target land.
|
||||||
@@ -2,7 +2,7 @@ Name:Greenside Watcher
|
|||||||
ManaCost:1 G
|
ManaCost:1 G
|
||||||
Types:Creature Elf Druid
|
Types:Creature Elf Druid
|
||||||
PT:2/1
|
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
|
DeckNeeds:Type$Gate
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/greenside_watcher.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/greenside_watcher.jpg
|
||||||
Oracle:{T}: Untap target Gate.
|
Oracle:{T}: Untap target Gate.
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Juniper Order Druid
|
|||||||
ManaCost:2 G
|
ManaCost:2 G
|
||||||
Types:Creature Human Cleric Druid
|
Types:Creature Human Cleric Druid
|
||||||
PT:1/1
|
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
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/juniper_order_druid.jpg
|
||||||
Oracle:{T}: Untap target land.
|
Oracle:{T}: Untap target land.
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Ley Druid
|
|||||||
ManaCost:2 G
|
ManaCost:2 G
|
||||||
Types:Creature Human Druid
|
Types:Creature Human Druid
|
||||||
PT:1/1
|
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
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/ley_druid.jpg
|
||||||
Oracle:{T}: Untap target land.
|
Oracle:{T}: Untap target land.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:2 G G
|
|||||||
Types:Creature Human Druid
|
Types:Creature Human Druid
|
||||||
PT:1/1
|
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.
|
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:TrigUntap:DB$Untap | Defined$ Self
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/stone_seeder_hierophant.jpg
|
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.
|
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
|
ManaCost:1 G
|
||||||
Types:Creature Satyr Druid
|
Types:Creature Satyr Druid
|
||||||
PT:1/2
|
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
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/voyaging_satyr.jpg
|
||||||
Oracle:{T}: Untap target land.
|
Oracle:{T}: Untap target land.
|
||||||
|
|||||||
Reference in New Issue
Block a user