diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 210209a702e..f9b5378fd7f 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -448,7 +448,7 @@ public class AiCostDecision extends CostDecisionMakerBase { final AiController aic = ((PlayerControllerAi)player.getController()).getAi(); CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, isEffect(), c, null); - return PaymentDecision.card(list); + return list == null ? null : PaymentDecision.card(list); } @Override diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 4ea030507c0..d1960572d1e 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1730,6 +1730,7 @@ public class ComputerUtilCard { } final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used? pumped.addChangedCardKeywordsInternal(toCopy, null, false, timestamp2, 0, false); + pumped.updateKeywordsCache(pumped.getCurrentState()); applyStaticContPT(ai.getGame(), pumped, new CardCollection(c)); return pumped; } diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index d74e4fa45b2..ff271e8ac42 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -1580,7 +1580,7 @@ public class AttachAi extends SpellAbilityAi { && canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card); } else if (keyword.equals("Haste")) { - return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped() + return card.hasSickness() && ph.isPlayerTurn(ai) && !card.isTapped() && card.getNetCombatDamage() + powerBonus > 0 && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && ComputerUtilCombat.canAttackNextTurn(card); diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java index f742073fdc7..1a2e45360ce 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java @@ -113,9 +113,9 @@ public abstract class PumpAiBase extends SpellAbilityAi { return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true); } if (!ph.isPlayerTurn(ai)) { - return CombatUtil.canAttack(card, ai) - && (card.getNetCombatDamage() > 0) - && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS); + return card.getNetCombatDamage() > 0 + && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) + && CombatUtil.canAttack(card, ai); } else { if (ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isBefore(PhaseType.MAIN1)) { @@ -129,7 +129,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { && (combat == null || !combat.isAttacking(c))) { return false; } - return CombatUtil.canAttack(c, card.getController()) || (combat != null && combat.isAttacking(c)); + return (combat != null && combat.isAttacking(c)) || CombatUtil.canAttack(c, card.getController()); } }); return CombatUtil.canBlockAtLeastOne(card, attackers); @@ -148,8 +148,8 @@ public abstract class PumpAiBase extends SpellAbilityAi { return false; } // the cards controller needs to be the one attacked - return CombatUtil.canAttack(c, card.getController()) || (combat != null && combat.isAttacking(c) - && card.getController().equals(combat.getDefenderPlayerByAttacker(c))); + return (combat != null && combat.isAttacking(c) && card.getController().equals(combat.getDefenderPlayerByAttacker(c))) || + CombatUtil.canAttack(c, card.getController()); } }); return CombatUtil.canBlockAtLeastOne(card, attackers); @@ -199,7 +199,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { final boolean evasive = keyword.endsWith("Shadow"); // give evasive keywords to creatures that can or do attack if (evasive) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)); @@ -231,7 +231,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { } } } - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), @@ -244,25 +244,25 @@ public abstract class PumpAiBase extends SpellAbilityAi { && ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { return true; } - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), Keyword.HORSEMANSHIP).isEmpty(); } else if (keyword.endsWith("Intimidate")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getNotType(CardLists.filter( opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Artifact").isEmpty(); } else if (keyword.endsWith("Fear")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getNotColor(CardLists.getNotType(CardLists.filter( opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Artifact"), MagicColor.BLACK).isEmpty(); } else if (keyword.endsWith("Haste")) { - return card.hasSickness() && !ph.isPlayerTurn(opp) && !card.isTapped() + return CombatUtil.isAttackerSick(card, opp) && !ph.isPlayerTurn(opp) && !card.isTapped() && newPower > 0 && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && ComputerUtilCombat.canAttackNextTurn(card); @@ -293,7 +293,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { } return false; } else if (keyword.startsWith("Bushido")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && !opp.getCreaturesInPlay().isEmpty() && Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)); @@ -320,22 +320,22 @@ public abstract class PumpAiBase extends SpellAbilityAi { } return false; } else if (keyword.equals("Double Strike")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && newPower > 0 && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS); } else if (keyword.startsWith("Rampage")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && newPower > 0 && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() >= 2; } else if (keyword.startsWith("Flanking")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && newPower > 0 && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && !CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), Keyword.FLANKING).isEmpty(); } else if (keyword.startsWith("Trample")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && CombatUtil.canBeBlocked(card, null, opp) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 1 @@ -347,8 +347,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { if (combat != null && combat.isBlocking(card) && !card.hasKeyword(Keyword.WITHER)) { return true; } - return (!ph.isPlayerTurn(opp)) - && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS); } else if (keyword.endsWith("Wither")) { if (newPower <= 0 || card.hasKeyword(Keyword.INFECT)) { @@ -376,25 +375,25 @@ public abstract class PumpAiBase extends SpellAbilityAi { } else if (keyword.equals("Persist")) { return card.getBaseToughness() > 1 && !card.hasKeyword(Keyword.UNDYING); } else if (keyword.equals("Islandwalk")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty() && Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)); } else if (keyword.equals("Swampwalk")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty() && Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)); } else if (keyword.equals("Mountainwalk")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty() && Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)); } else if (keyword.equals("Forestwalk")) { - return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) + return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp)) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && newPower > 0 && !CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty() diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index 6408d2e8738..affdd46376a 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -1132,9 +1132,6 @@ public class CombatUtil { if (!canBlock(blocker, nextTurn)) { return false; } - if (!canBeBlocked(attacker, blocker.getController())) { - return false; - } if (isUnblockableFromLandwalk(attacker, blocker.getController()) && !blocker.hasKeyword("CARDNAME can block creatures with landwalk abilities as though they didn't have those abilities.")) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index a86cdcf21e5..7dfbd0b5170 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1353,17 +1353,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } - if (tr.isSameController()) { - Player newController; - if (entity instanceof Card) { - newController = ((Card) entity).getController(); - for (final Card c : targetChosen.getTargetCards()) { - if (entity != c && !c.getController().equals(newController)) - return false; - } - } - } - if (hasParam("MaxTotalTargetPower") && entity instanceof Card) { int soFar = Aggregates.sum(getTargets().getTargetCards(), CardPredicates.Accessors.fnGetNetPower); // only add if it isn't already targeting @@ -1377,6 +1366,17 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } + if (tr.isSameController()) { + Player newController; + if (entity instanceof Card) { + newController = ((Card) entity).getController(); + for (final Card c : targetChosen.getTargetCards()) { + if (entity != c && !c.getController().equals(newController)) + return false; + } + } + } + if (tr.isDifferentControllers()) { Player newController; if (entity instanceof Card) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java index 46c2e4c4658..d5080bb86e8 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java @@ -223,11 +223,6 @@ public class StaticAbilityCantAttackBlock { if (!stAb.matchesValidParam("ValidTarget", target)) { return false; } - - final Player defender = target instanceof Card ? ((Card) target).getController() : (Player) target; - if (!stAb.matchesValidParam("ValidDefender", defender)) { - return false; - } return true; } diff --git a/forge-gui/res/cardsfolder/f/frenzied_saddlebrute.txt b/forge-gui/res/cardsfolder/f/frenzied_saddlebrute.txt index b1bcfe13f19..528872dd731 100644 --- a/forge-gui/res/cardsfolder/f/frenzied_saddlebrute.txt +++ b/forge-gui/res/cardsfolder/f/frenzied_saddlebrute.txt @@ -3,6 +3,6 @@ ManaCost:4 R Types:Creature Orc Warrior PT:5/4 K:Haste -S:Mode$ CanAttackIfHaste | ValidDefender$ Opponent | Description$ All creatures can attack your opponents and planeswalkers your opponents control as though those creatures had haste. +S:Mode$ CanAttackIfHaste | ValidTarget$ Opponent,Planeswalker.OppCtrl | Description$ All creatures can attack your opponents and planeswalkers your opponents control as though those creatures had haste. SVar:PlayMain1:TRUE Oracle:Haste\nAll creatures can attack your opponents and planeswalkers your opponents control as though those creatures had haste. diff --git a/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt b/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt index f0f5f86ae61..b8e3631db9a 100644 --- a/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt +++ b/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt @@ -2,7 +2,7 @@ Name:Thaumatic Compass ManaCost:2 Types:Artifact A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card into your hand, then shuffle. -T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | IsPresent$ Land.YouCtrl | PresentCompare$ GE7 | Execute$ DBTransform | TriggerDescription$ At the beginning of your end step, if you control seven or more lands, transform Thaumatic Compass. +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Land.YouCtrl | PresentCompare$ GE7 | Execute$ DBTransform | TriggerDescription$ At the beginning of your end step, if you control seven or more lands, transform Thaumatic Compass. SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform AlternateMode:DoubleFaced Oracle:{3}, {T}: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\nAt the beginning of your end step, if you control seven or more lands, transform Thaumatic Compass.