From cab436e9e5e897544ddf84b96863d31ae1a2698a Mon Sep 17 00:00:00 2001 From: htrajan Date: Thu, 16 Apr 2020 15:53:51 -0700 Subject: [PATCH] wip --- Mage.Sets/src/mage/cards/f/FlameSpill.java | 4 +- Mage.Sets/src/mage/cards/r/RamThrough.java | 4 +- .../src/mage/cards/s/SuperDuperDeathRay.java | 4 +- .../cards/z/ZilorthaStrengthIncarnate.java | 82 +++++++++++++++++++ .../src/mage/sets/IkoriaLairOfBehemoths.java | 1 + .../iko/ZilorthaStrengthIncarnateTest.java | 23 ++++++ Mage/src/main/java/mage/game/Game.java | 8 ++ Mage/src/main/java/mage/game/GameImpl.java | 38 ++++++++- .../java/mage/game/combat/CombatGroup.java | 47 +++++++---- 9 files changed, 186 insertions(+), 25 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/iko/ZilorthaStrengthIncarnateTest.java diff --git a/Mage.Sets/src/mage/cards/f/FlameSpill.java b/Mage.Sets/src/mage/cards/f/FlameSpill.java index fd49a4f9923..50707d477dc 100644 --- a/Mage.Sets/src/mage/cards/f/FlameSpill.java +++ b/Mage.Sets/src/mage/cards/f/FlameSpill.java @@ -15,6 +15,8 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; +import static mage.game.combat.CombatGroup.getLethalDamage; + /** * @author TheElk801 */ @@ -62,7 +64,7 @@ class FlameSpillEffect extends OneShotEffect { if (permanent == null || sourceObject == null) { return false; } - int lethal = Math.max(permanent.getToughness().getValue() - permanent.getDamage(), 0); + int lethal = getLethalDamage(permanent, game); if (sourceObject.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { lethal = Math.min(lethal, 1); } diff --git a/Mage.Sets/src/mage/cards/r/RamThrough.java b/Mage.Sets/src/mage/cards/r/RamThrough.java index 0c1fa86c73e..5d808ee476a 100644 --- a/Mage.Sets/src/mage/cards/r/RamThrough.java +++ b/Mage.Sets/src/mage/cards/r/RamThrough.java @@ -18,6 +18,8 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; +import static mage.game.combat.CombatGroup.getLethalDamage; + /** * @author TheElk801 */ @@ -85,7 +87,7 @@ class RamThroughEffect extends OneShotEffect { if (!myPermanent.getAbilities().containsKey(TrampleAbility.getInstance().getId())) { return anotherPermanent.damage(power, myPermanent.getId(), game, false, true) > 0; } - int lethal = Math.max(anotherPermanent.getToughness().getValue() - anotherPermanent.getDamage(), 0); + int lethal = getLethalDamage(anotherPermanent, game); if (myPermanent.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { lethal = Math.min(lethal, 1); } diff --git a/Mage.Sets/src/mage/cards/s/SuperDuperDeathRay.java b/Mage.Sets/src/mage/cards/s/SuperDuperDeathRay.java index 7badbea28b1..80994ec3752 100644 --- a/Mage.Sets/src/mage/cards/s/SuperDuperDeathRay.java +++ b/Mage.Sets/src/mage/cards/s/SuperDuperDeathRay.java @@ -17,6 +17,8 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; +import static mage.game.combat.CombatGroup.getLethalDamage; + /** * @author TheElk801 */ @@ -68,7 +70,7 @@ class SuperDuperDeathRayEffect extends OneShotEffect { if (permanent == null || sourceObject == null) { return false; } - int lethal = Math.max(permanent.getToughness().getValue() - permanent.getDamage(), 0); + int lethal = getLethalDamage(permanent, game); if (sourceObject.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { lethal = Math.min(lethal, 1); } diff --git a/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java b/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java new file mode 100644 index 00000000000..09ead1ed2e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java @@ -0,0 +1,82 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author htrajan + */ +public final class ZilorthaStrengthIncarnate extends CardImpl { + + public ZilorthaStrengthIncarnate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(7); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ZilorthaStrengthIncarnateEffect())); + } + + private ZilorthaStrengthIncarnate(ZilorthaStrengthIncarnate card) { + super(card); + } + + @Override + public Card copy() { + return new ZilorthaStrengthIncarnate(this); + } +} + +class ZilorthaStrengthIncarnateEffect extends ContinuousEffectImpl { + + ZilorthaStrengthIncarnateEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Lethal damage dealt to creatures you control is determined by their power rather than their toughness"; + } + + private ZilorthaStrengthIncarnateEffect(ZilorthaStrengthIncarnateEffect effect) { + super(effect); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + // Change the rule + FilterCreaturePermanent filter = StaticFilters.FILTER_PERMANENT_CREATURE.copy(); + filter.add(new ControllerIdPredicate(source.getControllerId())); + game.addUsePowerInsteadOfToughnessForDamageLethalityFilter(source.getSourceId(), filter); + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.RulesEffects; + } + + @Override + public ContinuousEffect copy() { + return new ZilorthaStrengthIncarnateEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java b/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java index 4a1e426d634..00fc24c96e9 100644 --- a/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java +++ b/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java @@ -330,6 +330,7 @@ public final class IkoriaLairOfBehemoths extends ExpansionSet { cards.add(new SetCardInfo("Zagoth Mamba", 106, Rarity.UNCOMMON, mage.cards.z.ZagothMamba.class)); cards.add(new SetCardInfo("Zagoth Triome", 259, Rarity.RARE, mage.cards.z.ZagothTriome.class)); cards.add(new SetCardInfo("Zenith Flare", 217, Rarity.UNCOMMON, mage.cards.z.ZenithFlare.class)); + cards.add(new SetCardInfo("Zilortha, Strength Incarnate", 275, Rarity.MYTHIC, mage.cards.z.ZilorthaStrengthIncarnate.class)); cards.add(new SetCardInfo("Zirda, the Dawnwaker", 233, Rarity.RARE, mage.cards.z.ZirdaTheDawnwaker.class)); cards.removeIf(setCardInfo -> mutateNames.contains(setCardInfo.getName())); // remove when mutate is implemented 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 new file mode 100644 index 00000000000..a63703491f6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/ZilorthaStrengthIncarnateTest.java @@ -0,0 +1,23 @@ +package org.mage.test.cards.single.iko; + +import mage.constants.Zone; +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); + + attack(2, playerA, "Savai Sabertooth"); + block(2, playerB, "Drannith Healer", "Savai Sabertooth"); + + execute(); + + assertGraveyardCount(playerA, "Savai Sabertooth", 1); + assertGraveyardCount(playerB, "Drannith Healer", 1); + } + +} diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 3a3cdd1b81a..d2d20ed707a 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -17,6 +17,9 @@ import mage.cards.decks.Deck; import mage.choices.Choice; import mage.constants.*; import mage.counters.Counters; +import mage.filter.Filter; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicate; import mage.game.combat.Combat; import mage.game.command.Commander; import mage.game.command.Emblem; @@ -485,4 +488,9 @@ public interface Game extends MageItem, Serializable { default Set getCommandersIds(Player player) { return getCommandersIds(player, CommanderCardType.ANY); } + + void addUsePowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter); + + List getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters(); + } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index cd5fb55985e..454aefb61b0 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1,6 +1,7 @@ package mage.game; import mage.MageException; +import mage.MageInt; import mage.MageObject; import mage.abilities.*; import mage.abilities.common.AttachableToRestrictedAbility; @@ -31,6 +32,7 @@ import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.combat.Combat; @@ -69,6 +71,9 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; public abstract class GameImpl implements Game, Serializable { @@ -144,6 +149,8 @@ public abstract class GameImpl implements Game, Serializable { // temporary store for income concede commands, don't copy private final LinkedList concedingPlayers = new LinkedList<>(); + private Map usePowerInsteadOfToughnessForDamageLethalityFilters = new HashMap<>(); + public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { this.id = UUID.randomUUID(); this.range = range; @@ -1888,6 +1895,7 @@ public abstract class GameImpl implements Game, Serializable { List legendary = new ArrayList<>(); List worldEnchantment = new ArrayList<>(); + List usePowerInsteadOfToughnessForDamageLethalityFilters = getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters(); for (Permanent perm : getBattlefield().getAllActivePermanents()) { if (perm.isCreature()) { //20091005 - 704.5f @@ -1897,10 +1905,19 @@ public abstract class GameImpl implements Game, Serializable { continue; } } //20091005 - 704.5g/704.5h - else if (perm.getToughness().getValue() <= perm.getDamage() || perm.isDeathtouched()) { - if (perm.destroy(null, this, false)) { - somethingHappened = true; - continue; + 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. + */ + boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream() + .anyMatch(filter -> filter.match(perm, this)); + MageInt lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ? perm.getPower() : perm.getToughness(); + if (lethalDamageThreshold.getValue() <= perm.getDamage() || perm.isDeathtouched()) { + if (perm.destroy(null, this, false)) { + somethingHappened = true; + continue; + } } } if (perm.getPairedCard() != null) { @@ -3300,4 +3317,17 @@ public abstract class GameImpl implements Game, Serializable { return player.getCommandersIds(); } + @Override + public void addUsePowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter) { + usePowerInsteadOfToughnessForDamageLethalityFilters.putIfAbsent(source, filter); + } + + @Override + public List getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters() { + return usePowerInsteadOfToughnessForDamageLethalityFilters.isEmpty() ? emptyList() : getBattlefield().getAllActivePermanents().stream() + .map(Card::getId) + .filter(usePowerInsteadOfToughnessForDamageLethalityFilters::containsKey) + .map(usePowerInsteadOfToughnessForDamageLethalityFilters::get) + .collect(Collectors.toList()); + } } diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index be2f139af66..378e4bcc249 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -5,6 +5,7 @@ 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; @@ -19,12 +20,15 @@ import mage.abilities.keyword.TrampleAbility; import mage.constants.AsThoughEffectType; import mage.constants.Outcome; import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.Copyable; +import static java.util.Collections.emptyList; + /** * * @author BetaSteward_at_googlemail.com @@ -271,12 +275,7 @@ public class CombatGroup implements Serializable, Copyable { if (blocked && canDamage(attacker, first)) { int damage = getDamageValueFromPermanent(attacker, game); if (hasTrample(attacker)) { - int lethalDamage; - if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { - lethalDamage = 1; - } else { - lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0); - } + int lethalDamage = getLethalDamage(blocker, attacker, game); if (lethalDamage >= damage) { blocker.markDamage(damage, attacker.getId(), game, true, true); } else { @@ -325,12 +324,7 @@ public class CombatGroup implements Serializable, Copyable { for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException Permanent blocker = game.getPermanent(blockerId); if (blocker != null) { - int lethalDamage; - if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { - lethalDamage = 1; - } else { - lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0); - } + int lethalDamage = getLethalDamage(blocker, attacker, game); if (lethalDamage >= damage) { if (!oldRuleDamage) { assigned.put(blockerId, damage); @@ -521,12 +515,7 @@ public class CombatGroup implements Serializable, Copyable { for (UUID attackerId : attackerOrder) { Permanent attacker = game.getPermanent(attackerId); if (attacker != null) { - int lethalDamage; - if (blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { - lethalDamage = 1; - } else { - lethalDamage = Math.max(attacker.getToughness().getValue() - attacker.getDamage(), 0); - } + int lethalDamage = getLethalDamage(attacker, blocker, game); if (lethalDamage >= damage) { if (!oldRuleDamage) { assigned.put(attackerId, damage); @@ -936,4 +925,26 @@ public class CombatGroup implements Serializable, Copyable { } return false; } + + private static int getLethalDamage(Permanent blocker, Permanent attacker, Game game) { + int lethalDamage; + if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { + lethalDamage = 1; + } else { + lethalDamage = getLethalDamage(blocker, game); + } + return lethalDamage; + } + + public static int getLethalDamage(Permanent damagedPermanent, Game game) { + List usePowerInsteadOfToughnessForDamageLethalityFilters = game.getActiveUsePowerInsteadOfToughnessForDamageLethalityFilters(); + /* + * 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. + */ + 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); + } }