diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java index 5594de7300e..05cff5918e4 100644 --- a/forge-ai/src/main/java/forge/ai/AiProps.java +++ b/forge-ai/src/main/java/forge/ai/AiProps.java @@ -120,10 +120,10 @@ public enum AiProps { /** */ FLASH_CHANCE_TO_CAST_FOR_ETB_BEFORE_MAIN1("10"), /** */ FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB("0"), /** */ FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER("100"), - FLASH_USE_AURAS_AS_COMBAT_TRICKS("true"), - FLASH_AURA_CHANCE_TO_CAST_EARLY("0"), - FLASH_AURA_CHANCE_CAST_AT_EOT("10"), - FLASH_AURA_CHANCE_TO_RESPOND_TO_STACK("0"); /** */ + FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"), + FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("0"), + FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("10"), + FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("0"); /** */ // 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/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 7b74dd97518..48206f0f303 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -2,6 +2,7 @@ package forge.ai.ability; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.Lists; import forge.ai.*; import forge.game.Game; import forge.game.GameObject; @@ -49,15 +50,6 @@ public class AttachAi extends SpellAbilityAi { return false; } - // Flash logic - boolean advancedFlash = false; - if (ai.getController().isAI()) { - advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC); - } - if (source.withFlash(ai) && source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai)) { - return false; - } - if (abCost != null) { // AI currently disabled for these costs if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) { @@ -97,6 +89,15 @@ public class AttachAi extends SpellAbilityAi { } } + // Flash logic + boolean advancedFlash = false; + if (ai.getController().isAI()) { + advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC); + } + if (source.withFlash(ai) && source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) { + return false; + } + if (abCost.getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. (Endless Scream and Venarian // Gold) @@ -128,26 +129,72 @@ public class AttachAi extends SpellAbilityAi { return true; } - private boolean doAdvancedFlashAuraLogic(Player ai) { + private boolean doAdvancedFlashAuraLogic(Player ai, SpellAbility sa, Card attachTarget) { + Card source = sa.getHostCard(); Game game = ai.getGame(); Combat combat = game.getCombat(); AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); + + if (!aic.getBooleanProperty(AiProps.FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS)) { + // Currently this only works with buff auras, so if the relevant toggle is disabled, just return true + // for instant speed use. To be improved later. + return true; + } + + int power = 0, toughness = 0; + List keywords = Lists.newArrayList(); + for (StaticAbility stAb : source.getStaticAbilities()) { + if ("Continuous".equals(stAb.getParam("Mode"))) { + if (stAb.hasParam("AddPower")) { + power += AbilityUtils.calculateAmount(source, stAb.getParam("AddPower"), stAb); + } + if (stAb.hasParam("AddToughness")) { + toughness += AbilityUtils.calculateAmount(source, stAb.getParam("AddToughness"), stAb); + } + if (stAb.hasParam("AddKeyword")) { + keywords.addAll(Lists.newArrayList(stAb.getParam("AddKeyword").split(" & "))); + } + } + } + + boolean isBuffAura = !sa.isCurse() && (power > 0 || toughness > 0 || !keywords.isEmpty()); + if (!isBuffAura) { + // Currently only works with buff auras, otherwise returns for instant speed use. To be improved later. + return true; + } + boolean canRespondToStack = false; if (!game.getStack().isEmpty()) { SpellAbility peekSa = game.getStack().peekAbility(); Player activator = peekSa.getActivatingPlayer(); - if (activator != null && activator.isOpponentOf(ai) && peekSa.getApi() != ApiType.DestroyAll && - peekSa.getApi() != ApiType.Destroy) { - // TODO: improve this so that the AI predicts how much damage will be dealt to the creature - // so that it can try to save it (and won't bother targeting it if it can't be saved) - canRespondToStack = true; + if (activator != null && activator.isOpponentOf(ai) + && (!peekSa.usesTargeting() || peekSa.getTargets().getTargetCards().contains(attachTarget))) { + if (peekSa.getApi() == ApiType.DealDamage || peekSa.getApi() == ApiType.DamageAll) { + int dmg = AbilityUtils.calculateAmount(peekSa.getHostCard(), peekSa.getParam("NumDmg"), peekSa); + if (dmg < toughness + attachTarget.getNetToughness()) { + canRespondToStack = true; + } + } else if (peekSa.getApi() == ApiType.Destroy || peekSa.getApi() == ApiType.DestroyAll) { + if (!attachTarget.hasKeyword(Keyword.INDESTRUCTIBLE) && !ComputerUtil.canRegenerate(ai, attachTarget) + && keywords.contains("Indestructible")) { + canRespondToStack = true; + } + } else if (peekSa.getApi() == ApiType.Pump || peekSa.getApi() == ApiType.PumpAll) { + int p = AbilityUtils.calculateAmount(peekSa.getHostCard(), peekSa.getParam("NumAtt"), peekSa); + int t = AbilityUtils.calculateAmount(peekSa.getHostCard(), peekSa.getParam("NumDef"), peekSa); + if (t < 0 && toughness > 0 && attachTarget.getNetToughness() + t + toughness > 0) { + canRespondToStack = true; + } else if (p < 0 && power > 0 && attachTarget.getNetToughness() + t + toughness > 0) { + // Yep, still need to ensure that the net toughness will be positive here even if buffing for power + canRespondToStack = true; + } + } } } - boolean useAurasAsTricks = aic.getBooleanProperty(AiProps.FLASH_USE_AURAS_AS_COMBAT_TRICKS); - int chanceToCastAtEOT = aic.getIntProperty(AiProps.FLASH_AURA_CHANCE_CAST_AT_EOT); - int chanceToCastEarly = aic.getIntProperty(AiProps.FLASH_AURA_CHANCE_TO_CAST_EARLY); - int chanceToRespondToStack = aic.getIntProperty(AiProps.FLASH_AURA_CHANCE_TO_RESPOND_TO_STACK); + int chanceToCastAtEOT = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT); + int chanceToCastEarly = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY); + int chanceToRespondToStack = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK); boolean hasFloatMana = ai.getManaPool().totalMana() > 0; boolean willDiscardNow = game.getPhaseHandler().is(PhaseType.END_OF_TURN, ai) @@ -155,11 +202,11 @@ public class AttachAi extends SpellAbilityAi { boolean willRespondToStack = canRespondToStack && MyRandom.percentTrue(chanceToRespondToStack); boolean willCastEarly = MyRandom.percentTrue(chanceToCastEarly); boolean willCastAtEOT = game.getPhaseHandler().is(PhaseType.END_OF_TURN) - && game.getPhaseHandler().getNextTurn().equals(ai) && MyRandom.percentTrue(chanceToCastAtEOT) || !useAurasAsTricks; + && game.getPhaseHandler().getNextTurn().equals(ai) && MyRandom.percentTrue(chanceToCastAtEOT); boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willRespondToStack || willCastAtEOT || willCastEarly; - if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) { + if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java index 25fd2b80f12..49aaff7b37f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java @@ -123,7 +123,8 @@ public class PermanentCreatureAi extends PermanentAi { if (!game.getStack().isEmpty()) { SpellAbility peekSa = game.getStack().peekAbility(); Player activator = peekSa.getActivatingPlayer(); - if (activator != null && activator.isOpponentOf(ai) && peekSa.getApi() != ApiType.DestroyAll) { + if (activator != null && activator.isOpponentOf(ai) && peekSa.getApi() != ApiType.DestroyAll + && peekSa.getApi() != ApiType.DamageAll) { canRespondToStack = true; } } diff --git a/forge-gui/res/ai/Cautious.ai b/forge-gui/res/ai/Cautious.ai index 9c286a738cb..6c765eeb1a7 100644 --- a/forge-gui/res/ai/Cautious.ai +++ b/forge-gui/res/ai/Cautious.ai @@ -171,14 +171,13 @@ FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB=0 FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER=100 # If enabled, the AI will try to hold off playing auras with Flash until the declare blockers step in combat. # If disabled, but advanced logic is enabled, will generally try to play these auras before its own turn. -FLASH_USE_AURAS_AS_COMBAT_TRICKS=true +FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS=true # The chance that the AI will cast a flash aura enchantment at the earliest opportunity -FLASH_AURA_CHANCE_TO_CAST_EARLY=0 +FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY=0 # The chance that the AI will cast a flash aura at the end of turn before its own turn -FLASH_AURA_CHANCE_CAST_AT_EOT=5 -# The chance that the AI will respond to stack with a flash aura (may do silly things sometimes, so better left -# disabled or at a low chance until improved) -FLASH_AURA_CHANCE_TO_RESPOND_TO_STACK=0 +FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT=5 +# The chance that the AI will respond to stack with a flash aura which makes sense in context +FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK=100 # Scry AI toggles # The total number of mana-producing lands at which the AI will still consider scrying away non-lands diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index fd0f751a028..e4844b9eeac 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -172,14 +172,13 @@ FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB=0 FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER=100 # If enabled, the AI will try to hold off playing auras with Flash until the declare blockers step in combat. # If disabled, but advanced logic is enabled, will generally try to play these auras before its own turn. -FLASH_USE_AURAS_AS_COMBAT_TRICKS=true +FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS=true # The chance that the AI will cast a flash aura enchantment at the earliest opportunity -FLASH_AURA_CHANCE_TO_CAST_EARLY=5 +FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY=1 # The chance that the AI will cast a flash aura at the end of turn before its own turn -FLASH_AURA_CHANCE_CAST_AT_EOT=10 -# The chance that the AI will respond to stack with a flash aura (may do silly things sometimes, so better left -# disabled or at a low chance until improved) -FLASH_AURA_CHANCE_TO_RESPOND_TO_STACK=0 +FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT=5 +# The chance that the AI will respond to stack with a flash aura which makes sense in context +FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK=100 # Scry AI toggles # The total number of mana-producing lands at which the AI will still consider scrying away non-lands diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index addc695a53e..aa49163c7bb 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -170,18 +170,15 @@ FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB=15 # The chance the AI will attempt to add a new blocker in combat where it's low on creature count compared to the # number of attacking creatures. FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER=100 -# The chance that the AI will try to hold off playing auras with Flash until the declare blockers step in combat. -FLASH_CHANCE_TO_USE_AURAS_AS_COMBAT_TRICKS=100 # If enabled, the AI will try to hold off playing auras with Flash until the declare blockers step in combat. # If disabled, but advanced logic is enabled, will generally try to play these auras before its own turn. -FLASH_USE_AURAS_AS_COMBAT_TRICKS=true +FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS=true # The chance that the AI will cast a flash aura enchantment at the earliest opportunity -FLASH_AURA_CHANCE_TO_CAST_EARLY=0 +FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY=0 # The chance that the AI will cast a flash aura at the end of turn before its own turn -FLASH_AURA_CHANCE_CAST_AT_EOT=10 -# The chance that the AI will respond to stack with a flash aura (may do silly things sometimes, so better left -# disabled or at a low chance until improved) -FLASH_AURA_CHANCE_TO_RESPOND_TO_STACK=100 +FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT=10 +# The chance that the AI will respond to stack with a flash aura which makes sense in context +FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK=100 # Scry AI toggles # The total number of mana-producing lands at which the AI will still consider scrying away non-lands diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index 173b62fd4b4..186c1cf1c7e 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -172,14 +172,13 @@ FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB=10 FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER=100 # If enabled, the AI will try to hold off playing auras with Flash until the declare blockers step in combat. # If disabled, but advanced logic is enabled, will generally try to play these auras before its own turn. -FLASH_USE_AURAS_AS_COMBAT_TRICKS=true +FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS=true # The chance that the AI will cast a flash aura enchantment at the earliest opportunity -FLASH_AURA_CHANCE_TO_CAST_EARLY=10 +FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY=3 # The chance that the AI will cast a flash aura at the end of turn before its own turn -FLASH_AURA_CHANCE_CAST_AT_EOT=30 -# The chance that the AI will respond to stack with a flash aura (may do silly things sometimes, so better left -# disabled or at a low chance until improved) -FLASH_AURA_CHANCE_TO_RESPOND_TO_STACK=10 +FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT=10 +# The chance that the AI will respond to stack with a flash aura which makes sense in context +FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK=100 # Scry AI toggles # The total number of mana-producing lands at which the AI will still consider scrying away non-lands