diff --git a/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java b/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java index 09ead1ed2e1..4c5578c65b7 100644 --- a/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java +++ b/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java @@ -61,7 +61,7 @@ class ZilorthaStrengthIncarnateEffect extends ContinuousEffectImpl { // Change the rule FilterCreaturePermanent filter = StaticFilters.FILTER_PERMANENT_CREATURE.copy(); filter.add(new ControllerIdPredicate(source.getControllerId())); - game.addUsePowerInsteadOfToughnessForDamageLethalityFilter(source.getSourceId(), filter); + game.addPowerInsteadOfToughnessForDamageLethalityFilter(source.getSourceId(), filter); return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/ZilorthaStrengthIncarnateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/ZilorthaStrengthIncarnateTest.java index a63703491f6..6e207ba17f4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/ZilorthaStrengthIncarnateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/ZilorthaStrengthIncarnateTest.java @@ -1,18 +1,25 @@ package org.mage.test.cards.single.iko; +import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.After; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; public class ZilorthaStrengthIncarnateTest extends CardTestPlayerBase { - @Test - public void testNotPresent_damageResolvesLethalityAsNormal() { - addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth", 1); - addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer", 1); + @After + public void after() { + assertAllCommandsUsed(); + } - attack(2, playerA, "Savai Sabertooth"); - block(2, playerB, "Drannith Healer", "Savai Sabertooth"); + @Test + public void testNotPresent_combatDamageResolvesLethalityAsNormal() { + addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth"); + addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer"); + + attack(1, playerA, "Savai Sabertooth"); + block(1, playerB, "Drannith Healer", "Savai Sabertooth"); execute(); @@ -20,4 +27,131 @@ public class ZilorthaStrengthIncarnateTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Drannith Healer", 1); } + @Test + public void testPresent_combatDamageResolvesLethalityUsingPower() { + addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth"); + addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer"); + + attack(1, playerA, "Savai Sabertooth"); + block(1, playerB, "Drannith Healer", "Savai Sabertooth"); + + execute(); + + assertGraveyardCount(playerA, "Savai Sabertooth", 0); + assertGraveyardCount(playerB, "Drannith Healer", 1); + } + + /* + * 2020-04-17 + * A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it. + */ + @Test + public void testPresent_oneDamageRequiredToDestroyZeroPowerCreature() { + addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerA, "Aegis Turtle"); + addCard(Zone.BATTLEFIELD, playerB, "Aegis Turtle"); + + attack(1, playerA, "Aegis Turtle"); + block(1, playerB, "Aegis Turtle", "Aegis Turtle"); + + execute(); + + assertGraveyardCount(playerA, "Aegis Turtle", 0); + assertGraveyardCount(playerB, "Aegis Turtle", 0); + } + + @Test + public void testNotPresent_flameSpillResolvesAsNormal() { + addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + addCard(Zone.HAND, playerB, "Flame Spill"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Flame Spill", "Savai Sabertooth"); + + execute(); + + assertGraveyardCount(playerA, "Savai Sabertooth", 1); + assertGraveyardCount(playerB, "Flame Spill", 1); + + assertLife(playerA, 17); + } + + @Test + public void testPresent_flameSpillResolvesUsingPower() { + addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + addCard(Zone.HAND, playerB, "Flame Spill"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Flame Spill", "Savai Sabertooth"); + + execute(); + + assertGraveyardCount(playerA, "Savai Sabertooth", 1); + assertGraveyardCount(playerB, "Flame Spill", 1); + + assertLife(playerA, 19); + } + + /* + * 2020-04-17 + * Because damage remains marked on a creature until the damage is removed as the turn ends, nonlethal damage dealt to a creature you control may become lethal if Zilortha enters or leaves the battlefield during that turn. + */ + @Test + public void testPresent_leavesBattlefield_damageResolvesLethalityUsingPower_thenCheckedAgainstToughness() { + addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth"); + addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer"); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3); + addCard(Zone.HAND, playerB, "Murder"); + + attack(1, playerA, "Savai Sabertooth"); + block(1, playerB, "Drannith Healer", "Savai Sabertooth"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Murder", "Zilortha, Strength Incarnate"); + + execute(); + + assertGraveyardCount(playerA, "Zilortha, Strength Incarnate", 1); + assertGraveyardCount(playerA, "Savai Sabertooth", 1); + assertGraveyardCount(playerB, "Drannith Healer", 1); + assertGraveyardCount(playerB, "Murder", 1); + } + + @Test + public void testPresent_ownedByBothPlayers() { + addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerA, "Maned Serval"); + addCard(Zone.BATTLEFIELD, playerB, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerB, "Maned Serval"); + + attack(1, playerA, "Maned Serval"); + block(1, playerB, "Maned Serval", "Maned Serval"); + + execute(); + + assertGraveyardCount(playerA, "Maned Serval", 1); + assertGraveyardCount(playerB, "Maned Serval", 1); + } + + @Test + public void testAbsent_entersBattlefield_damageResolvesLethalityUsingToughness_thenCheckedAgainstPower() { + addCard(Zone.HAND, playerA, "Zilortha, Strength Incarnate"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + addCard(Zone.BATTLEFIELD, playerA, "Maned Serval"); + addCard(Zone.BATTLEFIELD, playerB, "Maned Serval"); + + attack(1, playerA, "Maned Serval"); + block(1, playerB, "Maned Serval", "Maned Serval"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Zilortha, Strength Incarnate"); + + execute(); + + assertGraveyardCount(playerA, "Maned Serval", 1); + assertGraveyardCount(playerB, "Maned Serval", 0); + } } diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index c13da0ceb26..f674c5c3bcf 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -489,8 +489,8 @@ public interface Game extends MageItem, Serializable { return getCommandersIds(player, CommanderCardType.ANY); } - void addUsePowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter); + void addPowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter); - List getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters(); + List getActivePowerInsteadOfToughnessForDamageLethalityFilters(); } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 212d9f1d6f2..efa40d6f72f 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1,7 +1,6 @@ package mage.game; import mage.MageException; -import mage.MageInt; import mage.MageObject; import mage.abilities.*; import mage.abilities.common.AttachableToRestrictedAbility; @@ -1898,7 +1897,7 @@ public abstract class GameImpl implements Game, Serializable { List legendary = new ArrayList<>(); List worldEnchantment = new ArrayList<>(); - List usePowerInsteadOfToughnessForDamageLethalityFilters = getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters(); + List usePowerInsteadOfToughnessForDamageLethalityFilters = getActivePowerInsteadOfToughnessForDamageLethalityFilters(); for (Permanent perm : getBattlefield().getAllActivePermanents()) { if (perm.isCreature()) { //20091005 - 704.5f @@ -1911,12 +1910,14 @@ public abstract class GameImpl implements Game, Serializable { else { /* * for handling Zilortha, Strength Incarnate: - * Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on. + * 2020-04-17: Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on. */ boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream() .anyMatch(filter -> filter.match(perm, this)); - MageInt lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ? perm.getPower() : perm.getToughness(); - if (lethalDamageThreshold.getValue() <= perm.getDamage() || perm.isDeathtouched()) { + int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ? + // Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it. + Math.max(perm.getPower().getValue(), 1) : perm.getToughness().getValue(); + if (lethalDamageThreshold <= perm.getDamage() || perm.isDeathtouched()) { if (perm.destroy(null, this, false)) { somethingHappened = true; continue; @@ -3321,12 +3322,12 @@ public abstract class GameImpl implements Game, Serializable { } @Override - public void addUsePowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter) { + public void addPowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter) { usePowerInsteadOfToughnessForDamageLethalityFilters.putIfAbsent(source, filter); } @Override - public List getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters() { + public List getActivePowerInsteadOfToughnessForDamageLethalityFilters() { return usePowerInsteadOfToughnessForDamageLethalityFilters.isEmpty() ? emptyList() : getBattlefield().getAllActivePermanents().stream() .map(Card::getId) .filter(usePowerInsteadOfToughnessForDamageLethalityFilters::containsKey) diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 378e4bcc249..a190177e20f 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -1,22 +1,11 @@ package mage.game.combat; -import java.io.Serializable; -import java.util.*; -import java.util.stream.Stream; - -import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility; import mage.abilities.common.ControllerDivideCombatDamageAbility; import mage.abilities.common.DamageAsThoughNotBlockedAbility; -import mage.abilities.keyword.BandingAbility; -import mage.abilities.keyword.BandsWithOtherAbility; -import mage.abilities.keyword.CantBlockAloneAbility; -import mage.abilities.keyword.DeathtouchAbility; -import mage.abilities.keyword.DoubleStrikeAbility; -import mage.abilities.keyword.FirstStrikeAbility; -import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.*; import mage.constants.AsThoughEffectType; import mage.constants.Outcome; import mage.filter.StaticFilters; @@ -27,7 +16,9 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.Copyable; -import static java.util.Collections.emptyList; +import java.io.Serializable; +import java.util.*; +import java.util.stream.Stream; /** * @@ -937,14 +928,17 @@ public class CombatGroup implements Serializable, Copyable { } public static int getLethalDamage(Permanent damagedPermanent, Game game) { - List usePowerInsteadOfToughnessForDamageLethalityFilters = game.getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters(); + List usePowerInsteadOfToughnessForDamageLethalityFilters = game.getActivePowerInsteadOfToughnessForDamageLethalityFilters(); /* * for handling Zilortha, Strength Incarnate: + * 2020-04-17 * Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on. */ boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream() .anyMatch(filter -> filter.match(damagedPermanent, game)); - MageInt lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality? damagedPermanent.getPower() : damagedPermanent.getToughness(); - return Math.max(lethalDamageThreshold.getValue() - damagedPermanent.getDamage(), 0); + int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ? + // Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it. + Math.max(damagedPermanent.getPower().getValue(), 1) : damagedPermanent.getToughness().getValue(); + return Math.max(lethalDamageThreshold - damagedPermanent.getDamage(), 0); } }