Fix Stack Overflow (#2228)

* Fix cards

* Fix Stack Overflow

* Performance fix

Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
Co-authored-by: TRT <>
This commit is contained in:
tool4ever
2023-01-11 12:06:12 +01:00
committed by GitHub
parent 11a893e64a
commit 876668c370
9 changed files with 53 additions and 41 deletions

View File

@@ -33,6 +33,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import forge.ai.AiCardMemory.MemorySet;
import forge.ai.ability.ChooseGenericEffectAi; import forge.ai.ability.ChooseGenericEffectAi;
import forge.ai.ability.ProtectAi; import forge.ai.ability.ProtectAi;
import forge.ai.ability.TokenAi; import forge.ai.ability.TokenAi;
@@ -449,8 +450,20 @@ public class ComputerUtil {
} }
// try everything when about to die // try everything when about to die
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) { // 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"); final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
if (!nonCreatures.isEmpty()) { if (!nonCreatures.isEmpty()) {
return ComputerUtilCard.getWorstAI(nonCreatures); return ComputerUtilCard.getWorstAI(nonCreatures);
@@ -460,6 +473,7 @@ public class ComputerUtil {
} }
} }
} }
}
else if (pref.contains("DiscardCost")) { // search for permanents with DiscardMe else if (pref.contains("DiscardCost")) { // search for permanents with DiscardMe
for (int ip = 0; ip < 6; ip++) { // priority 0 is the lowest, priority 5 the highest for (int ip = 0; ip < 6; ip++) { // priority 0 is the lowest, priority 5 the highest
final int priority = 6 - ip; final int priority = 6 - ip;
@@ -609,7 +623,7 @@ public class ComputerUtil {
int count = 0; int count = 0;
while (count < amount) { while (count < amount) {
Card prefCard = getCardPreference(ai, source, "SacCost", typeList); Card prefCard = getCardPreference(ai, source, "SacCost", typeList, ability);
if (prefCard == null) { if (prefCard == null) {
prefCard = ComputerUtilCard.getWorstAI(typeList); prefCard = ComputerUtilCard.getWorstAI(typeList);
} }

View File

@@ -328,10 +328,10 @@ public class ComputerUtilCombat {
if (blockers.size() == 0 if (blockers.size() == 0
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) { || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
unblocked.add(attacker); unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) } else if (attacker.hasKeyword(Keyword.TRAMPLE) && !attacker.hasKeyword(Keyword.INFECT)) {
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) { int dmgAfterShielding = getAttack(attacker) - totalShieldDamage(attacker, blockers);
if (!attacker.hasKeyword(Keyword.INFECT)) { if (dmgAfterShielding > 0) {
damage += getAttack(attacker) - totalShieldDamage(attacker, blockers); damage += dmgAfterShielding;
} }
} }
} }
@@ -369,15 +369,16 @@ public class ComputerUtilCombat {
if (blockers.size() == 0 if (blockers.size() == 0
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) { || StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
unblocked.add(attacker); unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) } else if (attacker.hasKeyword(Keyword.TRAMPLE)) {
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers); int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers);
if (trampleDamage > 0) {
if (attacker.hasKeyword(Keyword.INFECT)) { if (attacker.hasKeyword(Keyword.INFECT)) {
poison += trampleDamage; poison += trampleDamage;
} }
poison += predictPoisonFromTriggers(attacker, ai, trampleDamage); poison += predictPoisonFromTriggers(attacker, ai, trampleDamage);
} }
} }
}
poison += sumPoisonIfUnblocked(unblocked, ai); poison += sumPoisonIfUnblocked(unblocked, ai);
@@ -686,7 +687,7 @@ public class ComputerUtilCombat {
final int defenderDefense = blocker.getLethalDamage() - flankingMagnitude + defBushidoMagnitude; final int defenderDefense = blocker.getLethalDamage() - flankingMagnitude + defBushidoMagnitude;
return defenderDefense; return defenderDefense;
} // shieldDamage }
// For AI safety measures like Regeneration // For AI safety measures like Regeneration
/** /**

View File

@@ -234,7 +234,7 @@ public class ComputerUtilCost {
return true; 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 // TODO cheating via autopay can still happen, need to get the real ai player from controlledBy
if (cost == null || !ai.isAI()) { if (cost == null || !ai.isAI()) {
return true; return true;
@@ -247,18 +247,17 @@ public class ComputerUtilCost {
exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST)); exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST));
} }
if (part.payCostFromSource()) { if (part.payCostFromSource()) {
list.add(source); list.add(sourceAbility.getHostCard());
} else if (part.getType().equals("OriginalHost")) { } else if (part.getType().equals("OriginalHost")) {
list.add(sourceAbility.getOriginalHost()); list.add(sourceAbility.getOriginalHost());
} else if (part.getAmount().equals("All")) { } else if (part.getAmount().equals("All")) {
// Does the AI want to use Sacrifice All? // Does the AI want to use Sacrifice All?
return false; return false;
} else { } else {
final String amount = part.getAmount();
Integer c = part.convertAmount(); Integer c = part.convertAmount();
if (c == null) { if (c == null) {
c = AbilityUtils.calculateAmount(source, amount, sourceAbility); c = part.getAbilityAmount(sourceAbility);
} }
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude); CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude);
@@ -636,7 +635,7 @@ public class ComputerUtilCost {
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect) return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); && CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} // canPayCost() }
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) { public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();

View File

@@ -316,10 +316,6 @@ public class ComputerUtilMana {
continue; continue;
} }
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) {
continue;
}
if (sa.getApi() == ApiType.Animate) { 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 // 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")) if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
@@ -381,9 +377,15 @@ public class ComputerUtilMana {
continue; continue;
} }
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) { if (!canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
return paymentChoice; continue;
} }
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
continue;
}
return paymentChoice;
} }
return null; return null;
} }

View File

@@ -118,13 +118,11 @@ public abstract class SpellAbilityEffect {
int amount = AbilityUtils.calculateAmount(sa.getHostCard(), svar, sa); int amount = AbilityUtils.calculateAmount(sa.getHostCard(), svar, sa);
sb.append(" "); sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount)))); sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
} else { } else if (sa.costHasManaX()) {
if (sa.costHasManaX()) {
int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid(); int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid();
sb.append(" "); sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount)))); sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
} }
}
String currentName = CardTranslation.getTranslatedName(sa.getHostCard().getName()); String currentName = CardTranslation.getTranslatedName(sa.getHostCard().getName());
String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", currentName); String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", currentName);

View File

@@ -88,7 +88,6 @@ public class CostExert extends CostPartWithList {
typeList = CardLists.getValidCards(typeList, this.getType().split(";"), payer, source, ability); typeList = CardLists.getValidCards(typeList, this.getType().split(";"), payer, source, ability);
final int amount = this.getAbilityAmount(ability); final int amount = this.getAbilityAmount(ability);
return needsAnnoucement || (typeList.size() >= amount); return needsAnnoucement || (typeList.size() >= amount);
} }

View File

@@ -82,7 +82,6 @@ public class CostPutCardToLib extends CostPartWithList {
sb.append(Cost.convertAmountTypeToWords(i, getAmount(), desc)); sb.append(Cost.convertAmountTypeToWords(i, getAmount(), desc));
} }
if (sameZone) { if (sameZone) {
sb.append(" from the same ").append(from); sb.append(" from the same ").append(from);
} else if (!this.payCostFromSource()) { } else if (!this.payCostFromSource()) {

View File

@@ -3,7 +3,7 @@ ManaCost:5 R
Types:Creature Devil Types:Creature Devil
PT:4/2 PT:4/2
K:Undying 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:ReanimateDmg:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | DamageSource$ TriggeredCard | NumDmg$ Damage
SVar:Damage:TriggeredCard$CardPower 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. 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.

View File

@@ -3,7 +3,7 @@ ManaCost:3
Types:Artifact Creature Phyrexian Dragon Types:Artifact Creature Phyrexian Dragon
PT:2/2 PT:2/2
K:Double Strike 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:TrigDiscard:DB$ Discard | Mode$ Hand | Defined$ You | SubAbility$ DBDraw
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 3 SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 3
K:Unearth:3 R R K:Unearth:3 R R