From b2279a8e9c0bcce295eb52fa7c49944f759159de Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 31 Dec 2024 22:07:07 +0400 Subject: [PATCH] tests: added many use cases for must be blocked, must blocking and menace effects (related to #13182) --- .../combat/AttackBlockRestrictionsTest.java | 452 ++++++++++++++++-- .../main/java/mage/game/combat/Combat.java | 15 +- 2 files changed, 423 insertions(+), 44 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java b/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java index c6ae191e7d8..9b759bc2784 100644 --- a/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java @@ -5,15 +5,17 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.game.permanent.Permanent; import org.junit.Assert; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + /** * Test restrictions for choosing attackers and blockers. * - * @author noxx + * @author noxx, JayDi85 */ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { @@ -337,10 +339,9 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { /* * Mogg Flunkies cannot attack alone. Cards like Goblin Assault force all goblins to attack each turn. * Mogg Flunkies should not be able to attack. - */ + */ @Test - public void testMustAttackButCannotAttackAlone() - { + public void testMustAttackButCannotAttackAlone() { /* Mogg Flunkies {1}{R} 3/3 Creature — Goblin Mogg Flunkies can't attack or block alone. @@ -434,11 +435,11 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { @Test public void underworldCerberusBlockedByOneTest() { - /* Underworld Cerberus {3}{B}{3} 6/6 - * Underworld Cerberus can't be blocked except by three or more creatures. - * Cards in graveyards can't be the targets of spells or abilities. - * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. - */ + /* Underworld Cerberus {3}{B}{3} 6/6 + * Underworld Cerberus can't be blocked except by three or more creatures. + * Cards in graveyards can't be the targets of spells or abilities. + * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. + */ addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus"); addCard(Zone.BATTLEFIELD, playerB, "Memnite"); // 1/1 @@ -450,18 +451,18 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { try { execute(); fail("Expected exception not thrown"); - } catch(UnsupportedOperationException e) { + } catch (UnsupportedOperationException e) { assertEquals("Underworld Cerberus is blocked by 1 creature(s). It has to be blocked by 3 or more.", e.getMessage()); } } @Test public void underworldCerberusBlockedByTwoTest() { - /* Underworld Cerberus {3}{B}{3} 6/6 - * Underworld Cerberus can't be blocked except by three or more creatures. - * Cards in graveyards can't be the targets of spells or abilities. - * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. - */ + /* Underworld Cerberus {3}{B}{3} 6/6 + * Underworld Cerberus can't be blocked except by three or more creatures. + * Cards in graveyards can't be the targets of spells or abilities. + * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. + */ addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus"); addCard(Zone.BATTLEFIELD, playerB, "Memnite", 2); // 1/1 @@ -474,7 +475,7 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { try { execute(); fail("Expected exception not thrown"); - } catch(UnsupportedOperationException e) { + } catch (UnsupportedOperationException e) { assertEquals("Underworld Cerberus is blocked by 2 creature(s). It has to be blocked by 3 or more.", e.getMessage()); } } @@ -482,11 +483,11 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { @Test public void underworldCerberusBlockedByThreeTest() { - /* Underworld Cerberus {3}{B}{3} 6/6 - * Underworld Cerberus can't be blocked except by three or more creatures. - * Cards in graveyards can't be the targets of spells or abilities. - * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. - */ + /* Underworld Cerberus {3}{B}{3} 6/6 + * Underworld Cerberus can't be blocked except by three or more creatures. + * Cards in graveyards can't be the targets of spells or abilities. + * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. + */ addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus"); addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 @@ -511,17 +512,17 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { @Test public void underworldCerberusBlockedByTenTest() { - /* Underworld Cerberus {3}{B}{3} 6/6 - * Underworld Cerberus can't be blocked except by three or more creatures. - * Cards in graveyards can't be the targets of spells or abilities. - * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. - */ + /* Underworld Cerberus {3}{B}{3} 6/6 + * Underworld Cerberus can't be blocked except by three or more creatures. + * Cards in graveyards can't be the targets of spells or abilities. + * When Underworld Cerberus dies, exile it and each player returns all creature cards from their graveyard to their hand. + */ addCard(Zone.BATTLEFIELD, playerA, "Underworld Cerberus"); addCard(Zone.BATTLEFIELD, playerB, "Memnite", 10); // 1/1 // Blocked by 10 creatures - this is acceptable as it's >3 attack(3, playerA, "Underworld Cerberus"); - for(int i = 0; i < 10; i++) { + for (int i = 0; i < 10; i++) { block(3, playerB, "Memnite:" + i, "Underworld Cerberus"); } @@ -541,24 +542,24 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); } - + @Test public void irresistiblePreyMustBeBlockedTest() { addCard(Zone.BATTLEFIELD, playerA, "Llanowar Elves"); addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.HAND, playerA, "Irresistible Prey"); - + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable"); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Irresistible Prey", "Llanowar Elves"); // must be blocked - + attack(1, playerA, "Llanowar Elves"); attack(1, playerA, "Alpha Myr"); - + // attempt to block the creature that doesn't have "must be blocked" block(1, playerB, "Bronze Sable", "Alpha Myr"); - + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -568,4 +569,383 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { assertTapped("Alpha Myr", true); assertLife(playerB, 18); } -} + + @Test + public void test_MustBeBlocked_nothing() { + // Fear of Being Hunted must be blocked if able. + addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2 + + attack(1, playerA, "Fear of Being Hunted"); + checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted"); + checkBlockers("no blocker", 1, playerB, ""); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 4); + } + + @Test + public void test_MustBeBlocked_1_blocker() { + // Fear of Being Hunted must be blocked if able. + addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Alpha Myr", 1); // 2/1 + + attack(1, playerA, "Fear of Being Hunted"); + checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted"); + checkBlockers("forced x1 blocker", 1, playerB, "Alpha Myr"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Fear of Being Hunted", 1); + } + + @Test + public void test_MustBeBlocked_many_blockers_good() { + // Fear of Being Hunted must be blocked if able. + addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 10); // 3/3 + + // TODO: human logic can't be tested (until isHuman replaced by ~isComputer), so current use case will + // take first available blocker + attack(1, playerA, "Fear of Being Hunted"); + checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted"); + checkBlockers("x1 optimal blocker", 1, playerB, "Spectral Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Fear of Being Hunted", 1); + } + + @Test + public void test_MustBeBlocked_many_blockers_bad() { + // Fear of Being Hunted must be blocked if able. + addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 10); // 1/1 + + // TODO: human logic can't be tested (until isHuman replaced by ~isComputer), so current use case will + // take first available blocker + attack(1, playerA, "Fear of Being Hunted"); + checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted"); + checkBlockers("x1 optimal blocker", 1, playerB, "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertPermanentCount(playerA, "Fear of Being Hunted", 1); + } + + @Test + @Ignore + // TODO: enable and duplicate for AI -- after implement choose blocker logic and isHuman replace by ~isComputer + public void test_MustBeBlocked_many_blockers_optimal() { + // Fear of Being Hunted must be blocked if able. + addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Deadbridge Goliath", 1); // 5/5 + + attack(1, playerA, "Fear of Being Hunted"); + checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted"); + checkBlockers("x1 optimal blocker", 1, playerB, "Deadbridge Goliath"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Fear of Being Hunted", 1); + } + + @Test + public void test_MustBlocking_zero_blockers() { + // All creatures able to block target creature this turn do so. + addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G} + // + // Menace + // Each creature you control with menace can't be blocked except by three or more creatures. + addCard(Zone.BATTLEFIELD, playerA, "Sonorous Howlbonder"); // 2/2 + + // prepare + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodscent", "Sonorous Howlbonder"); + + attack(1, playerA, "Sonorous Howlbonder"); + checkAttackers("x1 attacker", 1, playerA, "Sonorous Howlbonder"); + checkBlockers("no blocker", 1, playerB, ""); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + assertPermanentCount(playerA, "Sonorous Howlbonder", 1); + } + + @Test + public void test_MustBlocking_full_blockers() { + // All creatures able to block target creature this turn do so. + addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G} + // + // Menace + // Each creature you control with menace can't be blocked except by three or more creatures. + addCard(Zone.BATTLEFIELD, playerA, "Sonorous Howlbonder"); // 2/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + + // prepare + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodscent", "Sonorous Howlbonder"); + + attack(1, playerA, "Sonorous Howlbonder"); + setChoiceAmount(playerA, 1); // assign damage to 1 of 3 blocking memnites + checkAttackers("x1 attacker", 1, playerA, "Sonorous Howlbonder"); + checkBlockers("x3 blockers", 1, playerB, "Memnite", "Memnite", "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Sonorous Howlbonder", 1); + } + + @Test + public void test_MustBlocking_many_blockers() { + // possible bug: AI's blockers auto-fix assign too many blockers (e.g. x10 instead x3 by required effect) + + // All creatures able to block target creature this turn do so. + addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G} + // + // Menace + // Each creature you control with menace can't be blocked except by three or more creatures. + addCard(Zone.BATTLEFIELD, playerA, "Sonorous Howlbonder"); // 2/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 5); // 1/1 + + // prepare + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodscent", "Sonorous Howlbonder"); + + attack(1, playerA, "Sonorous Howlbonder"); + setChoiceAmount(playerA, 1); // assign damage to 1 of 3 blocking memnites + checkAttackers("x1 attacker", 1, playerA, "Sonorous Howlbonder"); + checkBlockers("all blockers", 1, playerB, "Memnite", "Memnite", "Memnite", "Memnite", "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Sonorous Howlbonder", 1); + } + + @Test + @Ignore + // TODO: need exception fix - java.lang.UnsupportedOperationException: Sonorous Howlbonder is blocked by 1 creature(s). It has to be blocked by 3 or more. + // It's auto-fix in block configuration, so exception must be fixed cause AI works with it + public void test_MustBlocking_low_blockers() { + // possible bug: exception on wrong block configuration + // if effect require x3 blockers, but opponent has only 1 then it must use 1 blocker anyway + + // All creatures able to block target creature this turn do so. + addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G} + // + // Menace + // Each creature you control with menace can't be blocked except by three or more creatures. + addCard(Zone.BATTLEFIELD, playerA, "Sonorous Howlbonder"); // 2/2 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // 1/1 + + // prepare + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodscent", "Sonorous Howlbonder"); + + attack(1, playerA, "Sonorous Howlbonder"); + setChoiceAmount(playerA, 1); // assign damage to 1 of 3 blocking memnites + checkAttackers("x1 attacker", 1, playerA, "Sonorous Howlbonder"); + checkBlockers("one possible blocker", 1, playerB, "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Sonorous Howlbonder", 1); + } + + @Test + public void test_MustBeBlockedWithMenace_0_blockers() { + // At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s + // power until end of turn. That creature must be blocked this combat if able. + addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + // Menace + addCard(Zone.BATTLEFIELD, playerA, "Alley Strangler", 1); // 2/3 + + addTarget(playerA, "Alley Strangler"); // boost target + setChoice(playerA, true); // boost target + attack(1, playerA, "Alley Strangler"); + checkAttackers("x1 attacker", 1, playerA, "Alley Strangler"); + checkBlockers("no blocker", 1, playerB, ""); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 4); + assertGraveyardCount(playerA, "Alley Strangler", 0); + } + + @Test + @Ignore // TODO: need improve of block configuration auto-fix (block by x2 instead x1) + public void test_MustBeBlockedWithMenace_all_blockers() { + // At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s + // power until end of turn. That creature must be blocked this combat if able. + addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + // Menace + addCard(Zone.BATTLEFIELD, playerA, "Alley Strangler", 1); // 2/3 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 2); // 1/1 + + // If the target creature has menace, two creatures must block it if able. + // (2020-06-23) + + addTarget(playerA, "Alley Strangler"); // boost target + setChoice(playerA, true); // boost target + attack(1, playerA, "Alley Strangler"); + checkAttackers("x1 attacker", 1, playerA, "Alley Strangler"); + checkBlockers("x2 blockers", 1, playerB, "Memnite", "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Alley Strangler", 0); + } + + @Test + @Ignore // TODO: need improve of block configuration auto-fix (block by x2 instead x1) + public void test_MustBeBlockedWithMenace_many_blockers() { + // At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s + // power until end of turn. That creature must be blocked this combat if able. + addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + // Menace + addCard(Zone.BATTLEFIELD, playerA, "Alley Strangler", 1); // 2/3 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 10); // 1/1 + + // If the target creature has menace, two creatures must block it if able. + // (2020-06-23) + + addTarget(playerA, "Alley Strangler"); // boost target + setChoice(playerA, true); // boost target + attack(1, playerA, "Alley Strangler"); + checkAttackers("x1 attacker", 1, playerA, "Alley Strangler"); + checkBlockers("x2 blockers", 1, playerB, "Memnite", "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Alley Strangler", 0); + } + + @Test + @Ignore + // TODO: need auto-fix cause AI use it (it must ignore bad blocker configuration and allow to pass without blockers at all) + public void test_MustBeBlockedWithMenace_low_blockers_auto() { + // At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s + // power until end of turn. That creature must be blocked this combat if able. + addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + // Menace + addCard(Zone.BATTLEFIELD, playerA, "Alley Strangler", 1); // 2/3 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // 1/1 + + // If the target creature has menace, two creatures must block it if able. + // (2020-06-23) + // + // If a creature is required to block a creature with menace, another creature must also block that creature + // if able. If none can, the creature that’s required to block can block another creature or not block at all. + // (2020-04-17) + + // auto-fix block config inside + + addTarget(playerA, "Alley Strangler"); // boost target + setChoice(playerA, true); // boost target + attack(1, playerA, "Alley Strangler"); + checkAttackers("x1 attacker", 1, playerA, "Alley Strangler"); + checkBlockers("no blockers", 1, playerB, ""); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 4); + assertGraveyardCount(playerA, "Alley Strangler", 0); + } + + @Test + @Ignore + // TODO: need exception fix java.lang.UnsupportedOperationException: Alley Strangler is blocked by 1 creature(s). It has to be blocked by 2 or more. + // It's ok to have such exception in unit tests from manual setup + // If it's impossible to auto-fix, then keep that error and ignore the test + public void test_MustBeBlockedWithMenace_low_blockers_manual() { + // At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s + // power until end of turn. That creature must be blocked this combat if able. + addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // + // Menace + addCard(Zone.BATTLEFIELD, playerA, "Alley Strangler", 1); // 2/3 + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // 1/1 + + // If the target creature has menace, two creatures must block it if able. + // (2020-06-23) + // + // If a creature is required to block a creature with menace, another creature must also block that creature + // if able. If none can, the creature that’s required to block can block another creature or not block at all. + // (2020-04-17) + + // define blocker manual + + addTarget(playerA, "Alley Strangler"); // boost target + setChoice(playerA, true); // boost target + attack(1, playerA, "Alley Strangler"); + block(1, playerB, "Memnite", "Alley Strangler"); + checkAttackers("x1 attacker", 1, playerA, "Alley Strangler"); + checkBlockers("no blockers", 1, playerB, ""); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 4); + assertGraveyardCount(playerA, "Alley Strangler", 0); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index b62a906a966..21a534f1786 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -893,7 +893,7 @@ public class Combat implements Serializable, Copyable { Map minNumberOfBlockersMap = new HashMap<>(); Map minPossibleBlockersMap = new HashMap<>(); - // check mustBlock requirements of creatures from opponents of attacking player + // FIND attackers and potential blockers for "must be blocked" effects for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES_CONTROLLED, player.getId(), game)) { // creature is controlled by an opponent of the attacker if (opponents.contains(creature.getControllerId())) { @@ -1012,7 +1012,7 @@ public class Combat implements Serializable, Copyable { if (toBeBlockedCreature != null) { CombatGroup toBeBlockedGroup = findGroup(toBeBlockedCreature); if (toBeBlockedGroup != null && toBeBlockedGroup.getDefendingPlayerId().equals(creature.getControllerId())) { - minNumberOfBlockersMap.put(toBeBlockedCreature, effect.getMinNumberOfBlockers()); + minNumberOfBlockersMap.put(toBeBlockedCreature, effect.getMinNumberOfBlockers()); // TODO: fail on multiple effects 1 + 2 min blockers? Permanent toBeBlockedCreaturePermanent = game.getPermanent(toBeBlockedCreature); if (toBeBlockedCreaturePermanent != null) { minPossibleBlockersMap.put(toBeBlockedCreature, toBeBlockedCreaturePermanent.getMinBlockedBy()); @@ -1096,12 +1096,10 @@ public class Combat implements Serializable, Copyable { } } - } - } - // check if for attacking creatures with mustBeBlockedByAtLeastX requirements are fulfilled + // APPLY potential blockers to attackers with "must be blocked" effects for (UUID toBeBlockedCreatureId : mustBeBlockedByAtLeastX.keySet()) { for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (combatGroup.getAttackers().contains(toBeBlockedCreatureId)) { @@ -1124,6 +1122,8 @@ public class Combat implements Serializable, Copyable { if (!requirementFulfilled) { // creature is not blocked but has possible blockers if (controller.isHuman()) { + // HUMAN logic - send warning about wrong blocker config and repeat declare + // TODO: replace isHuman by !isComputer for working unit tests Permanent toBeBlockedCreature = game.getPermanent(toBeBlockedCreatureId); if (toBeBlockedCreature != null) { // check if all possible blocker block other creatures they are forced to block @@ -1142,9 +1142,8 @@ public class Combat implements Serializable, Copyable { } } } - } else { - // take the first potential blocker from the set to block for the AI + // AI logic - auto-fix wrong blocker config (take the first potential blocker) for (UUID possibleBlockerId : mustBeBlockedByAtLeastX.get(toBeBlockedCreatureId)) { String blockRequiredMessage = isCreatureDoingARequiredBlock( possibleBlockerId, toBeBlockedCreatureId, mustBeBlockedByAtLeastX, game); @@ -1167,8 +1166,8 @@ public class Combat implements Serializable, Copyable { } } } - } + // check if creatures are forced to block but do not block at all or block creatures they are not forced to block StringBuilder sb = new StringBuilder(); for (Map.Entry> entry : creatureMustBlockAttackers.entrySet()) {