diff --git a/forge-ai/src/main/java/forge/ai/AiCardMemory.java b/forge-ai/src/main/java/forge/ai/AiCardMemory.java index 208cfb884f3..436c87e6c57 100644 --- a/forge-ai/src/main/java/forge/ai/AiCardMemory.java +++ b/forge-ai/src/main/java/forge/ai/AiCardMemory.java @@ -45,6 +45,7 @@ public class AiCardMemory { private final Set memAttachedThisTurn; private final Set memAnimatedThisTurn; private final Set memBouncedThisTurn; + private final Set memActivatedThisTurn; public AiCardMemory() { this.memMandatoryAttackers = new HashSet<>(); @@ -52,6 +53,7 @@ public class AiCardMemory { this.memAttachedThisTurn = new HashSet<>(); this.memAnimatedThisTurn = new HashSet<>(); this.memBouncedThisTurn = new HashSet<>(); + this.memActivatedThisTurn = new HashSet<>(); } /** @@ -65,6 +67,7 @@ public class AiCardMemory { ATTACHED_THIS_TURN, ANIMATED_THIS_TURN, BOUNCED_THIS_TURN, + ACTIVATED_THIS_TURN, //REVEALED_CARDS // stub, not linked to AI code yet } @@ -80,6 +83,8 @@ public class AiCardMemory { return memAnimatedThisTurn; case BOUNCED_THIS_TURN: return memBouncedThisTurn; + case ACTIVATED_THIS_TURN: + return memActivatedThisTurn; //case REVEALED_CARDS: // return memRevealedCards; default: @@ -253,6 +258,7 @@ public class AiCardMemory { clearMemorySet(MemorySet.ATTACHED_THIS_TURN); clearMemorySet(MemorySet.ANIMATED_THIS_TURN); clearMemorySet(MemorySet.BOUNCED_THIS_TURN); + clearMemorySet(MemorySet.ACTIVATED_THIS_TURN); } // Static functions to simplify access to AI card memory of a given AI player. diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index f672212113a..30717db2797 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1435,20 +1435,28 @@ public class ComputerUtil { } objects = canBeTargeted; } - - if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) { - toughness = saviour.hasParam("NumDef") ? - AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("NumDef"), saviour) : 0; - final List keywords = saviour.hasParam("KW") ? - Arrays.asList(saviour.getParam("KW").split(" & ")) : new ArrayList(); - if (keywords.contains("Indestructible")) { - grantIndestructible = true; - } - if (keywords.contains("Hexproof") || keywords.contains("Shroud")) { - grantShroud = true; + + SpellAbility saviorWithSubs = saviour; + while (saviorWithSubs != null) { + ApiType curApi = saviorWithSubs.getApi(); + if (curApi == ApiType.Pump || curApi == ApiType.PumpAll) { + toughness = saviorWithSubs.hasParam("NumDef") ? + AbilityUtils.calculateAmount(saviorWithSubs.getHostCard(), saviorWithSubs.getParam("NumDef"), saviour) : 0; + final List keywords = saviorWithSubs.hasParam("KW") ? + Arrays.asList(saviorWithSubs.getParam("KW").split(" & ")) : new ArrayList(); + if (keywords.contains("Indestructible")) { + grantIndestructible = true; + } + if (keywords.contains("Hexproof") || keywords.contains("Shroud")) { + grantShroud = true; + } + break; } + // Consider pump in subabilities, e.g. Bristling Hydra hexproof subability + saviorWithSubs = saviorWithSubs.getSubAbility(); + } - + if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) { if (saviour.getParam("CounterType").equals("P1P1")) { toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour); @@ -1588,9 +1596,11 @@ public class ComputerUtil { // Destroy => regeneration/bounce/shroud else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) - && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone + && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll - || saviourApi == ApiType.Protection || saviourApi == null)) { + || saviourApi == ApiType.Protection || saviourApi == null + || saviorWithSubs.getApi() == ApiType.Pump + || saviorWithSubs.getApi() == ApiType.PumpAll)) { for (final Object o : objects) { if (o instanceof Card) { final Card c = (Card) o; @@ -1604,7 +1614,9 @@ public class ComputerUtil { continue; } - if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) { + if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll + || saviorWithSubs.getApi() == ApiType.Pump + || saviorWithSubs.getApi() == ApiType.PumpAll) { if ((tgt == null && !grantIndestructible) || (!grantShroud && !grantIndestructible)) { continue; diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 4f1cdad9c15..f08d2cc1d14 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -249,6 +249,15 @@ public class CountersPutAi extends SpellAbilityAi { return true; } } + } else if (sa.getSubAbility() != null + && "Self".equals(sa.getSubAbility().getParam("Defined")) + && sa.getSubAbility().getParamOrDefault("KW", "").contains("Hexproof") + && !AiCardMemory.isRememberedCard(ai, source, AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) { + // Bristling Hydra: save from death using a ping activation + if (ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(source)) { + AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); + return true; + } } else if (ai.getCounters(CounterType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) { // outside of combat, this logic only works if the relevant AI profile option is enabled // and if there is enough energy saved