mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/**
|
||||
|
||||
@@ -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<Player> payers) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user