implement [MH3] Ulamog, the Defiler

This commit is contained in:
Susucre 2024-05-25 16:58:44 +02:00
parent 4cb93b6d36
commit d0971145f2
3 changed files with 338 additions and 0 deletions

View file

@ -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<Card> 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;
}
}

View file

@ -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("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("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("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("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("Victimize", 278, Rarity.UNCOMMON, mage.cards.v.Victimize.class));
cards.add(new SetCardInfo("Warren Soultrader", 110, Rarity.RARE, mage.cards.w.WarrenSoultrader.class)); cards.add(new SetCardInfo("Warren Soultrader", 110, Rarity.RARE, mage.cards.w.WarrenSoultrader.class));

View file

@ -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.
* WardSacrifice 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);
}
}