diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java index 290744383e4..413e1c45d1f 100644 --- a/forge-ai/src/main/java/forge/ai/AiProps.java +++ b/forge-ai/src/main/java/forge/ai/AiProps.java @@ -129,7 +129,10 @@ public enum AiProps { /** */ FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"), FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"), FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"), - FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"); /** */ + FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"), + BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */ + BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */ + BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"); /** */ // Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting // <-- There are no experimental options here --> 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 9085d922e54..8f8e2240660 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -26,6 +26,7 @@ import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; +import forge.util.MyRandom; import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -677,7 +678,7 @@ public class ChangeZoneAi extends SpellAbilityAi { // only use blink or bounce effects if (!(destination.equals(ZoneType.Exile) - && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBounce".equals(sa.getParam("AILogic")))) + && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic")))) && !destination.equals(ZoneType.Hand)) { return false; } @@ -937,7 +938,7 @@ public class ChangeZoneAi extends SpellAbilityAi { // if it's blink or bounce, try to save my about to die stuff final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger - || "DelayedBounce".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered")))); + || "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered")))); if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) { // save my about to die stuff Card tobounce = canBouncePermanent(ai, sa, list); @@ -1231,6 +1232,7 @@ public class ChangeZoneAi extends SpellAbilityAi { // filter out untargetables CardCollectionView aiPermanents = CardLists .filterControlledBy(list, ai); + CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS); // Felidar Guardian + Saheeli Rai combo support if (sa.getHostCard().getName().equals("Felidar Guardian")) { @@ -1274,6 +1276,33 @@ public class ChangeZoneAi extends SpellAbilityAi { } } } + // Reload planeswalkers + else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) { + int maxLoyaltyToConsider = 2; + int loyaltyDiff = 2; + int chance = 30; + if (ai.getController().isAI()) { + AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); + maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY); + loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF); + chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE); + } + if (MyRandom.percentTrue(chance)) { + Collections.sort(aiPlaneswalkers, new Comparator() { + @Override + public int compare(final Card a, final Card b) { + return a.getCounters(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY); + } + }); + for (Card pw : aiPlaneswalkers) { + int curLoyalty = pw.getCounters(CounterType.LOYALTY); + int freshLoyalty = pw.getCurrentState().getBaseLoyalty(); + if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) { + return pw; + } + } + } + } return null; } diff --git a/forge-gui/res/ai/Cautious.ai b/forge-gui/res/ai/Cautious.ai index 2cc850ed726..f276aa1c9c9 100644 --- a/forge-gui/res/ai/Cautious.ai +++ b/forge-gui/res/ai/Cautious.ai @@ -252,6 +252,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200 BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3 BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3 +# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist) +# A chance (per check) to activate this ability +BLINK_RELOAD_PLANESWALKER_CHANCE=15 +# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload +BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2 +# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it +BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=2 + # If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked, # it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix # combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index 2801deae8af..060ebb5f114 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200 BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3 BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3 +# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist) +# A chance (per check) to activate this ability +BLINK_RELOAD_PLANESWALKER_CHANCE=30 +# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload +BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2 +# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it +BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=2 + # If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked, # it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix # combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index 17744a3c4a1..c5592fa230c 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=400 BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=5 BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=5 +# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist) +# A chance (per check) to activate this ability +BLINK_RELOAD_PLANESWALKER_CHANCE=70 +# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload +BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2 +# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it +BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=1 + # If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked, # it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix # combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index e2771362cd8..97cc512165f 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200 BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3 BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3 +# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist) +# A chance (per check) to activate this ability +BLINK_RELOAD_PLANESWALKER_CHANCE=60 +# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload +BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2 +# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it +BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=1 + # If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked, # it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix # combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the diff --git a/forge-gui/res/cardsfolder/t/teferis_time_twist.txt b/forge-gui/res/cardsfolder/t/teferis_time_twist.txt index cfa594d4401..0d474640a75 100644 --- a/forge-gui/res/cardsfolder/t/teferis_time_twist.txt +++ b/forge-gui/res/cardsfolder/t/teferis_time_twist.txt @@ -1,7 +1,7 @@ Name:Teferi's Time Twist ManaCost:1 U Types:Instant -A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | AILogic$ DelayedBounce | SubAbility$ DBAnimate | SpellDescription$ Exile target permanent you control. Return that card to the battlefield under its owner’s control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it. +A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | AILogic$ DelayedBlink | SubAbility$ DBAnimate | SpellDescription$ Exile target permanent you control. Return that card to the battlefield under its owner’s control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it. SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Keywords$ etbCounter:P1P1:1:ValidCard$ Creature.IsRemembered | RememberObjects$ Remembered | SubAbility$ DelTrig | AILogic$ Always SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigBounce | RememberObjects$ Remembered | AILogic$ Always | TriggerDescription$ Return that card to the battlefield under its owner’s control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it. SVar:TrigBounce:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ DelayTriggerRemembered