diff --git a/Mage.Sets/src/mage/cards/a/AmberGristleOMaul.java b/Mage.Sets/src/mage/cards/a/AmberGristleOMaul.java index 9718ebc71b4..fde37635661 100644 --- a/Mage.Sets/src/mage/cards/a/AmberGristleOMaul.java +++ b/Mage.Sets/src/mage/cards/a/AmberGristleOMaul.java @@ -72,6 +72,7 @@ enum AmberGristleOMaulValue implements DynamicValue { .map(game::getControllerId) .anyMatch(sourceAbility::isControlledBy)) .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .distinct() .map(game::getPlayer) .filter(Objects::nonNull) diff --git a/Mage.Sets/src/mage/cards/a/AstralConfrontation.java b/Mage.Sets/src/mage/cards/a/AstralConfrontation.java index 888fdebb72e..10ee01af8fe 100644 --- a/Mage.Sets/src/mage/cards/a/AstralConfrontation.java +++ b/Mage.Sets/src/mage/cards/a/AstralConfrontation.java @@ -16,6 +16,7 @@ import mage.game.Game; import mage.game.combat.CombatGroup; import mage.target.common.TargetCreaturePermanent; +import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -66,6 +67,7 @@ enum AstralConfrontationValue implements DynamicValue { .anyMatch(sourceAbility::isControlledBy) ) .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .distinct() .filter(opponents::contains) .mapToInt(x -> 1) diff --git a/Mage.Sets/src/mage/cards/f/FiremaneCommando.java b/Mage.Sets/src/mage/cards/f/FiremaneCommando.java index 2910a194f96..b9428440b29 100644 --- a/Mage.Sets/src/mage/cards/f/FiremaneCommando.java +++ b/Mage.Sets/src/mage/cards/f/FiremaneCommando.java @@ -17,6 +17,7 @@ import mage.game.combat.CombatGroup; import mage.game.events.GameEvent; import mage.target.targetpointer.FixedTarget; +import java.util.Objects; import java.util.UUID; /** @@ -84,6 +85,7 @@ class FiremaneCommandoTriggeredAbility extends TriggeredAbilityImpl { .getGroups() .stream() .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .anyMatch(this.getControllerId()::equals); this.getEffects().setValue("damage", youWereAttacked ? 0 : 1); this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); diff --git a/Mage.Sets/src/mage/cards/n/NemesisPhoenix.java b/Mage.Sets/src/mage/cards/n/NemesisPhoenix.java index 16bbb5edeb1..d58da3b3b3e 100644 --- a/Mage.Sets/src/mage/cards/n/NemesisPhoenix.java +++ b/Mage.Sets/src/mage/cards/n/NemesisPhoenix.java @@ -16,6 +16,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.combat.CombatGroup; +import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -72,6 +73,7 @@ enum NemesisPhoenixCondition implements Condition { .map(game::getControllerId) .anyMatch(source::isControlledBy)) .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .distinct() .filter(opponents::contains) .count() >= 2; diff --git a/Mage.Sets/src/mage/cards/p/PackAttack.java b/Mage.Sets/src/mage/cards/p/PackAttack.java index cf35adea4fe..183bfa35b21 100644 --- a/Mage.Sets/src/mage/cards/p/PackAttack.java +++ b/Mage.Sets/src/mage/cards/p/PackAttack.java @@ -55,6 +55,7 @@ enum PackAttackValue implements DynamicValue { .getGroups() .stream() .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .distinct() .map(game::getPlayer) .filter(Objects::nonNull) diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/RemoveFromCombatTest.java b/Mage.Tests/src/test/java/org/mage/test/combat/RemoveFromCombatTest.java index a4856415130..503a37ea8fc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/combat/RemoveFromCombatTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/combat/RemoveFromCombatTest.java @@ -1,4 +1,3 @@ - package org.mage.test.combat; import mage.constants.PhaseStep; @@ -9,8 +8,7 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class RemoveFromCombatTest extends CardTestPlayerBase { @@ -21,7 +19,7 @@ public class RemoveFromCombatTest extends CardTestPlayerBase { * continued attacking and dealt 3 damage to me. */ @Test - public void testLeavesCombatIfNoLongerACreature() { + public void test_LeavesCombatIfNoLongerACreature() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); addCard(Zone.HAND, playerA, "Lightning Blast", 1); @@ -51,7 +49,61 @@ public class RemoveFromCombatTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - } + @Test + public void test_Defender_AttackPlayer() { + // Enchant player + // Creatures attacking enchanted player have trample. + addCard(Zone.HAND, playerA, "Curse of Hospitality", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Alpha Myr", 1); // 2/1 + + // prepare + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Hospitality"); + addTarget(playerA, playerB); + + // attack and get trumple + attack(1, playerA, "Grizzly Bears"); + block(1, playerB, "Alpha Myr", "Grizzly Bears"); + setChoiceAmount(playerA, 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 1); // must get 1 from trumple + } + + @Test + public void test_Defender_AttackPlaneswalkerAndRemoveDefender() { + // possible bug: NPE error on defender remove from battle + + // Enchant player + // Creatures attacking enchanted player have trample. + addCard(Zone.HAND, playerA, "Curse of Hospitality", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Adaptive Snapjaw", 1); // 6/2 + addCard(Zone.BATTLEFIELD, playerB, "Jace, Memory Adept", 1); // 4 + addCard(Zone.BATTLEFIELD, playerB, "Alpha Myr", 1); // 2/1 + + // prepare + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Hospitality"); + addTarget(playerA, playerB); + + // attack planeswalker and remove it from battlefield due damage + attack(1, playerA, "Adaptive Snapjaw", "Jace, Memory Adept"); + attack(1, playerA, "Grizzly Bears", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + assertGraveyardCount(playerB, "Jace, Memory Adept", 1); + } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 0ebe172136a..3ba9783342c 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -2557,6 +2557,7 @@ public abstract class GameImpl implements Game { .getGroups() .stream() .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .noneMatch(perm.getId()::equals) && this.getPlayer(perm.getProtectorId()) == null || perm.isControlledBy(perm.getProtectorId())) { diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index e64d18ed337..0f831e5633d 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -1594,6 +1594,7 @@ public class Combat implements Serializable, Copyable { .stream() .filter(group -> group.getAttackers().contains(attackerId)) .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) .findFirst() .orElse(null); } diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index e5316975bb9..695f442e4b2 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -31,7 +31,7 @@ public class CombatGroup implements Serializable, Copyable { protected List attackerOrder = new ArrayList<>(); protected Map players = new HashMap<>(); protected boolean blocked; - protected UUID defenderId; // planeswalker or player + protected UUID defenderId; // planeswalker or player, can be null after remove from combat (e.g. due damage) protected UUID defendingPlayerId; protected boolean defenderIsPermanent;