diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 001e2328747..a9b97a8ff4a 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2424,6 +2424,18 @@ public class ComputerUtil { } } } + } else if (logic.equals("ProtectionFromType")) { + // TODO: protection vs. damage-dealing and milling instants/sorceries in low creature decks and the like? + // Maybe non-creature artifacts in certain cases? + List choices = ImmutableList.of("Creature", "Planeswalker"); // types that make sense to get protected against + CardCollection evalList = new CardCollection(); + + evalList.addAll(ai.getOpponents().getCardsIn(ZoneType.Battlefield)); + + chosen = ComputerUtilCard.getMostProminentCardType(evalList, choices); + if (StringUtils.isEmpty(chosen)) { + chosen = "Creature"; // if in doubt, choose Creature, I guess + } } else { // Are we picking a type to reduce costs for that type? diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index a54865c18d4..a223fddb600 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -28,6 +28,7 @@ import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbilityMustTarget; import forge.game.zone.ZoneType; @@ -116,6 +117,8 @@ public class ChangeZoneAi extends SpellAbilityAi { if (aiLogic != null) { if (aiLogic.equals("Always")) { return true; + } else if (aiLogic.startsWith("ExileSpell")) { + return doExileSpellLogic(aiPlayer, sa); } else if (aiLogic.startsWith("SacAndUpgrade")) { // Birthing Pod, Natural Order, etc. return doSacAndUpgradeLogic(aiPlayer, sa); } else if (aiLogic.startsWith("SacAndRetFromGrave")) { // Recurring Nightmare, etc. @@ -2075,6 +2078,36 @@ public class ChangeZoneAi extends SpellAbilityAi { } } + private boolean doExileSpellLogic(final Player aiPlayer, final SpellAbility sa) { + String aiLogic = sa.getParamOrDefault("AILogic", ""); + SpellAbilityStackInstance top = aiPlayer.getGame().getStack().peek(); + List dangerousApi = Arrays.asList(ApiType.DealDamage, ApiType.DamageAll, ApiType.Destroy, ApiType.DestroyAll, ApiType.Sacrifice, ApiType.SacrificeAll); + int manaCost = 0; + int minCost = 0; + + if (aiLogic.contains(".")) { + minCost = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1)); + } + + if (top != null) { + SpellAbility topSA = top.getSpellAbility(false); + if (topSA != null) { + if (topSA.getPayCosts().hasManaCost()) { + manaCost = topSA.getPayCosts().getTotalMana().getCMC(); + } + + if ((manaCost >= minCost || dangerousApi.contains(topSA.getApi())) + && topSA.getActivatingPlayer().isOpponentOf(aiPlayer) + && sa.canTargetSpellAbility(topSA)) { + sa.resetTargets(); + sa.getTargets().add(topSA); + return sa.isTargetNumberValid(); + } + } + } + return false; + } + private static CardCollection getSafeTargetsIfUnlessCostPaid(Player ai, SpellAbility sa, Iterable potentialTgts) { // Determines if the controller of each potential target can negate the ChangeZone effect // by paying the Unless cost. Returns the list of targets that can be saved that way. diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 5a76b6c82da..089c236120c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -114,13 +114,14 @@ public class SacrificeAi extends SpellAbilityAi { } final String defined = sa.getParamOrDefault("Defined", "You"); + final String targeted = sa.getParamOrDefault("ValidTgts", ""); final String valid = sa.getParamOrDefault("SacValid", "Self"); if (valid.equals("Self")) { // Self Sacrifice. - } else if (defined.equals("Player") + } else if (defined.equals("Player") || targeted.equals("Player") || targeted.equals("Opponent") || ((defined.equals("Player.Opponent") || defined.equals("Opponent")) && !sa.isTrigger())) { // is either "Defined$ Player.Opponent" or "Defined$ Opponent" obsolete? - + // If Sacrifice hits both players: // Only cast it if Human has the full amount of valid // Only cast it if AI doesn't have the full amount of Valid diff --git a/forge-gui/res/cardsfolder/c/consecrate_consume.txt b/forge-gui/res/cardsfolder/c/consecrate_consume.txt index 9678a44b8d1..7a0da4e56e0 100644 --- a/forge-gui/res/cardsfolder/c/consecrate_consume.txt +++ b/forge-gui/res/cardsfolder/c/consecrate_consume.txt @@ -11,11 +11,8 @@ ALTERNATE Name:Consume ManaCost:2 W B Types:Sorcery -A:SP$ Pump | Cost$ 2 W B | ValidTgts$ Player | IsCurse$ True | RememberTargets$ True | SubAbility$ DBChooseCard | SpellDescription$ Target player sacrifices a creature with the greatest power among creatures they control. You gain life equal to its power. -SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | Mandatory$ True | SubAbility$ DBSac -SVar:DBSac:DB$ Sacrifice | Defined$ Player.IsRemembered | SacValid$ Card.ChosenCard | RememberSacrificed$ True | SubAbility$ DBGainLife | SacMessage$ the creature with the highest power +A:SP$ Sacrifice | Cost$ 2 W B | ValidTgts$ Player | SacValid$ Creature.greatestPowerControlledByTargered | Mandatory$ True | SubAbility$ DBGainLife | SacMessage$ the creature with the highest power | RememberSacrificed$ True | SpellDescription$ Target player sacrifices a creature with the greatest power among creatures they control. You gain life equal to its power. SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:RememberedLKI$CardPower -SVar:SplitNeedsToPlay:Creature.OppCtrl Oracle:Target player sacrifices a creature with the greatest power among creatures they control. You gain life equal to its power. diff --git a/forge-gui/res/cardsfolder/g/gales_redirection.txt b/forge-gui/res/cardsfolder/g/gales_redirection.txt index 7ed1a535b27..94cc4f71ea6 100644 --- a/forge-gui/res/cardsfolder/g/gales_redirection.txt +++ b/forge-gui/res/cardsfolder/g/gales_redirection.txt @@ -1,7 +1,7 @@ Name:Gale's Redirection ManaCost:3 U U Types:Instant -A:SP$ ChangeZone | ValidTgts$ Card | TargetType$ Spell | TgtZone$ Stack | Origin$ Stack | Fizzle$ True | Destination$ Exile | TgtPrompt$ Choose target spell to exile | RememberChanged$ True | SubAbility$ DBRoll | SpellDescription$ Exile target spell. +A:SP$ ChangeZone | ValidTgts$ Card | TargetType$ Spell | TgtZone$ Stack | Origin$ Stack | Fizzle$ True | Destination$ Exile | AILogic$ ExileSpell.3 | TgtPrompt$ Choose target spell to exile | RememberChanged$ True | SubAbility$ DBRoll | SpellDescription$ Exile target spell. SVar:DBRoll:DB$ RollDice | Sides$ 20 | Modifier$ Y | ResultSubAbilities$ 1-14:DBMayPlay,Else:DBMayPlayWithoutCost | StackDescription$ SpellDescription | SpellDescription$ Roll a d20 and add that spell's mana value. SVar:DBMayPlay:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered | Duration$ Permanent | ExileOnMoved$ Exile | SubAbility$ DBCleanup | SpellDescription$ 1—14 VERT You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast it. SVar:DBMayPlayWithoutCost:DB$ Effect | StaticAbilities$ STPlayWithoutCost | RememberObjects$ Remembered | Duration$ Permanent | ExileOnMoved$ Exile | SubAbility$ DBCleanup | SpellDescription$ 15+ VERT You may cast that card without paying its mana cost for as long as it remains exiled. @@ -9,5 +9,4 @@ SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | Effect SVar:STPlayWithoutCost:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may cast that card without paying its mana cost for as long as it remains exiled. SVar:Y:SpellTargeted$CardManaCostLKI SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -AI:RemoveDeck:All Oracle:Exile target spell, then roll a d20 and add that spell's mana value.\n1-14 | You may cast the exiled card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell.\n15+ | You may cast the exiled card without paying its mana cost for as long as it remains exiled. diff --git a/forge-gui/res/cardsfolder/s/serras_emissary.txt b/forge-gui/res/cardsfolder/s/serras_emissary.txt index 93190eed563..1a00bd26fdd 100644 --- a/forge-gui/res/cardsfolder/s/serras_emissary.txt +++ b/forge-gui/res/cardsfolder/s/serras_emissary.txt @@ -4,7 +4,7 @@ Types:Creature Angel PT:7/7 K:Flying K:ETBReplacement:Other:ChooseCT -SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Card | AILogic$ MostProminentOppControls | SpellDescription$ As CARDNAME enters the battlefield, choose a card type. +SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Card | AILogic$ ProtectionFromType | SpellDescription$ As CARDNAME enters the battlefield, choose a card type. S:Mode$ Continuous | Affected$ You,Creature.YouCtrl | AddKeyword$ Protection from ChosenType | Description$ You and creatures you control have protection from the chosen card type. SVar:PlayMain1:TRUE Oracle:Flying\nAs Serra's Emissary enters the battlefield, choose a card type.\nYou and creatures you control have protection from the chosen card type.