[SPM] implement Shadow of the Goblin

This commit is contained in:
jmlundeen 2025-09-05 10:57:26 -05:00
parent f663f5e002
commit b63660e022
3 changed files with 203 additions and 0 deletions

View file

@ -0,0 +1,78 @@
package mage.cards.s;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.triggers.BeginningOfFirstMainTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
*
* @author Jmlundeen
*/
public final class ShadowOfTheGoblin extends CardImpl {
public ShadowOfTheGoblin(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}");
// Unreliable Visions -- At the beginning of your first main phase, discard a card. If you do, draw a card.
this.addAbility(new BeginningOfFirstMainTriggeredAbility(
new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()), false)
.withFlavorWord("Unreliable Visions")
);
// Undying Vengeance -- Whenever you play a land or cast a spell from anywhere other than your hand, this enchantment deals 1 damage to each opponent.
this.addAbility(new ShadowOfTheGoblinTriggeredAbility(new DamagePlayersEffect(1, TargetController.OPPONENT))
.withFlavorWord("Undying Vengeance"));
}
private ShadowOfTheGoblin(final ShadowOfTheGoblin card) {
super(card);
}
@Override
public ShadowOfTheGoblin copy() {
return new ShadowOfTheGoblin(this);
}
}
class ShadowOfTheGoblinTriggeredAbility extends TriggeredAbilityImpl {
public ShadowOfTheGoblinTriggeredAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect, false);
setTriggerPhrase("Whenever you play a land or cast a spell from anywhere other than your hand, ");
}
protected ShadowOfTheGoblinTriggeredAbility(final ShadowOfTheGoblinTriggeredAbility triggeredAbility) {
super(triggeredAbility);
}
@Override
public ShadowOfTheGoblinTriggeredAbility copy() {
return new ShadowOfTheGoblinTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.PLAY_LAND ||
event.getType() == GameEvent.EventType.SPELL_CAST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(event.getPlayerId()) && event.getZone() != Zone.HAND;
}
}

View file

@ -189,6 +189,8 @@ public final class MarvelsSpiderMan extends ExpansionSet {
cards.add(new SetCardInfo("Scout the City", 113, Rarity.COMMON, mage.cards.s.ScoutTheCity.class)); cards.add(new SetCardInfo("Scout the City", 113, Rarity.COMMON, mage.cards.s.ScoutTheCity.class));
cards.add(new SetCardInfo("Secret Identity", 43, Rarity.UNCOMMON, mage.cards.s.SecretIdentity.class)); cards.add(new SetCardInfo("Secret Identity", 43, Rarity.UNCOMMON, mage.cards.s.SecretIdentity.class));
cards.add(new SetCardInfo("Selfless Police Captain", 12, Rarity.COMMON, mage.cards.s.SelflessPoliceCaptain.class)); cards.add(new SetCardInfo("Selfless Police Captain", 12, Rarity.COMMON, mage.cards.s.SelflessPoliceCaptain.class));
cards.add(new SetCardInfo("Shadow of the Goblin", 262, Rarity.RARE, mage.cards.s.ShadowOfTheGoblin.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Shadow of the Goblin", 87, Rarity.RARE, mage.cards.s.ShadowOfTheGoblin.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Shock", 88, Rarity.COMMON, mage.cards.s.Shock.class)); cards.add(new SetCardInfo("Shock", 88, Rarity.COMMON, mage.cards.s.Shock.class));
cards.add(new SetCardInfo("Shocker, Unshakable", 89, Rarity.UNCOMMON, mage.cards.s.ShockerUnshakable.class)); cards.add(new SetCardInfo("Shocker, Unshakable", 89, Rarity.UNCOMMON, mage.cards.s.ShockerUnshakable.class));
cards.add(new SetCardInfo("Silver Sable, Mercenary Leader", 13, Rarity.UNCOMMON, mage.cards.s.SilverSableMercenaryLeader.class)); cards.add(new SetCardInfo("Silver Sable, Mercenary Leader", 13, Rarity.UNCOMMON, mage.cards.s.SilverSableMercenaryLeader.class));

View file

@ -0,0 +1,123 @@
package org.mage.test.cards.single.spm;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author Jmlundeen
*/
public class ShadowOfTheGoblinTest extends CardTestPlayerBase {
/*
Shadow of the Goblin
{1}{R}
Enchantment
Unreliable Visions -- At the beginning of your first main phase, discard a card. If you do, draw a card.
Undying Vengeance -- Whenever you play a land or cast a spell from anywhere other than your hand, this enchantment deals 1 damage to each opponent.
*/
private static final String shadowOfTheGoblin = "Shadow of the Goblin";
/*
Party Thrasher
{1}{R}
Creature - Lizard Wizard
Noncreature spells you cast from exile have convoke.
At the beginning of your precombat main phase, you may discard a card. If you do, exile the top two cards of your library, then choose one of them. You may play that card this turn.
1/4
*/
private static final String partyThrasher = "Party Thrasher";
/*
Misthollow Griffin
{2}{U}{U}
Creature - Griffin
Flying
You may cast Misthollow Griffin from exile.
3/3
*/
private static final String misthollowGriffin = "Misthollow Griffin";
/*
Glarb, Calamity's Augur
{B}{G}{U}
Legendary Creature - Frog Wizard Noble
Deathtouch
You may look at the top card of your library any time.
You may play lands and cast spells with mana value 4 or greater from the top of your library.
{T}: Surveil 2.
2/4
*/
private static final String glarbCalamitysAugur = "Glarb, Calamity's Augur";
@Test
public void testShadowOfTheGoblinFromHand() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, shadowOfTheGoblin);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.HAND, playerA, "Mountain");
addCard(Zone.HAND, playerA, partyThrasher);
setChoice(playerA, false); // shadow trigger
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, partyThrasher);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertLife(playerB, 20); // no trigger
}
@Test
public void testShadowOfTheGoblinFromExile() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, shadowOfTheGoblin);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.BATTLEFIELD, playerA, partyThrasher);
addCard(Zone.HAND, playerA, "Mountain");
addCard(Zone.EXILED, playerA, misthollowGriffin);
setChoice(playerA, "<i>Unreliable Visions</i>");
setChoice(playerA, true); // discard
setChoice(playerA, "Mountain", 2);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, misthollowGriffin);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertLife(playerB, 20 - 1 - 1); // land + spell from exile
}
@Test
public void testShadowOfTheGoblinFromLibrary() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.BATTLEFIELD, playerA, shadowOfTheGoblin);
addCard(Zone.BATTLEFIELD, playerA, glarbCalamitysAugur);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.LIBRARY, playerA, misthollowGriffin);
addCard(Zone.LIBRARY, playerA, "Island");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, misthollowGriffin);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertLife(playerB, 20 - 1 - 1); // land + spell from library
}
}