diff --git a/Mage.Sets/src/mage/cards/u/UlamogTheDefiler.java b/Mage.Sets/src/mage/cards/u/UlamogTheDefiler.java new file mode 100644 index 00000000000..8f6962d37bb --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UlamogTheDefiler.java @@ -0,0 +1,199 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CastSourceTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.AnnihilatorAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.Set; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class UlamogTheDefiler extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.P1P1); + + public UlamogTheDefiler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{10}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDRAZI); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // When you cast this spell, target opponent exiles half their library, rounded up. + Ability ability = new CastSourceTriggeredAbility(new UlamogTheDefilerTargetEffect()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // Ward--Sacrifice two permanents. + this.addAbility(new WardAbility(new SacrificeTargetCost(2, StaticFilters.FILTER_PERMANENT))); + + // Ulamog, the Defiler enters the battlefield with a number of +1/+1 counters on it equal to the greatest mana value among cards in exile. + this.addAbility( + new EntersBattlefieldAbility( + new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), UlamogTheDefilerValue.instance, false + ), "with a number of +1/+1 counters on it equal to " + + "the greatest mana value among cards in exile" + ).addHint(UlamogTheDefilerValue.hint) + ); + + // Ulamog has annihilator X, where X is the number of +1/+1 counters on it. + this.addAbility(new SimpleStaticAbility(new UlamogTheDefilerContinuousAbility())); + } + + private UlamogTheDefiler(final UlamogTheDefiler card) { + super(card); + } + + @Override + public UlamogTheDefiler copy() { + return new UlamogTheDefiler(this); + } +} + +class UlamogTheDefilerTargetEffect extends OneShotEffect { + + UlamogTheDefilerTargetEffect() { + super(Outcome.Detriment); + staticText = "target opponent exiles half their library, rounded up"; + } + + private UlamogTheDefilerTargetEffect(final UlamogTheDefilerTargetEffect effect) { + super(effect); + } + + @Override + public UlamogTheDefilerTargetEffect copy() { + return new UlamogTheDefilerTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(source.getFirstTarget()); + if (opponent == null) { + return false; + } + int toExile = (opponent.getLibrary().size() + 1) / 2; + Set cards = opponent.getLibrary().getTopCards(game, toExile); + if (cards.isEmpty()) { + return false; + } + opponent.moveCardsToExile(cards, source, game, true, null, ""); + return true; + } + +} + +enum UlamogTheDefilerValue implements DynamicValue { + instance; + + static final Hint hint = new ValueHint("Greatest mana value among cards in exile", instance); + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getExile() + .getAllCardsByRange(game, sourceAbility.getControllerId()) + .stream() + .mapToInt(Card::getManaValue) + .max() + .orElse(0); + } + + @Override + public DynamicValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } +} + +class UlamogTheDefilerContinuousAbility extends ContinuousEffectImpl { + + // Keep the last annihilator ability added. + private Ability ability; + // Keep the last annihilator amount added. + private int lastAmount; + + UlamogTheDefilerContinuousAbility() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "{this} has annihilator X, where X is the number of +1/+1 counters on it"; + this.addDependencyType(DependencyType.AddingAbility); + this.ability = new AnnihilatorAbility(0); + this.lastAmount = 0; + } + + private UlamogTheDefilerContinuousAbility(final UlamogTheDefilerContinuousAbility effect) { + super(effect); + this.ability = effect.ability.copy(); + // From GainAbilitySourceEffect: + ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents + this.lastAmount = effect.lastAmount; + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + if (getAffectedObjectsSet()) { + Permanent permanent = game.getPermanentEntering(source.getSourceId()); + if (permanent != null) { + affectedObjectList.add(new MageObjectReference(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1, game)); + } + } + } + + @Override + public UlamogTheDefilerContinuousAbility copy() { + return new UlamogTheDefilerContinuousAbility(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent; + if (getAffectedObjectsSet()) { + permanent = affectedObjectList.get(0).getPermanent(game); + } else { + permanent = game.getPermanent(source.getSourceId()); + } + if (permanent != null) { + int amount = permanent.getCounters(game).getCount(CounterType.P1P1); + if (amount != lastAmount) { + // Only instantiate a new ability if the number of P1P1 counters changed. + ability = new AnnihilatorAbility(amount); + lastAmount = amount; + } + permanent.addAbility(ability, source.getSourceId(), game); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index 4d08b3828de..4c84c1e2f3e 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -146,6 +146,7 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Trickster's Elk", 175, Rarity.UNCOMMON, mage.cards.t.TrickstersElk.class)); cards.add(new SetCardInfo("Tune the Narrative", 75, Rarity.COMMON, mage.cards.t.TuneTheNarrative.class)); cards.add(new SetCardInfo("Ugin's Labyrinth", 233, Rarity.MYTHIC, mage.cards.u.UginsLabyrinth.class)); + cards.add(new SetCardInfo("Ulamog, the Defiler", 15, Rarity.MYTHIC, mage.cards.u.UlamogTheDefiler.class)); cards.add(new SetCardInfo("Urza's Cave", 234, Rarity.UNCOMMON, mage.cards.u.UrzasCave.class)); cards.add(new SetCardInfo("Victimize", 278, Rarity.UNCOMMON, mage.cards.v.Victimize.class)); cards.add(new SetCardInfo("Warren Soultrader", 110, Rarity.RARE, mage.cards.w.WarrenSoultrader.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/UlamogTheDefilerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/UlamogTheDefilerTest.java new file mode 100644 index 00000000000..0a4bdc80c6b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/UlamogTheDefilerTest.java @@ -0,0 +1,138 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class UlamogTheDefilerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.u.UlamogTheDefiler Ulamog, the Defiler} {10} + * Legendary Creature — Eldrazi + * When you cast this spell, target opponent exiles half their library, rounded up. + * Ward—Sacrifice two permanents. + * Ulamog, the Defiler enters the battlefield with a number of +1/+1 counters on it equal to the greatest mana value among cards in exile. + * Ulamog has annihilator X, where X is the number of +1/+1 counters on it. + * 7/7 + */ + private static final String ulamog = "Ulamog, the Defiler"; + + @Test + public void test_OnlyLandsExiled() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerB); + + addCard(Zone.HAND, playerA, ulamog); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 10); + addCard(Zone.LIBRARY, playerB, "Taiga", 11); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ulamog); + addTarget(playerA, playerB); + + attack(3, playerA, ulamog, playerB); + // Annihilator 0 triggers + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerB, 6); + assertLife(playerB, 20 - 7); + assertGraveyardCount(playerB, "Mountain", 0); + } + + @Test + public void test_Annihilator2_FromFreshlyExiled() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerB); + + addCard(Zone.HAND, playerA, ulamog); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 10); + addCard(Zone.LIBRARY, playerB, "Taiga", 11); + addCard(Zone.LIBRARY, playerB, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerB, "Elite Vanguard"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ulamog); + addTarget(playerA, playerB); + + attack(3, playerA, ulamog, playerB); + // Annihilator 2 triggers + setChoice(playerB, "Mountain", 2); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerB, 7); + assertExileCount(playerB, "Elite Vanguard", 1); + assertExileCount(playerB, "Grizzly Bears", 1); + assertLife(playerB, 20 - 9); + assertGraveyardCount(playerB, "Mountain", 2); + } + + @Test + public void test_Annihilator2_FromOldExiled() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerB); + + addCard(Zone.HAND, playerA, ulamog); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 10); + addCard(Zone.LIBRARY, playerB, "Taiga", 11); + addCard(Zone.EXILED, playerA, "Grizzly Bears"); + addCard(Zone.EXILED, playerA, "Elite Vanguard"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ulamog); + addTarget(playerA, playerB); + + attack(3, playerA, ulamog, playerB); + // Annihilator 2 triggers + setChoice(playerB, "Mountain", 2); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerB, 6); + assertLife(playerB, 20 - 9); + assertGraveyardCount(playerB, "Mountain", 2); + } + + @Test + public void test_Annihilator4_Change_FromCounterLater() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerB); + + addCard(Zone.HAND, playerA, ulamog); + addCard(Zone.BATTLEFIELD, playerA, "Luminarch Aspirant"); // At the beginning of combat on your turn, put a +1/+1 counter on target creature you control. + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 10); + addCard(Zone.LIBRARY, playerB, "Taiga", 11); + addCard(Zone.LIBRARY, playerB, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerB, "Elite Vanguard"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ulamog); + addTarget(playerA, playerB); + addTarget(playerA, ulamog); // Aspirant trigger + + attack(3, playerA, ulamog, playerB); + addTarget(playerA, ulamog); // Aspirant trigger + // Annihilator 4 triggers + setChoice(playerB, "Mountain", 4); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerB, 7); + assertLife(playerB, 20 - 11); + assertGraveyardCount(playerB, "Mountain", 4); + } + +}