implement [MH3] Unstable Amulet

This commit is contained in:
Susucre 2024-06-04 15:38:49 +02:00
parent 83ad5d26dc
commit b35924dc4c
4 changed files with 252 additions and 0 deletions

View file

@ -0,0 +1,164 @@
package mage.cards.u;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.costs.common.PayEnergyCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterSpell;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.card.CastFromZonePredicate;
import mage.game.ExileZone;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.Set;
import java.util.UUID;
/**
* @author Susucr
*/
public final class UnstableAmulet extends CardImpl {
private static final FilterSpell filter = new FilterSpell("a spell from anywhere other than your hand");
static {
filter.add(Predicates.not(new CastFromZonePredicate(Zone.HAND)));
}
public UnstableAmulet(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}");
// When Unstable Amulet enters the battlefield, you get {E}{E}.
this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(2)));
// Whenever you cast a spell from anywhere other than your hand, Unstable Amulet deals 1 damage to each opponent.
this.addAbility(new SpellCastControllerTriggeredAbility(
new DamagePlayersEffect(1, TargetController.OPPONENT),
filter, false
));
// {T}, Pay {E}{E}: Exile the top card of your library. You may play it until you exile another card with Unstable Amulet.
Ability ability = new SimpleActivatedAbility(
new UnstableAmuletEffect(),
new TapSourceCost()
);
ability.addCost(new PayEnergyCost(2));
this.addAbility(ability);
}
private UnstableAmulet(final UnstableAmulet card) {
super(card);
}
@Override
public UnstableAmulet copy() {
return new UnstableAmulet(this);
}
}
class UnstableAmuletEffect extends OneShotEffect {
UnstableAmuletEffect() {
super(Outcome.DrawCard);
staticText = "Exile the top card of your library. You may play it until you exile another card with {this}.";
}
private UnstableAmuletEffect(final UnstableAmuletEffect effect) {
super(effect);
}
@Override
public UnstableAmuletEffect copy() {
return new UnstableAmuletEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null || !controller.getLibrary().hasCards()) {
return false;
}
Card card = controller.getLibrary().getFromTop(game);
if (card == null) {
return false;
}
UUID exileId = CardUtil.getExileZoneId(game, source);
String exileName = CardUtil.getSourceIdName(game, source);
controller.moveCardsToExile(card, source, game, true, exileId, exileName);
game.getState().processAction(game);
if (!Zone.EXILED.equals(game.getState().getZone(card.getId()))) {
return true;
}
// Allow the card to be played until it leaves that exile zone.
ContinuousEffect effect = new UnstableAmuletPlayEffect(exileId);
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
// Clean the exile Zone from other cards, that can no longer be played.
ExileZone exileZone = game.getExile().getExileZone(exileId);
if (exileZone == null) {
return true;
}
Set<Card> inExileZone = exileZone.getCards(game);
for (Card cardInExile : inExileZone) {
if (cardInExile.getId().equals(card.getId())) {
continue;
}
game.getExile().moveToMainExileZone(cardInExile, game);
}
return true;
}
}
class UnstableAmuletPlayEffect extends AsThoughEffectImpl {
// The exile zone the card should be in for the effect to work.
private final UUID exileId;
UnstableAmuletPlayEffect(UUID exileId) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
this.exileId = exileId;
}
private UnstableAmuletPlayEffect(final UnstableAmuletPlayEffect effect) {
super(effect);
this.exileId = effect.exileId;
}
@Override
public UnstableAmuletPlayEffect copy() {
return new UnstableAmuletPlayEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
UUID cardId = getTargetPointer().getFirst(game, source);
if (cardId == null) {
this.discard();
return false;
}
ExileZone exileZone = game.getExile().getExileZone(exileId);
if (exileZone == null || !exileZone.contains(cardId)) {
this.discard();
return false;
}
return cardId.equals(objectId) && affectedControllerId.equals(source.getControllerId());
}
}

View file

@ -285,6 +285,7 @@ public final class ModernHorizons3 extends ExpansionSet {
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("Unfathomable Truths", 77, Rarity.COMMON, mage.cards.u.UnfathomableTruths.class));
cards.add(new SetCardInfo("Unstable Amulet", 142, Rarity.UNCOMMON, mage.cards.u.UnstableAmulet.class));
cards.add(new SetCardInfo("Urza's Cave", 234, Rarity.UNCOMMON, mage.cards.u.UrzasCave.class));
cards.add(new SetCardInfo("Urza's Incubator", 297, Rarity.RARE, mage.cards.u.UrzasIncubator.class));
cards.add(new SetCardInfo("Utter Insignificance", 78, Rarity.COMMON, mage.cards.u.UtterInsignificance.class));

View file

@ -0,0 +1,83 @@
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 UnstableAmuletTest extends CardTestPlayerBase {
/**
* {@link mage.cards.u.UnstableAmulet Unstable Amulet} {1}{R}
* Artifact
* When Unstable Amulet enters the battlefield, you get {E}{E} (two energy counters).
* Whenever you cast a spell from anywhere other than your hand, Unstable Amulet deals 1 damage to each opponent.
* {T}, Pay {E}{E}: Exile the top card of your library. You may play it until you exile another card with Unstable Amulet.
*/
private static final String amulet = "Unstable Amulet";
@Test
public void test_Simple() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.BATTLEFIELD, playerA, "Taiga", 2);
addCard(Zone.HAND, playerA, amulet);
addCard(Zone.LIBRARY, playerA, "Grizzly Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, amulet, true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay {E}{E}");
checkPlayableAbility("No mana to cast Bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Grizzly Bears", false);
checkExileCount("Bears in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1);
checkPlayableAbility("Can play Bears", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true);
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears");
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertLife(playerB, 20 - 1); // 1 damage from Amulet trigger
assertPermanentCount(playerA, "Grizzly Bears", 1);
}
@Test
public void test_ExileAnother_EndPreviousPlay() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.BATTLEFIELD, playerA, "Taiga", 2);
addCard(Zone.BATTLEFIELD, playerA, "Island");
addCard(Zone.HAND, playerA, amulet);
addCard(Zone.HAND, playerA, "Tune the Narrative"); // Draw a card. You get {E}{E}
addCard(Zone.LIBRARY, playerA, "Balduvian Bears");
addCard(Zone.LIBRARY, playerA, "Plains", 2); // draw for turn 3 + Tune the Narrative
addCard(Zone.LIBRARY, playerA, "Grizzly Bears");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, amulet, true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay {E}{E}");
checkPlayableAbility("1: No mana to cast Grizzly Bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Grizzly Bears", false);
checkExileCount("1: Bears in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1);
checkPlayableAbility("2: Can play Grizzly Bears", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true);
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Tune the Narrative", true);
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay {E}{E}");
checkExileCount("3: Grizzly Bears in exile", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1);
checkExileCount("3: Balduvian Bears in exile", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 1);
checkPlayableAbility("3: Can no longer play Grizzly Bears", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Grizzly Bears", false);
checkPlayableAbility("3: Can play Balduvian Bears", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Balduvian Bears", true);
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears");
setStopAt(3, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 1); // 1 damage from Amulet trigger
assertExileCount(playerA, "Grizzly Bears", 1);
assertPermanentCount(playerA, "Balduvian Bears", 1);
}
}

View file

@ -117,6 +117,10 @@ public class Exile implements Serializable, Copyable<Exile> {
exileZone.add(card);
}
public void moveToMainExileZone(Card card, Game game) {
moveToAnotherZone(card, game, getExileZone(PERMANENT));
}
@Override
public Exile copy() {
return new Exile(this);