From 876668c3709591c9e7fef2569590e9eeab2665e4 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 11 Jan 2023 12:06:12 +0100 Subject: [PATCH] Fix Stack Overflow (#2228) * Fix cards * Fix Stack Overflow * Performance fix Co-authored-by: tool4EvEr Co-authored-by: TRT <> --- .../src/main/java/forge/ai/ComputerUtil.java | 32 +++++++++++++------ .../java/forge/ai/ComputerUtilCombat.java | 21 ++++++------ .../main/java/forge/ai/ComputerUtilCost.java | 9 +++--- .../main/java/forge/ai/ComputerUtilMana.java | 14 ++++---- .../game/ability/SpellAbilityEffect.java | 10 +++--- .../main/java/forge/game/cost/CostExert.java | 1 - .../forge/game/cost/CostPutCardToLib.java | 3 +- .../cardsfolder/f/flayer_of_the_hatebound.txt | 2 +- .../cardsfolder/p/phyrexian_dragon_engine.txt | 2 +- 9 files changed, 53 insertions(+), 41 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 8ab5228fc26..abfc9e97348 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -33,6 +33,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; +import forge.ai.AiCardMemory.MemorySet; import forge.ai.ability.ChooseGenericEffectAi; import forge.ai.ability.ProtectAi; import forge.ai.ability.TokenAi; @@ -449,14 +450,27 @@ public class ComputerUtil { } // try everything when about to die - if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) - && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) { - final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature"); - if (!nonCreatures.isEmpty()) { - return ComputerUtilCard.getWorstAI(nonCreatures); - } else if (!typeList.isEmpty()) { - // TODO make sure survival is possible in case the creature blocks a trampler - return ComputerUtilCard.getWorstAI(typeList); + if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + // in some rare situations the call to lifeInDanger could lead us back here, this will prevent an overflow + boolean preventReturn = sa != null && sa.isManaAbility(); + if (preventReturn) { + AiCardMemory.rememberCard(ai, sa.getHostCard(), MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL); + } + + boolean danger = ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat()); + + if (preventReturn) { + AiCardMemory.forgetCard(ai, sa.getHostCard(), MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL); + } + + if (danger) { + final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature"); + if (!nonCreatures.isEmpty()) { + return ComputerUtilCard.getWorstAI(nonCreatures); + } else if (!typeList.isEmpty()) { + // TODO make sure survival is possible in case the creature blocks a trampler + return ComputerUtilCard.getWorstAI(typeList); + } } } } @@ -609,7 +623,7 @@ public class ComputerUtil { int count = 0; while (count < amount) { - Card prefCard = getCardPreference(ai, source, "SacCost", typeList); + Card prefCard = getCardPreference(ai, source, "SacCost", typeList, ability); if (prefCard == null) { prefCard = ComputerUtilCard.getWorstAI(typeList); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index a1428d824b3..656645992f4 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -328,10 +328,10 @@ public class ComputerUtilCombat { if (blockers.size() == 0 || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) { unblocked.add(attacker); - } else if (attacker.hasKeyword(Keyword.TRAMPLE) - && getAttack(attacker) > totalShieldDamage(attacker, blockers)) { - if (!attacker.hasKeyword(Keyword.INFECT)) { - damage += getAttack(attacker) - totalShieldDamage(attacker, blockers); + } else if (attacker.hasKeyword(Keyword.TRAMPLE) && !attacker.hasKeyword(Keyword.INFECT)) { + int dmgAfterShielding = getAttack(attacker) - totalShieldDamage(attacker, blockers); + if (dmgAfterShielding > 0) { + damage += dmgAfterShielding; } } } @@ -369,13 +369,14 @@ public class ComputerUtilCombat { if (blockers.size() == 0 || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) { unblocked.add(attacker); - } else if (attacker.hasKeyword(Keyword.TRAMPLE) - && getAttack(attacker) > totalShieldDamage(attacker, blockers)) { + } else if (attacker.hasKeyword(Keyword.TRAMPLE)) { int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers); - if (attacker.hasKeyword(Keyword.INFECT)) { - poison += trampleDamage; + if (trampleDamage > 0) { + if (attacker.hasKeyword(Keyword.INFECT)) { + poison += trampleDamage; + } + poison += predictPoisonFromTriggers(attacker, ai, trampleDamage); } - poison += predictPoisonFromTriggers(attacker, ai, trampleDamage); } } @@ -686,7 +687,7 @@ public class ComputerUtilCombat { final int defenderDefense = blocker.getLethalDamage() - flankingMagnitude + defBushidoMagnitude; return defenderDefense; - } // shieldDamage + } // For AI safety measures like Regeneration /** diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 4cb075b9e10..4717a910bb6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -234,7 +234,7 @@ public class ComputerUtilCost { return true; } - public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility, final boolean effect) { + public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final SpellAbility sourceAbility, final boolean effect) { // TODO cheating via autopay can still happen, need to get the real ai player from controlledBy if (cost == null || !ai.isAI()) { return true; @@ -247,18 +247,17 @@ public class ComputerUtilCost { exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST)); } if (part.payCostFromSource()) { - list.add(source); + list.add(sourceAbility.getHostCard()); } else if (part.getType().equals("OriginalHost")) { list.add(sourceAbility.getOriginalHost()); } else if (part.getAmount().equals("All")) { // Does the AI want to use Sacrifice All? return false; } else { - final String amount = part.getAmount(); Integer c = part.convertAmount(); if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, sourceAbility); + c = part.getAbilityAmount(sourceAbility); } final AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude); @@ -636,7 +635,7 @@ public class ComputerUtilCost { return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect) && CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); - } // canPayCost() + } public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { final Card source = sa.getHostCard(); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 87cc9c397b7..05414e0dcfa 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -316,10 +316,6 @@ public class ComputerUtilMana { continue; } - if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) { - continue; - } - if (sa.getApi() == ApiType.Animate) { // For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined")) @@ -381,9 +377,15 @@ public class ComputerUtilMana { continue; } - if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) { - return paymentChoice; + if (!canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) { + continue; } + + if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) { + continue; + } + + return paymentChoice; } return null; } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 0bf5d8306fc..f46d837bc99 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -118,12 +118,10 @@ public abstract class SpellAbilityEffect { int amount = AbilityUtils.calculateAmount(sa.getHostCard(), svar, sa); sb.append(" "); sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount)))); - } else { - if (sa.costHasManaX()) { - int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid(); - sb.append(" "); - sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount)))); - } + } else if (sa.costHasManaX()) { + int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid(); + sb.append(" "); + sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount)))); } String currentName = CardTranslation.getTranslatedName(sa.getHostCard().getName()); diff --git a/forge-game/src/main/java/forge/game/cost/CostExert.java b/forge-game/src/main/java/forge/game/cost/CostExert.java index 4e4fae78cae..bec43cc5c5f 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExert.java +++ b/forge-game/src/main/java/forge/game/cost/CostExert.java @@ -88,7 +88,6 @@ public class CostExert extends CostPartWithList { typeList = CardLists.getValidCards(typeList, this.getType().split(";"), payer, source, ability); final int amount = this.getAbilityAmount(ability); - return needsAnnoucement || (typeList.size() >= amount); } diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java b/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java index ecb924a950c..04a18c68094 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java +++ b/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java @@ -82,7 +82,6 @@ public class CostPutCardToLib extends CostPartWithList { sb.append(Cost.convertAmountTypeToWords(i, getAmount(), desc)); } - if (sameZone) { sb.append(" from the same ").append(from); } else if (!this.payCostFromSource()) { @@ -137,7 +136,7 @@ public class CostPutCardToLib extends CostPartWithList { } typeList = CardLists.getValidCards(typeList, getType().split(";"), payer, source, ability); - + if (typeList.size() < i) { return false; } diff --git a/forge-gui/res/cardsfolder/f/flayer_of_the_hatebound.txt b/forge-gui/res/cardsfolder/f/flayer_of_the_hatebound.txt index 55b891c10d7..7938a3b13f6 100644 --- a/forge-gui/res/cardsfolder/f/flayer_of_the_hatebound.txt +++ b/forge-gui/res/cardsfolder/f/flayer_of_the_hatebound.txt @@ -3,7 +3,7 @@ ManaCost:5 R Types:Creature Devil PT:4/2 K:Undying -T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Card.Self,Creature.YouOwn+Other | Execute$ ReanimateDmg | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield from your graveyard, that creature deals damage equal to its power to any target. +T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Card.Self+YouOwn,Creature.YouOwn+Other | Execute$ ReanimateDmg | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield from your graveyard, that creature deals damage equal to its power to any target. SVar:ReanimateDmg:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | DamageSource$ TriggeredCard | NumDmg$ Damage SVar:Damage:TriggeredCard$CardPower Oracle:Undying (When this creature dies, if it had no +1/+1 counters on it, return it to the battlefield under its owner's control with a +1/+1 counter on it.)\nWhenever Flayer of the Hatebound or another creature enters the battlefield from your graveyard, that creature deals damage equal to its power to any target. diff --git a/forge-gui/res/cardsfolder/p/phyrexian_dragon_engine.txt b/forge-gui/res/cardsfolder/p/phyrexian_dragon_engine.txt index 7bc76146457..cd7bae2cef4 100644 --- a/forge-gui/res/cardsfolder/p/phyrexian_dragon_engine.txt +++ b/forge-gui/res/cardsfolder/p/phyrexian_dragon_engine.txt @@ -3,7 +3,7 @@ ManaCost:3 Types:Artifact Creature Phyrexian Dragon PT:2/2 K:Double Strike -T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | OptionalDecider$ You | TriggerZones$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield from your graveyard, you may discard your hand. If you do, draw three cards. +T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | OptionalDecider$ You | TriggerZones$ Battlefield | ValidCard$ Card.Self+YouOwn | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield from your graveyard, you may discard your hand. If you do, draw three cards. SVar:TrigDiscard:DB$ Discard | Mode$ Hand | Defined$ You | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 3 K:Unearth:3 R R