diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java index cbd8ba96d6c..6797cdc95a5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java @@ -76,4 +76,40 @@ public class BlockRequirementTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Silvercoat Lion", 1); assertPermanentCount(playerB, "Prized Unicorn", 1); } + + /** + * Joraga Invocation is bugged big time. He cast it with 2 creatures out. I + * only had one untapped creature. Blocked one of his, hit Done, error + * message popped up saying the other one needed to be blocked in an + * infinite loop. Had to shut down the program via Task Manager. + */ + @Test + public void testJoragaInvocationTest() { + addCard(Zone.BATTLEFIELD, playerB, "Forest", 6); + // Each creature you control gets +3/+3 until end of turn and must be blocked this turn if able. + addCard(Zone.HAND, playerB, "Joraga Invocation"); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); // 2/4 + + // Swampwalk + addCard(Zone.BATTLEFIELD, playerA, "Bog Wraith"); // 3/3 + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Joraga Invocation"); + + // Silvercoat Lion has not to block because it has to pay {3} to block + attack(2, playerB, "Silvercoat Lion"); + attack(2, playerB, "Pillarfield Ox"); + block(2, playerA, "Bog Wraith", "Pillarfield Ox"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 15); + + assertGraveyardCount(playerB, "Joraga Invocation", 1); + assertPowerToughness(playerB, "Silvercoat Lion", 5, 5); + assertPowerToughness(playerB, "Pillarfield Ox", 5, 7); + assertGraveyardCount(playerA, "Bog Wraith", 1); + } + } diff --git a/Mage/src/mage/cards/repository/CardRepository.java b/Mage/src/mage/cards/repository/CardRepository.java index d4364e3275a..970c5670c3d 100644 --- a/Mage/src/mage/cards/repository/CardRepository.java +++ b/Mage/src/mage/cards/repository/CardRepository.java @@ -60,7 +60,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 39; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 22; + private static final long CARD_CONTENT_VERSION = 23; private final Random random = new Random(); private Dao cardDao; diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 5c8a5154da7..5ba13a273a3 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -665,7 +665,7 @@ public class Combat implements Serializable, Copyable { } - // check attacking creature mustBeBlockedByAtLeastOne + // check if for attacking creatures with mustBeBlockedByAtLeastOne requirements are fulfilled for (UUID toBeBlockedCreatureId : mustBeBlockedByAtLeastOne.keySet()) { for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (combatGroup.getBlockers().isEmpty() && combatGroup.getAttackers().contains(toBeBlockedCreatureId)) { @@ -675,56 +675,27 @@ public class Combat implements Serializable, Copyable { if (toBeBlockedCreature != null) { // check if all possible blocker block other creatures they are forced to block // read through all possible blockers - boolean possibleBlockerAvailable = false; for (UUID possibleBlockerId : mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId)) { - Set forcingAttackers = creatureMustBlockAttackers.get(possibleBlockerId); - if (forcingAttackers == null) { - // no other creature forces the blocker to block -> it's available - possibleBlockerAvailable = true; - break; + String blockRequiredMessage = isCreatureDoingARequiredBlock(possibleBlockerId, mustBeBlockedByAtLeastOne, game); + if (blockRequiredMessage != null) { // message means not required + game.informPlayer(controller, blockRequiredMessage + "It's a requirement to block " + toBeBlockedCreature.getIdName()); + return false; } - // get the attackers he blocks - List blockedAttackers = null; - for (CombatGroup combatGroupToCheck : game.getCombat().getGroups()) { - if (combatGroupToCheck.getBlockers().contains(possibleBlockerId)) { - blockedAttackers = combatGroupToCheck.getAttackers(); - break; - } - } - if (blockedAttackers == null) { - // he blocks no other creature -> it's available - possibleBlockerAvailable = true; - break; - } - - // get attackers forcing the possible blocker to block - possibleBlockerAvailable = true; - for (UUID blockedAttackerId : blockedAttackers) { - if (creatureMustBlockAttackers.get(possibleBlockerId).contains(blockedAttackerId)) { - possibleBlockerAvailable = false; - break; - } - } - if (possibleBlockerAvailable) { - break; - } - } - if (possibleBlockerAvailable) { - if (!game.isSimulation()) { - game.informPlayer(controller, new StringBuilder(toBeBlockedCreature.getLogName()).append(" has to be blocked by at least one creature.").toString()); - } - return false; } } } else { // take the first potential blocker from the set to block for the AI - UUID blockingCreatureId = mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId).iterator().next(); - Permanent blockingCreature = game.getPermanent(blockingCreatureId); - if (blockingCreature != null) { - Player defender = game.getPlayer(blockingCreature.getControllerId()); - if (defender != null) { - defender.declareBlocker(defender.getId(), blockingCreatureId, toBeBlockedCreatureId, game); + for (UUID possibleBlockerId : mustBeBlockedByAtLeastOne.get(toBeBlockedCreatureId)) { + String blockRequiredMessage = isCreatureDoingARequiredBlock(possibleBlockerId, mustBeBlockedByAtLeastOne, game); + if (blockRequiredMessage != null) { + // set the block + Permanent possibleBlocker = game.getPermanent(possibleBlockerId); + Player defender = game.getPlayer(possibleBlocker.getControllerId()); + if (defender != null) { + defender.declareBlocker(defender.getId(), possibleBlockerId, toBeBlockedCreatureId, game); + } + break; } } } @@ -800,6 +771,48 @@ public class Combat implements Serializable, Copyable { return true; } + /** + * Checks if a possible creature for a block is already doing another + * required block + * + * @param possibleBlockerId + * @param mustBeBlockedByAtLeastOne + * @param game + * @return null block is required otherwise message with reason why not + */ + protected String isCreatureDoingARequiredBlock(UUID possibleBlockerId, Map> mustBeBlockedByAtLeastOne, Game game) { + Permanent possibleBlocker = game.getPermanent(possibleBlockerId); + if (possibleBlocker != null) { + if (possibleBlocker.getBlocking() == 0) { + return possibleBlocker.getIdName() + " does not block, but could block creatures with requirement to be blocked."; + } + Set forcingAttackers = creatureMustBlockAttackers.get(possibleBlockerId); + if (forcingAttackers == null) { + // no other creature forces the blocker to block -> it's available + // check now, if it already blocks a creature that mustBeBlockedByAtLeastOne + if (possibleBlocker.getBlocking() > 0) { + CombatGroup combatGroupOfPossibleBlocker = findGroupOfBlocker(possibleBlockerId); + for (UUID blockedAttackerId : combatGroupOfPossibleBlocker.getAttackers()) { + if (mustBeBlockedByAtLeastOne.containsKey(blockedAttackerId)) { + // blocks a creature that has to be blocked by at least one + if (combatGroupOfPossibleBlocker.getBlockers().size() == 1) { + // the creature blocks alone already a creature that has to be blocked by at least one, + // so this is ok + return null; + } + // TODO: Check if the attacker is already blocked by another creature + // and despite there is need that this attacker blocks this attacker also + // I don't know why + Permanent blockedAttacker = game.getPermanent(blockedAttackerId); + return possibleBlocker.getIdName() + " blocks with other creatures " + blockedAttacker.getIdName() + ", which has to be blocked by only one creature. "; + } + } + } + } + } + return null; + } + /** * Checks the canBeBlockedCheckAfter RestrictionEffect Is the block still * valid after all block decisions are done @@ -1037,6 +1050,15 @@ public class Combat implements Serializable, Copyable { return null; } + public CombatGroup findGroupOfBlocker(UUID blockerId) { + for (CombatGroup group : groups) { + if (group.getBlockers().contains(blockerId)) { + return group; + } + } + return null; + } + // public int totalUnblockedDamage(Game game) { // int total = 0; // for (CombatGroup group : groups) {