diff --git a/Mage.Sets/src/mage/cards/d/Deification.java b/Mage.Sets/src/mage/cards/d/Deification.java new file mode 100644 index 00000000000..e619a2a84af --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Deification.java @@ -0,0 +1,119 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.ChoosePlaneswalkerTypeEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterPlaneswalkerPermanent; +import mage.filter.predicate.mageobject.ChosenPlaneswalkerTypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.RemoveCountersEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author jimga150 + */ +public final class Deification extends CardImpl { + + private static final FilterPlaneswalkerPermanent filter = new FilterPlaneswalkerPermanent("planeswalkers of the chosen type"); + + static { + filter.add(ChosenPlaneswalkerTypePredicate.TRUE); + } + + public Deification(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + + // As Deification enters the battlefield, choose a planeswalker type. + this.addAbility(new AsEntersBattlefieldAbility(new ChoosePlaneswalkerTypeEffect(Outcome.AddAbility))); + + // Planeswalkers you control of the chosen type have hexproof. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAllEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, filter))); + + // As long as you control a creature, if damage dealt to a planeswalker you control of the chosen type would result in all loyalty counters on it being removed, instead all but one of those counters are removed. + addAbility(new SimpleStaticAbility(new DeificationReplacementEffect())); + } + + private Deification(final Deification card) { + super(card); + } + + @Override + public Deification copy() { + return new Deification(this); + } +} + +// Based on SerraTheBenevolentEmblemEffect +class DeificationReplacementEffect extends ReplacementEffectImpl { + + DeificationReplacementEffect() { + super(Duration.Custom, Outcome.Benefit); + staticText = "As long as you control a creature, if damage dealt to a planeswalker you control of the chosen " + + "type would result in all loyalty counters on it being removed, instead all but one of those " + + "counters are removed."; + } + + private DeificationReplacementEffect(final DeificationReplacementEffect effect) { + super(effect); + } + + @Override + public DeificationReplacementEffect copy() { + return new DeificationReplacementEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.REMOVE_COUNTERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + + RemoveCountersEvent rEvent = (RemoveCountersEvent) event; + + if (!source.isControlledBy(rEvent.getPlayerId())) { + return false; + } + Permanent planeswalker = game.getPermanentOrLKIBattlefield(rEvent.getTargetId()); + if (planeswalker == null) { + return false; + } + if (!rEvent.counterRemovedDueToDamage()){ + // not due to damage, prevention does not occur + return false; + } + + int loyaltyCounters = planeswalker.getCounters(game).getCount(CounterType.LOYALTY); + return planeswalker.hasSubtype(ChoosePlaneswalkerTypeEffect.getChosenPlaneswalkerType(source.getSourceId(), game), game) + && (loyaltyCounters - event.getAmount()) < 1 + && game.getBattlefield().count( + StaticFilters.FILTER_CONTROLLED_CREATURE, + event.getPlayerId(), source, game) > 0; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent planeswalker = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (planeswalker == null) { + return false; + } + int loyaltyCounters = planeswalker.getCounters(game).getCount(CounterType.LOYALTY); + event.setAmount(loyaltyCounters - 1); + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java b/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java index 3a5121910c7..0d30aa719ef 100644 --- a/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java +++ b/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java @@ -64,6 +64,7 @@ public final class MarchOfTheMachineTheAftermath extends ExpansionSet { cards.add(new SetCardInfo("Death-Rattle Oni", 13, Rarity.UNCOMMON, mage.cards.d.DeathRattleOni.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Death-Rattle Oni", 197, Rarity.UNCOMMON, mage.cards.d.DeathRattleOni.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Death-Rattle Oni", 63, Rarity.UNCOMMON, mage.cards.d.DeathRattleOni.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deification", 2, Rarity.RARE, mage.cards.d.Deification.class)); cards.add(new SetCardInfo("Drannith Ruins", 100, Rarity.RARE, mage.cards.d.DrannithRuins.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Drannith Ruins", 150, Rarity.RARE, mage.cards.d.DrannithRuins.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Drannith Ruins", 185, Rarity.RARE, mage.cards.d.DrannithRuins.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mat/DeificationTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mat/DeificationTests.java new file mode 100644 index 00000000000..a0d268fe0f9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mat/DeificationTests.java @@ -0,0 +1,98 @@ +package org.mage.test.cards.single.mat; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author jimga150 + */ +public class DeificationTests extends CardTestPlayerBase { + + @Test + public void testDamagePrevention() { + + addCard(Zone.HAND, playerA, "Deification"); + addCard(Zone.HAND, playerA, "Lightning Bolt", 4); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerA, "Chandra, Dressed to Kill"); // 3 / Planeswalker type: Chandra + addCard(Zone.BATTLEFIELD, playerA, "Tibalt, the Fiend-Blooded"); // 2 / Planeswalker type: Tibalt + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); // need a creature for Deification to work + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deification", true); + setChoice(playerA, "Chandra"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", true); + addTarget(playerA, "Tibalt, the Fiend-Blooded"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", true); + addTarget(playerA, "Chandra, Dressed to Kill"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Tibalt, the Fiend-Blooded", 1); + assertGraveyardCount(playerA, "Chandra, Dressed to Kill", 0); + assertCounterCount(playerA, "Chandra, Dressed to Kill", CounterType.LOYALTY, 1); + + } + + @Test + public void testDamagePreventionNoCreatures() { + + addCard(Zone.HAND, playerA, "Deification"); + addCard(Zone.HAND, playerA, "Lightning Bolt", 4); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerA, "Chandra, Dressed to Kill"); // 3 / Planeswalker type: Chandra + addCard(Zone.BATTLEFIELD, playerA, "Tibalt, the Fiend-Blooded"); // 2 / Planeswalker type: Tibalt + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deification", true); + setChoice(playerA, "Chandra"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", true); + addTarget(playerA, "Tibalt, the Fiend-Blooded"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", true); + addTarget(playerA, "Chandra, Dressed to Kill"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Tibalt, the Fiend-Blooded", 1); + assertGraveyardCount(playerA, "Chandra, Dressed to Kill", 1); + + } + + @Test + public void testDamagePreventionNonLethal() { + + addCard(Zone.HAND, playerA, "Deification"); + addCard(Zone.HAND, playerA, "Lightning Bolt", 4); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerA, "Chandra, Awakened Inferno"); // 6 / Planeswalker type: Chandra + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); // need a creature for Deification to work + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deification", true); + setChoice(playerA, "Chandra"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", true); + addTarget(playerA, "Chandra, Awakened Inferno"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Chandra, Awakened Inferno", 0); + // not lethal damage, so should have taken all 3 damage + assertCounterCount(playerA, "Chandra, Awakened Inferno", CounterType.LOYALTY, 3); + + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChoosePlaneswalkerTypeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChoosePlaneswalkerTypeEffect.java new file mode 100644 index 00000000000..f1272d9bda6 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ChoosePlaneswalkerTypeEffect.java @@ -0,0 +1,78 @@ +package mage.abilities.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.choices.Choice; +import mage.choices.ChoicePlaneswalkerType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author jimga150 + */ + +// Based on ChooseCreatureTypeEffect +public class ChoosePlaneswalkerTypeEffect extends OneShotEffect { + + public ChoosePlaneswalkerTypeEffect(Outcome outcome) { + super(outcome); + staticText = "choose a planeswalker type"; + } + + protected ChoosePlaneswalkerTypeEffect(final ChoosePlaneswalkerTypeEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject mageObject = game.getPermanentEntering(source.getSourceId()); + if (mageObject == null) { + mageObject = game.getObject(source); + } + if (controller != null && mageObject != null) { + Choice typeChoice = new ChoicePlaneswalkerType(mageObject); + if (controller.choose(outcome, typeChoice, game)) { + game.informPlayers(mageObject.getName() + ": " + controller.getLogName() + " has chosen " + typeChoice.getChoice()); + game.getState().setValue(source.getSourceId() + "_type", SubType.byDescription(typeChoice.getChoice())); + if (mageObject instanceof Permanent) { + ((Permanent) mageObject).addInfo("chosen type", CardUtil.addToolTipMarkTags("Chosen type: " + typeChoice.getChoice()), game); + } + return true; + } + } + return false; + } + + @Override + public ChoosePlaneswalkerTypeEffect copy() { + return new ChoosePlaneswalkerTypeEffect(this); + } + + public static SubType getChosenPlaneswalkerType(UUID objectId, Game game) { + return getChosenPlaneswalkerType(objectId, game, "_type"); + } + + /** + * @param objectId sourceId the effect was executed under + * @param game + * @param typePostfix special postfix if you want to store multiple choices + * from different effects + * @return + */ + public static SubType getChosenPlaneswalkerType(UUID objectId, Game game, String typePostfix) { + SubType planeswalkerType = null; + Object savedPlaneswalkerType = game.getState().getValue(objectId + typePostfix); + if (savedPlaneswalkerType != null) { + planeswalkerType = SubType.byDescription(savedPlaneswalkerType.toString()); + } + return planeswalkerType; + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenPlaneswalkerTypePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenPlaneswalkerTypePredicate.java new file mode 100644 index 00000000000..00318481fe2 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenPlaneswalkerTypePredicate.java @@ -0,0 +1,34 @@ +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.abilities.effects.common.ChooseCreatureTypeEffect; +import mage.abilities.effects.common.ChoosePlaneswalkerTypeEffect; +import mage.constants.SubType; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; + +/** + * + * @author jimga150 + */ +public enum ChosenPlaneswalkerTypePredicate implements ObjectSourcePlayerPredicate { + TRUE(true), FALSE(false); + + private final boolean value; + + ChosenPlaneswalkerTypePredicate(boolean value) { + this.value = value; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + SubType subType = ChoosePlaneswalkerTypeEffect.getChosenPlaneswalkerType(input.getSourceId(), game); + return input.getObject().hasSubtype(subType, game) == value; + } + + @Override + public String toString() { + return "Chosen Planeswalker type"; + } +} diff --git a/Mage/src/main/java/mage/game/events/CounterRemovedEvent.java b/Mage/src/main/java/mage/game/events/CounterRemovedEvent.java index 582bb461402..d14c8a3c5ca 100644 --- a/Mage/src/main/java/mage/game/events/CounterRemovedEvent.java +++ b/Mage/src/main/java/mage/game/events/CounterRemovedEvent.java @@ -22,7 +22,7 @@ public class CounterRemovedEvent extends GameEvent { this.isDamage = isDamage; } - boolean counterRemovedDueToDamage(){ + public boolean counterRemovedDueToDamage(){ return this.isDamage; } diff --git a/Mage/src/main/java/mage/game/events/RemoveCounterEvent.java b/Mage/src/main/java/mage/game/events/RemoveCounterEvent.java index b4192cb57ac..b402e7a7ab3 100644 --- a/Mage/src/main/java/mage/game/events/RemoveCounterEvent.java +++ b/Mage/src/main/java/mage/game/events/RemoveCounterEvent.java @@ -22,7 +22,7 @@ public class RemoveCounterEvent extends GameEvent { this.isDamage = isDamage; } - boolean counterRemovedDueToDamage(){ + public boolean counterRemovedDueToDamage(){ return this.isDamage; } diff --git a/Mage/src/main/java/mage/game/events/RemoveCountersEvent.java b/Mage/src/main/java/mage/game/events/RemoveCountersEvent.java index 7a108618eee..15f16060d2e 100644 --- a/Mage/src/main/java/mage/game/events/RemoveCountersEvent.java +++ b/Mage/src/main/java/mage/game/events/RemoveCountersEvent.java @@ -26,7 +26,7 @@ public class RemoveCountersEvent extends GameEvent { this.isDamage = isDamage; } - boolean counterRemovedDueToDamage(){ + public boolean counterRemovedDueToDamage(){ return this.isDamage; }