diff --git a/.gitattributes b/.gitattributes index dd67011f33e..16548776487 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2064,6 +2064,7 @@ res/cardsfolder/c/cover_of_darkness.txt svneol=native#text/plain res/cardsfolder/c/covert_operative.txt svneol=native#text/plain res/cardsfolder/c/covetous_dragon.txt svneol=native#text/plain res/cardsfolder/c/cowardice.txt svneol=native#text/plain +res/cardsfolder/c/cowed_by_wisdom.txt -text res/cardsfolder/c/cower_in_fear.txt -text res/cardsfolder/c/crab_umbra.txt svneol=native#text/plain res/cardsfolder/c/crabapple_cohort.txt svneol=native#text/plain diff --git a/res/cardsfolder/c/cowed_by_wisdom.txt b/res/cardsfolder/c/cowed_by_wisdom.txt new file mode 100644 index 00000000000..b83bcf977d6 --- /dev/null +++ b/res/cardsfolder/c/cowed_by_wisdom.txt @@ -0,0 +1,11 @@ +Name:Cowed by Wisdom +ManaCost:W +Types:Enchantment Aura +K:Enchant creature +A:SP$ Attach | Cost$ W | ValidTgts$ Creature | AILogic$ Curse +S:Mode$ CantAttackUnless | ValidCard$ Creature.AttachedBy | Cost$ X | Description$ Enchanted creature can't attack or block unless its controller pays {1} for each card in your hand. +S:Mode$ CantBlockUnless | ValidCard$ Creature.AttachedBy | Cost$ X +SVar:X:Count$InYourHand +SVar:Picture:http://www.wizards.com/global/images/magic/general/cowed_by_wisdom.jpg +Oracle:Enchant creature\nEnchanted creature can't attack or block unless its controller pays {1} for each card in your hand. +SetInfo:SOK Common \ No newline at end of file diff --git a/src/main/java/forge/card/staticability/StaticAbility.java b/src/main/java/forge/card/staticability/StaticAbility.java index 709fd184631..b2d4cfd20e4 100644 --- a/src/main/java/forge/card/staticability/StaticAbility.java +++ b/src/main/java/forge/card/staticability/StaticAbility.java @@ -467,7 +467,7 @@ public class StaticAbility { } if (mode.equals("CantBlockUnless")) { - return StaticAbilityCantAttackBlock.applyCantBlockUnlessAbility(this, card); + return StaticAbilityCantAttackBlock.applyCantBlockUnlessAbility(this, card, target); } return null; diff --git a/src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java b/src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java index edbed226863..410f5f87d90 100644 --- a/src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java +++ b/src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java @@ -94,20 +94,31 @@ public class StaticAbilityCantAttackBlock { * * @param stAb * a StaticAbility - * @param card + * @param blocker * the card * @return a Cost */ - public static Cost applyCantBlockUnlessAbility(final StaticAbility stAb, final Card card) { + public static Cost applyCantBlockUnlessAbility(final StaticAbility stAb, final Card blocker, final GameEntity attacker) { final HashMap params = stAb.getMapParams(); final Card hostCard = stAb.getHostCard(); if (params.containsKey("ValidCard") - && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { + && !blocker.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { return null; } + + if (params.containsKey("Attacker") && attacker != null + && !attacker.isValid(params.get("Attacker").split(","), hostCard.getController(), hostCard)) { + return null; + } + String costString = params.get("Cost"); + if ("X".equals(costString)) { + costString = Integer.toString(CardFactoryUtil.xCount(hostCard, hostCard.getSVar("X"))); + } else if ("Y".equals(costString)) { + costString = Integer.toString(CardFactoryUtil.xCount(hostCard, hostCard.getSVar("Y"))); + } - final Cost cost = new Cost(hostCard, params.get("Cost"), true); + final Cost cost = new Cost(hostCard, costString, true); return cost; } diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index 7c4987b65d1..2063ea79d78 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -471,6 +471,38 @@ public class Combat { Singletons.getModel().getGame().getEvents().post(new BlockerAssignedEvent()); } + public final void removeBlockAssignment(final Card attacker, final Card blocker) { + this.attackerMap.get(attacker).remove(blocker); + this.blockerMap.get(blocker).remove(attacker); + if (this.attackerMap.get(attacker).isEmpty()) { + this.blocked.remove(attacker); + } + if (this.blockerMap.get(blocker).isEmpty()) { + this.blockerMap.remove(blocker); + } + } + + /** + *

+ * undoBlockingAssignment. + *

+ * + * @param blocker + * a {@link forge.Card} object. + */ + public final void undoBlockingAssignment(final Card blocker) { + final List att = this.getAttackers(); + for (final Card attacker : att) { + if (this.getBlockers(attacker).contains(blocker)) { + this.getBlockingAttackerList(attacker).remove(blocker); + if (this.getBlockers(attacker).isEmpty()) { + this.blocked.remove(attacker); + } + } + } + this.blockerMap.remove(blocker); + } // undoBlockingAssignment(Card) + /** *

* getAllBlockers. @@ -615,34 +647,13 @@ public class Combat { this.blockerMap.remove(c); for (Card a : attackers) { this.attackerMap.get(a).remove(c); - if (stillDeclaring && this.attackerMap.get(a).size() == 0) { + if (stillDeclaring && this.attackerMap.get(a).isEmpty()) { this.blocked.remove(a); } } } } // removeFromCombat() - /** - *

- * undoBlockingAssignment. - *

- * - * @param blocker - * a {@link forge.Card} object. - */ - public final void undoBlockingAssignment(final Card blocker) { - final List att = this.getAttackers(); - for (final Card attacker : att) { - if (this.getBlockers(attacker).contains(blocker)) { - this.getBlockingAttackerList(attacker).remove(blocker); - if (this.getBlockers(attacker).size() == 0) { - this.blocked.remove(attacker); - } - } - } - this.blockerMap.remove(blocker); - } // undoBlockingAssignment(Card) - /** *

* verifyCreaturesInPlay. @@ -671,7 +682,7 @@ public class Combat { for (final Card attacker : attacking) { final List block = this.getBlockers(attacker); - if (block.size() == 0) { + if (block.isEmpty()) { // this damage is assigned to a player by setPlayerDamage() this.addUnblockedAttacker(attacker); diff --git a/src/main/java/forge/game/phase/PhaseUtil.java b/src/main/java/forge/game/phase/PhaseUtil.java index b7c6541a7b1..9a957d1ac3c 100644 --- a/src/main/java/forge/game/phase/PhaseUtil.java +++ b/src/main/java/forge/game/phase/PhaseUtil.java @@ -27,8 +27,16 @@ import forge.Card; import forge.CardLists; import forge.CardPredicates.Presets; import forge.Singletons; +import forge.card.cost.Cost; +import forge.card.spellability.Ability; +import forge.card.spellability.AbilityStatic; +import forge.card.staticability.StaticAbility; import forge.card.trigger.TriggerType; +import forge.game.GameActionUtil; import forge.game.GameState; +import forge.game.ai.ComputerUtil; +import forge.game.ai.ComputerUtilCost; +import forge.game.player.AIPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.match.CMatchUI; @@ -195,25 +203,59 @@ public class PhaseUtil { * @param game */ public static void handleDeclareBlockers(GameState game) { - game.getCombat().verifyCreaturesInPlay(); + final Combat combat = game.getCombat(); + combat.verifyCreaturesInPlay(); // Handles removing cards like Mogg Flunkies from combat if group block // didn't occur - final List filterList = game.getCombat().getAllBlockers(); + final List filterList = combat.getAllBlockers(); + for (Card blocker : filterList) { + final List attackers = new ArrayList(combat.getAttackersBlockedBy(blocker)); + for (Card attacker : attackers) { + Cost blockCost = new Cost(blocker, "0", true); + // Sort abilities to apply them in proper order + for (Card card : game.getCardsIn(ZoneType.Battlefield)) { + final ArrayList staticAbilities = card.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + Cost additionalCost = stAb.getCostAbility("CantBlockUnless", blocker, attacker); + blockCost = Cost.combine(blockCost, additionalCost); + } + } + + boolean hasPaid = blockCost.getTotalMana().isZero() && blockCost.isOnlyManaCost(); // true if needless to pay + if (!hasPaid) { + final Ability ability = new AbilityStatic(blocker, blockCost, null) { @Override public void resolve() {} }; + ability.setActivatingPlayer(blocker.getController()); + + if (blocker.getController().isHuman()) { + hasPaid = GameActionUtil.payCostDuringAbilityResolve(ability, blockCost, null, game); + } else { // computer + if (ComputerUtilCost.canPayCost(ability, blocker.getController())) { + ComputerUtil.playNoStack((AIPlayer)blocker.getController(), ability, game); + hasPaid = true; + } + } + } + + if ( !hasPaid ) { + combat.removeBlockAssignment(attacker, blocker); + } + } + } for (Card c : filterList) { if (c.hasKeyword("CARDNAME can't attack or block alone.") && c.isBlocking()) { - if (game.getCombat().getAllBlockers().size() < 2) { - game.getCombat().undoBlockingAssignment(c); + if (combat.getAllBlockers().size() < 2) { + combat.undoBlockingAssignment(c); } } } game.getStack().freezeStack(); - game.getCombat().setUnblocked(); + combat.setUnblocked(); List list = new ArrayList(); - list.addAll(game.getCombat().getAllBlockers()); + list.addAll(combat.getAllBlockers()); list = CardLists.filter(list, new Predicate() { @Override @@ -222,12 +264,12 @@ public class PhaseUtil { } }); - final List attList = game.getCombat().getAttackers(); + final List attList = combat.getAttackers(); CombatUtil.checkDeclareBlockers(list); for (final Card a : attList) { - final List blockList = game.getCombat().getBlockers(a); + final List blockList = combat.getBlockers(a); for (final Card b : blockList) { CombatUtil.checkBlockedAttackers(a, b); }