Attack fixes (#3241)

This commit is contained in:
tool4ever
2023-06-11 15:21:33 +02:00
committed by GitHub
parent 781eb35ee3
commit 4bc8c05a5e
15 changed files with 45 additions and 119 deletions

View File

@@ -135,16 +135,20 @@ public class AiAttackController {
return !c.isTapped() && !c.isCreature() && !c.isPlaneswalker();
}
};
CardCollection tappedDefenders = new CardCollection();
for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) {
/* TODO: is the commented out code still necessary for anything?
if (c.isToken() && c.getCopiedPermanent() == null && !c.canTransform(null)) {
for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.Animate))) {
if (sa.usesTargeting() || !sa.getParamOrDefault("Defined", "Self").equals("Self")) {
continue;
}
if (sa.hasParam("Crew") && !ComputerUtilCost.checkTapTypeCost(defender, sa.getPayCosts(), c, sa, tappedDefenders)) {
continue;
} else if (!ComputerUtilCost.canPayCost(sa, defender, false) || !sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
continue;
}
*/
for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.Animate))) {
if (ComputerUtilCost.canPayCost(sa, defender, false)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
if (animatedCopy.isCreature()) {
int saCMC = sa.getPayCosts() != null && sa.getPayCosts().hasManaCost() ?
sa.getPayCosts().getTotalMana().getCMC() : 0; // FIXME: imprecise, only works 100% for colorless mana
if (totalMana - manaReserved >= saCMC) {
@@ -153,6 +157,8 @@ public class AiAttackController {
}
}
}
defenders.removeAll(tappedDefenders);
// Transform (e.g. Incubator tokens)
for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.SetState))) {
Card transformedCopy = ComputerUtilCombat.canTransform(c);

View File

@@ -1109,7 +1109,9 @@ public class AiBlockController {
}
// TODO could be made more accurate if this would be inside each blocker choosing loop instead
lifeInDanger |= removeUnpayableBlocks(combat) && ComputerUtilCombat.lifeInDanger(ai, combat);
if (removeUnpayableBlocks(combat) || lifeInDanger) {
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
}
// == 2. If the AI life would still be in danger make a safer approach ==
if (lifeInDanger) {

View File

@@ -694,10 +694,8 @@ public class ComputerUtil {
public static CardCollection chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount, final CardCollectionView exclude, SpellAbility sa) {
CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.removeAll(exclude);
CardCollection typeList =
CardLists.getValidCards(all, type.split(";"), activate.getController(), activate, sa);
CardCollection typeList = CardLists.getValidCards(all, type.split(";"), activate.getController(), activate, sa);
// is this needed?
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
if (tap) {
@@ -733,7 +731,6 @@ public class ComputerUtil {
typeList = CardLists.getNotKeyword(typeList, "CARDNAME can't crew Vehicles.");
}
// is this needed?
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
if (tap) {
@@ -772,7 +769,6 @@ public class ComputerUtil {
CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
// is this needed?
typeList = CardLists.filter(typeList, Presets.TAPPED);
if (untap) {

View File

@@ -415,7 +415,7 @@ public class ComputerUtilCost {
* the source
* @return true, if successful
*/
public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sa) {
public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sa, final CardCollection alreadyTapped) {
if (cost == null) {
return true;
}
@@ -442,8 +442,13 @@ public class ComputerUtilCost {
return ComputerUtilCard.evaluateCreature(c) >= vehicleValue;
}
}); // exclude creatures >= vehicle
return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true,
Integer.parseInt(totalP), exclude) != null;
exclude.addAll(alreadyTapped);
CardCollection tappedCrew = ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true, Integer.parseInt(totalP), exclude);
if (tappedCrew != null) {
alreadyTapped.addAll(tappedCrew);
return true;
}
return false;
}
// check if we have a valid card to tap (e.g. Jaspera Sentinel)
@@ -480,36 +485,6 @@ public class ComputerUtilCost {
return true;
}
/**
* <p>
* shouldPayCost.
* </p>
*
* @param hostCard
* a {@link forge.game.card.Card} object.
* @param cost
* @return a boolean.
*/
@Deprecated
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) {
if (!ai.cantLoseForZeroOrLessLife()) {
continue;
}
final int remainingLife = ai.getLife();
final int lifeCost = part.convertAmount();
if ((remainingLife - lifeCost) < 10) {
return false; //Don't pay life if it would put AI under 10 life
} else if ((remainingLife / lifeCost) < 4) {
return false; //Don't pay life if it is more than 25% of current life
}
}
}
return true;
} // shouldPayCost()
/**
* <p>
* canPayCost.
@@ -580,15 +555,6 @@ public class ComputerUtilCost {
}
}
// KLD vehicle
if (sa.hasParam("Crew")) { // put under checkTapTypeCost?
for (final CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostTapType && part.getType().contains("+withTotalPowerGE")) {
return new AiCostDecision(player, sa, false).visit((CostTapType)part) != null;
}
}
}
// Ward - will be accounted for when rechecking a targeted ability
if (!sa.isTrigger() && sa.usesTargeting() && (!sa.isSpell() || !cannotBeCountered)) {
for (Card tgt : sa.getTargets().getTargetCards()) {

View File

@@ -312,7 +312,7 @@ public class ComputerUtilMana {
continue;
}
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa)) {
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, new CardCollection())) {
continue;
}

View File

@@ -130,7 +130,7 @@ public class AnimateAi extends SpellAbilityAi {
return true; // interrupt sacrifice
}
}
if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa)) {
if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa, new CardCollection())) {
return false; // prevent crewing with equal or better creatures
}
@@ -379,7 +379,7 @@ public class AnimateAi extends SpellAbilityAi {
return copy;
}
public static void becomeAnimated(final Card card, final boolean hasOriginalCardSickness, final SpellAbility sa) {
private static void becomeAnimated(final Card card, final boolean hasOriginalCardSickness, final SpellAbility sa) {
// duplicating AnimateEffect.resolve
final Card source = sa.getHostCard();
final Game game = sa.getActivatingPlayer().getGame();

View File

@@ -78,15 +78,7 @@ public class SacrificeAi extends SpellAbilityAi {
String num = sa.getParamOrDefault("Amount" , "1");
final int amount = AbilityUtils.calculateAmount(source, num, sa);
List<Card> list = null;
try {
list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
} catch (NullPointerException e) {
return false;
} finally {
if (list == null)
return false;
}//prevent NPE on MoJhoSto
List<Card> list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
for (Card c : list) {
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
@@ -140,30 +132,13 @@ public class SacrificeAi extends SpellAbilityAi {
amount = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), amount);
}
List<Card> humanList = null;
try {
humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
} catch (NullPointerException e) {
return false;
} finally {
if (humanList == null)
return false;
}//prevent NPE on MoJhoSto
List<Card> humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
// Since all of the cards have AI:RemoveDeck:All, I enabled 1 for 1
// (or X for X) trades for special decks
return humanList.size() >= amount;
} else if (defined.equals("You")) {
List<Card> computerList = null;
try {
computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
} catch (NullPointerException e) {
return false;
} finally {
if (computerList == null)
return false;
}//prevent NPE on MoJhoSto
List<Card> computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
for (Card c : computerList) {
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) <= 135) {
return true;

View File

@@ -2180,34 +2180,20 @@ public class AbilityUtils {
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb);
}
// Count$AttachedTo <DefinedCards related to spellability> <restriction>
// Count$AttachedTo <restriction>
if (sq[0].startsWith("AttachedTo")) {
final String[] k = l[0].split(" ");
int sum = 0;
for (Card card : getDefinedCards(c, k[1], ctb)) {
// Hateful Eidolon: the script uses LKI so that the attached cards have to be defined
// This card needs the spellability ("Auras You control", you refers to the activating player)
// CardFactoryUtils.xCount doesn't have the sa parameter, SVar:X:TriggeredCard$Valid <restriction> cannot handle this
sum += CardLists.getValidCardCount(card.getAttachedCards(), k[2], player, c, ctb);
}
int sum = CardLists.getValidCardCount(c.getAttachedCards(), k[1], player, c, ctb);
return doXMath(sum, expr, c, ctb);
}
// Count$CardManaCost
if (sq[0].contains("CardManaCost")) {
Card ce;
if (sq[0].contains("Remembered")) {
ce = (Card) c.getFirstRemembered();
}
else {
ce = c;
}
int cmc = c.getCMC();
int cmc = ce == null ? 0 : ce.getCMC();
if (sq[0].contains("LKI") && ctb instanceof SpellAbility && ce != null && !ce.isInZone(ZoneType.Stack) && ce.getManaCost() != null) {
if (sq[0].contains("LKI") && ctb instanceof SpellAbility && !c.isInZone(ZoneType.Stack) && c.getManaCost() != null) {
if (((SpellAbility) ctb).getXManaCostPaid() != null) {
cmc += ((SpellAbility) ctb).getXManaCostPaid() * ce.getManaCost().countX();
cmc += ((SpellAbility) ctb).getXManaCostPaid() * c.getManaCost().countX();
}
}

View File

@@ -33,7 +33,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final List<SpellAbility> sas = getTargetSpells(sa);
final boolean remember = sa.hasParam("RememberTargetedCard");
final Player activator = sa.getActivatingPlayer();
final Player chooser = sa.hasParam("Chooser") ? getDefinedPlayersOrTargeted(sa, "Chooser").get(0) : sa.getActivatingPlayer();
@@ -140,9 +139,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
changingTgtSI = changingTgtSI.getSubInstance();
}
}
if (remember) {
sa.getHostCard().addRemembered(tgtSA.getHostCard());
}
}
}
}

View File

@@ -5,7 +5,7 @@ PT:6/6
K:Convoke
K:Trample
K:Ward:2
K:etbCounter:P1P1:X
K:etbCounter:P1P1:X:no condition:CARDNAME enters the battlefield with two +1/+1 counters on it for each creature that convoked it.
SVar:X:Convoked$Amount/Twice
DeckHas:Ability$Counters
Oracle:Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nTrample, ward {2}\nAncient Imperiosaur enters the battlefield with two +1/+1 counters on it for each creature that convoked it.

View File

@@ -15,7 +15,7 @@ SVar:DBScry:DB$ Scry | ScryNum$ 2
A:AB$ Dig | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | Reveal$ True | SubAbility$ DBToken | SpellDescription$ Exile the top card of your library.
SVar:DBToken:DB$ Token | TokenAmount$ Z | TokenScript$ u_1_1_faerie_dragon_flying | SubAbility$ DBCleanup | SpellDescription$ Create a number of 1/1 blue Faerie Dragon creature tokens with flying equal to that card's mana value.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Z:Count$RememberedCardManaCost
SVar:Z:Remembered$CardManaCost
Text:CARDNAME can be your commander.
DeckHas:Ability$Token & Type$Faerie|Dragon
DeckHints:Type$Instant|Sorcery

View File

@@ -5,6 +5,6 @@ PT:1/2
K:Lifelink
T:Mode$ ChangesZone | ValidCard$ Creature.enchanted | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever an enchanted creature dies, draw a card for each Aura you controlled that was attached to it.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X
SVar:X:Count$AttachedTo TriggeredCardLKICopy Aura.YouCtrl
SVar:X:TriggeredCard$AttachedTo Aura.YouCtrl
SVar:EnchantMe:Multiple
Oracle:Lifelink\nWhenever an enchanted creature dies, draw a card for each Aura you controlled that was attached to it.

View File

@@ -1,9 +1,8 @@
Name:Imp's Mischief
ManaCost:1 B
Types:Instant
A:SP$ ChangeTargets | TargetType$ Spell.singleTarget | ValidTgts$ Card | TgtPrompt$ Select target spell with a single target | RememberTargetedCard$ True | SubAbility$ DBLoseLife | SpellDescription$ Change the target of target spell with a single target. You lose life equal to that spell's mana value.
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$CardManaCost
A:SP$ ChangeTargets | TargetType$ Spell.singleTarget | ValidTgts$ Card | TgtPrompt$ Select target spell with a single target | SubAbility$ DBLoseLife | SpellDescription$ Change the target of target spell with a single target. You lose life equal to that spell's mana value.
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ X
SVar:X:Targeted$CardManaCost
AI:RemoveDeck:All
Oracle:Change the target of target spell with a single target. You lose life equal to that spell's mana value.

View File

@@ -5,7 +5,7 @@ PT:*/*
K:ETBReplacement:Copy:ChooseSpell
SVar:ChooseSpell:DB$ ChangeZone | ChangeType$ Instant.YouOwn,Sorcery.YouOwn | ChangeNum$ 1 | Hidden$ True | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | Mandatory$ True | SpellDescription$ As CARDNAME enters the battlefield, exile an instant or sorcery card from your graveyard.
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the exiled card's mana value.
SVar:X:Count$RememberedCardManaCost
SVar:X:Remembered$CardManaCost
T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigSacLore | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage, you may sacrifice it. If you do, you may cast the exiled card without paying its mana cost.
SVar:TrigSacLore:AB$ Play | Cost$ Sac<1/CARDNAME> | Defined$ Remembered | Amount$ All | Controller$ You | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | ForgetRemembered$ True
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup

View File

@@ -8,6 +8,6 @@ SVar:SparkDamage:DB$ DealDamage | Defined$ ParentTarget | NumDmg$ X | SpellDescr
SVar:SparkPlay:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | ExileOnMoved$ Exile | SpellDescription$ You may play exiled card until end of turn.
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play remembered card.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$RememberedCardManaCost
SVar:X:Remembered$CardManaCost
AI:RemoveDeck:All
Oracle:Choose target creature. Exile the top card of your library. You may have Spark of Creativity deal damage to that creature equal to the exiled card's mana value. If you don't, you may play that card until end of turn.