diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 7a08a093c0c..2efa9a2456f 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -572,8 +572,7 @@ public class AiAttackController { if (numExtraBlocks > 0) { // TODO should be limited to how much getBlockCost the opp can pay while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) { - blockedAttackers.add(remainingAttackers.get(0)); - remainingAttackers.remove(0); + blockedAttackers.add(remainingAttackers.remove(0)); maxBlockersAfterCrew--; } } @@ -581,8 +580,7 @@ public class AiAttackController { if (remainingAttackers.isEmpty()) { break; } - blockedAttackers.add(remainingAttackers.get(0)); - remainingAttackers.remove(0); + blockedAttackers.add(remainingAttackers.remove(0)); maxBlockersAfterCrew--; } unblockedAttackers.addAll(remainingAttackers); @@ -605,8 +603,8 @@ public class AiAttackController { final CardCollection unblockableCantPayFor = new CardCollection(); final CardCollection unblockableWithoutCost = new CardCollection(); // TODO also check poison - for (Card attacker : CardLists.getKeyword(unblockedAttackers, Keyword.TRAMPLE)) { - Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent); + for (Card attacker : unblockedAttackers) { + Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent); if (tax == null) { unblockableWithoutCost.add(attacker); } else { @@ -620,19 +618,17 @@ public class AiAttackController { } } int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent); + unblockedAttackers = unblockableWithoutCost; if (dmgUnblockableAfterPaying > trampleDamage) { - unblockedAttackers.removeAll(unblockableCantPayFor); - unblockedAttackers.removeAll(unblockableWithPaying); + myFreeMana -= unblockableAttackTax; totalCombatDamage = dmgUnblockableAfterPaying; // recalculate the trampler damage with the reduced mana available now - myFreeMana -= unblockableAttackTax; trampleDamage = getDamageFromBlockingTramplers(blockedAttackers, remainingBlockers, myFreeMana).getLeft(); } else { - unblockedAttackers = unblockableWithoutCost; myFreeMana -= tramplerTaxPaid; // find out if we can still pay for some left for (Card attacker : unblockableWithPaying) { - Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent); + Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent); int taxCMC = tax.getCostMana().getMana().getCMC(); if (myFreeMana < unblockableAttackTax + taxCMC) { continue; @@ -664,7 +660,7 @@ public class AiAttackController { CardCollection remainingBlockers = new CardCollection(blockers); for (Card attacker : CardLists.getKeyword(blockedAttackers, Keyword.TRAMPLE)) { // TODO might sort by quotient of dmg/cost for best combination - Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent); + Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent); int taxCMC = tax != null ? tax.getCostMana().getMana().getCMC() : 0; if (myFreeMana < currentAttackTax + taxCMC) { continue; diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 6768165d939..18a7815c4f9 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -709,9 +709,10 @@ public class AiController { return AiPlayDecision.CantPlaySa; } + final Card host = sa.getHostCard(); + // Check a predefined condition if (sa.hasParam("AICheckSVar")) { - final Card host = sa.getHostCard(); final String svarToCheck = sa.getParam("AICheckSVar"); String comparator = "GE"; int compareTo = 1; @@ -734,7 +735,7 @@ public class AiController { } int oldCMC = -1; - boolean xCost = sa.costHasX() || sa.getHostCard().hasStartOfKeyword("Strive"); + boolean xCost = sa.costHasX() || host.hasStartOfKeyword("Strive"); if (!xCost) { if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { // for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check @@ -748,15 +749,15 @@ public class AiController { } // state needs to be switched here so API checks evaluate the right face - CardStateName currentState = sa.getCardState() != null && sa.getHostCard().getCurrentStateName() != sa.getCardStateName() && !sa.getHostCard().isInPlay() ? sa.getHostCard().getCurrentStateName() : null; + CardStateName currentState = sa.getCardState() != null && host.getCurrentStateName() != sa.getCardStateName() && !host.isInPlay() ? host.getCurrentStateName() : null; if (currentState != null) { - sa.getHostCard().setState(sa.getCardStateName(), false); + host.setState(sa.getCardStateName(), false); } AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc. if (currentState != null) { - sa.getHostCard().setState(currentState, false); + host.setState(currentState, false); } if (canPlay != AiPlayDecision.WillPlay) { @@ -766,10 +767,10 @@ public class AiController { // Account for possible Ward after the spell is fully targeted // TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best // one is warded and can't be paid for. - if (sa.usesTargeting()) { + if (sa.usesTargeting() && CardFactoryUtil.isCounterable(host)) { for (Card tgt : sa.getTargets().getTargetCards()) { // TODO some older cards don't use the keyword, so check for trigger instead - if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { + if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) { int amount = 0; Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); if (wardCost.hasManaCost()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 73094a7d4e6..9c979e25615 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -1268,21 +1268,17 @@ public class ComputerUtilCombat { } List list = Lists.newArrayList(); - if (!sa.hasParam("ValidCards")) { - list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null); - } - if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) { - list.add(attacker); - } if (sa.hasParam("ValidCards")) { if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null) || attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, null)) { list.add(attacker); } + } else { + list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null); } - if (list.isEmpty()) { - continue; + if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) { + list.add(attacker); } if (!list.contains(attacker)) { continue; @@ -1464,14 +1460,15 @@ public class ComputerUtilCombat { toughness -= predictDamageTo(attacker, damage, source, false); continue; } else if (ApiType.Pump.equals(sa.getApi())) { + if (!sa.hasParam("NumDef")) { + continue; + } if (sa.hasParam("Cost")) { if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) { continue; } } - if (!sa.hasParam("NumDef")) { - continue; - } + final String defined = sa.getParam("Defined"); CardCollection list = AbilityUtils.getDefinedCards(source, defined, sa); if (defined != null && defined.startsWith("TriggeredAttacker")) { @@ -1497,6 +1494,9 @@ public class ComputerUtilCombat { toughness += AbilityUtils.calculateAmount(source, bonus, sa); } } else if (ApiType.PumpAll.equals(sa.getApi())) { + if (!sa.hasParam("NumDef")) { + continue; + } if (sa.hasParam("Cost")) { if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) { continue; @@ -1506,9 +1506,6 @@ public class ComputerUtilCombat { if (!sa.hasParam("ValidCards")) { continue; } - if (!sa.hasParam("NumDef")) { - continue; - } if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) { continue; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 847f2ee12f5..46f379218ce 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -528,10 +528,11 @@ public class ComputerUtilCost { sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added. } + final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard()); + // Check for stuff like Nether Void int extraManaNeeded = 0; if (sa instanceof Spell) { - final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard()); for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) { final String snem = c.getSVar("AI_SpellsNeedExtraMana"); if (!StringUtils.isBlank(snem)) { @@ -591,7 +592,7 @@ public class ComputerUtilCost { } // Ward - will be accounted for when rechecking a targeted ability - if (!(sa instanceof WrappedAbility) && sa.usesTargeting()) { + if (!(sa instanceof WrappedAbility) && sa.usesTargeting() && !cannotBeCountered) { for (Card tgt : sa.getTargets().getTargetCards()) { if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); @@ -721,6 +722,17 @@ public class ComputerUtilCost { return false; } + // ward or human misplay + if (ApiType.Counter.equals(sa.getApi())) { + List spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa); + for (SpellAbility toBeCountered : spells) { + if (!CardFactoryUtil.isCounterable(toBeCountered.getHostCard())) { + return false; + } + // TODO check hasFizzled + } + } + // AI was crashing because the blank ability used to pay costs // Didn't have any of the data on the original SA to pay dependant costs