mirror of
https://github.com/magefree/mage.git
synced 2025-12-21 02:52:02 -08:00
implement [MH3] Ulamog, the Defiler
This commit is contained in:
parent
4cb93b6d36
commit
d0971145f2
3 changed files with 338 additions and 0 deletions
199
Mage.Sets/src/mage/cards/u/UlamogTheDefiler.java
Normal file
199
Mage.Sets/src/mage/cards/u/UlamogTheDefiler.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue