mirror of
https://github.com/correl/mage.git
synced 2024-11-25 11:09:53 +00:00
Attacking you abilities and filters - fixed that planeswalker removes from a combat can cause a game error (NPE error, example: Curse of Hospitality)
This commit is contained in:
parent
1f4dfd08ce
commit
9d9916280a
9 changed files with 68 additions and 6 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -55,6 +55,7 @@ enum PackAttackValue implements DynamicValue {
|
|||
.getGroups()
|
||||
.stream()
|
||||
.map(CombatGroup::getDefenderId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -1594,6 +1594,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
.stream()
|
||||
.filter(group -> group.getAttackers().contains(attackerId))
|
||||
.map(CombatGroup::getDefenderId)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
protected List<UUID> attackerOrder = new ArrayList<>();
|
||||
protected Map<UUID, UUID> 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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue