mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 03:08:02 +00:00
Attack fixes (#3241)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user