diff --git a/Mage.Sets/src/mage/cards/s/SoulcoilViper.java b/Mage.Sets/src/mage/cards/s/SoulcoilViper.java new file mode 100644 index 00000000000..47f60889067 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SoulcoilViper.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SoulcoilViper extends CardImpl { + + public SoulcoilViper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.SNAKE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {B}, {T}, Sacrifice Soulcoil Viper: Return target creature card from your graveyard to the battlefield with a finality counter on it. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()), new ManaCostsImpl<>("{B}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability); + } + + private SoulcoilViper(final SoulcoilViper card) { + super(card); + } + + @Override + public SoulcoilViper copy() { + return new SoulcoilViper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UchbenbakTheGreatMistake.java b/Mage.Sets/src/mage/cards/u/UchbenbakTheGreatMistake.java new file mode 100644 index 00000000000..0a8af157171 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UchbenbakTheGreatMistake.java @@ -0,0 +1,52 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.condition.common.DescendCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldWithCounterEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UchbenbakTheGreatMistake extends CardImpl { + + public UchbenbakTheGreatMistake(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SKELETON); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(6); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Menace + this.addAbility(new MenaceAbility()); + + // Descend 8 -- {4}{U}{B}: Return Uchbenbak, the Great Mistake from your graveyard to the battlefield with a finality counter on it. Activate only if there are eight or more permanent cards in your graveyard and only as a sorcery. + this.addAbility(new ConditionalActivatedAbility( + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldWithCounterEffect(CounterType.FINALITY.createInstance(), false), + new ManaCostsImpl<>("{4}{U}{B}"), DescendCondition.EIGHT + ).setTiming(TimingRule.SORCERY).setAbilityWord(AbilityWord.DESCEND_8).addHint(DescendCondition.getHint())); + } + + private UchbenbakTheGreatMistake(final UchbenbakTheGreatMistake card) { + super(card); + } + + @Override + public UchbenbakTheGreatMistake copy() { + return new UchbenbakTheGreatMistake(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index 67793c934a3..9cc242a08cd 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -150,6 +150,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Skullcap Snail", 119, Rarity.COMMON, mage.cards.s.SkullcapSnail.class)); cards.add(new SetCardInfo("Song of Stupefaction", 77, Rarity.COMMON, mage.cards.s.SongOfStupefaction.class)); cards.add(new SetCardInfo("Sorcerous Spyglass", 261, Rarity.UNCOMMON, mage.cards.s.SorcerousSpyglass.class)); + cards.add(new SetCardInfo("Soulcoil Viper", 120, Rarity.UNCOMMON, mage.cards.s.SoulcoilViper.class)); cards.add(new SetCardInfo("Souls of the Lost", 121, Rarity.RARE, mage.cards.s.SoulsOfTheLost.class)); cards.add(new SetCardInfo("Sovereign Okinec Ahau", 240, Rarity.MYTHIC, mage.cards.s.SovereignOkinecAhau.class)); cards.add(new SetCardInfo("Sovereign's Macuahuitl", 155, Rarity.COMMON, mage.cards.s.SovereignsMacuahuitl.class)); @@ -184,6 +185,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Treasure Cove", 267, Rarity.RARE, mage.cards.t.TreasureCove.class)); cards.add(new SetCardInfo("Treasure Map", 267, Rarity.RARE, mage.cards.t.TreasureMap.class)); cards.add(new SetCardInfo("Trumpeting Carnosaur", 324, Rarity.RARE, mage.cards.t.TrumpetingCarnosaur.class)); + cards.add(new SetCardInfo("Uchbenbak, the Great Mistake", 242, Rarity.UNCOMMON, mage.cards.u.UchbenbakTheGreatMistake.class)); cards.add(new SetCardInfo("Vanguard of the Rose", 42, Rarity.UNCOMMON, mage.cards.v.VanguardOfTheRose.class)); cards.add(new SetCardInfo("Vito, Fanatic of Aclazotz", 243, Rarity.MYTHIC, mage.cards.v.VitoFanaticOfAclazotz.class)); cards.add(new SetCardInfo("Waterlogged Hulk", 83, Rarity.UNCOMMON, mage.cards.w.WaterloggedHulk.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/FinalityCounterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/FinalityCounterTest.java new file mode 100644 index 00000000000..cc5a50328b4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/FinalityCounterTest.java @@ -0,0 +1,113 @@ +package org.mage.test.cards.replacement; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class FinalityCounterTest extends CardTestPlayerBase { + + private static final String viper = "Soulcoil Viper"; + private static final String corpse = "Walking Corpse"; + + @Test + public void testCounterAdded() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.BATTLEFIELD, playerA, viper); + addCard(Zone.GRAVEYARD, playerA, corpse); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B},", corpse); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, viper, 0); + assertGraveyardCount(playerA, viper, 1); + assertGraveyardCount(playerA, corpse, 0); + assertCounterCount(playerA, corpse, CounterType.FINALITY, 1); + } + + private static final String murder = "Murder"; + + @Test + public void testCounterApplies() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3); + addCard(Zone.BATTLEFIELD, playerA, viper); + addCard(Zone.HAND, playerA, murder); + addCard(Zone.GRAVEYARD, playerA, corpse); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B},", corpse); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, corpse); + + setStopAt(1, PhaseStep.END_TURN); + + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, viper, 0); + assertGraveyardCount(playerA, viper, 1); + assertPermanentCount(playerA, corpse, 0); + assertGraveyardCount(playerA, corpse, 0); + assertExileCount(playerA, corpse, 1); + } + + private static final String hexmage = "Vampire Hexmage"; + + @Test + public void testCounterRemoved() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3); + addCard(Zone.BATTLEFIELD, playerA, viper); + addCard(Zone.BATTLEFIELD, playerA, hexmage); + addCard(Zone.HAND, playerA, murder); + addCard(Zone.GRAVEYARD, playerA, corpse); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B},", corpse); + + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Sacrifice", corpse); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, corpse); + + setStopAt(1, PhaseStep.END_TURN); + + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, viper, 0); + assertGraveyardCount(playerA, viper, 1); + assertPermanentCount(playerA, corpse, 0); + assertGraveyardCount(playerA, corpse, 1); + assertExileCount(playerA, corpse, 0); + } + + private static final String unsummon = "Unsummon"; + + @Test + public void testCounterDontApply() { + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 1 + 1); + addCard(Zone.BATTLEFIELD, playerA, viper); + addCard(Zone.HAND, playerA, unsummon); + addCard(Zone.GRAVEYARD, playerA, corpse); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B},", corpse); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, unsummon, corpse); + + setStopAt(1, PhaseStep.END_TURN); + + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, viper, 0); + assertGraveyardCount(playerA, viper, 1); + assertPermanentCount(playerA, corpse, 0); + assertHandCount(playerA, corpse, 1); + assertExileCount(playerA, corpse, 0); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/FinalityCounterEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/FinalityCounterEffect.java new file mode 100644 index 00000000000..bbc69af7959 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/keyword/FinalityCounterEffect.java @@ -0,0 +1,57 @@ +package mage.abilities.effects.keyword; + +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; + +/** + * @author TheElk801 + */ +public class FinalityCounterEffect extends ReplacementEffectImpl { + + public FinalityCounterEffect() { + super(Duration.Custom, Outcome.Tap); + this.staticText = "If a creature with a finality counter on it would die, exile it instead."; + } + + private FinalityCounterEffect(final FinalityCounterEffect effect) { + super(effect); + } + + @Override + public FinalityCounterEffect copy() { + return new FinalityCounterEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + ((ZoneChangeEvent) event).setToZone(Zone.EXILED); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!((ZoneChangeEvent) event).isDiesEvent()) { + return false; + } + Permanent permanent = game.getPermanent(event.getTargetId()); + return permanent != null && permanent.getCounters(game).getCount(CounterType.FINALITY) > 0; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } +} diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 45dbd9a315c..43c8e253469 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -73,6 +73,7 @@ public enum CounterType { FEATHER("feather"), FETCH("fetch"), FILIBUSTER("filibuster"), + FINALITY("finality"), FIRST_STRIKE("first strike"), FLAME("flame"), FLOOD("flood"), diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index b52e611a4ef..8153a0f52b1 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -14,6 +14,7 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.PreventionEffectData; import mage.abilities.effects.common.CopyEffect; import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.keyword.FinalityCounterEffect; import mage.abilities.effects.keyword.ShieldCounterEffect; import mage.abilities.effects.keyword.StunCounterEffect; import mage.abilities.keyword.*; @@ -1182,6 +1183,9 @@ public abstract class GameImpl implements Game { // Apply stun counter mechanic state.addAbility(new SimpleStaticAbility(Zone.ALL, new StunCounterEffect()), null); + // Apply finality counter mechanic + state.addAbility(new SimpleStaticAbility(Zone.ALL, new FinalityCounterEffect()), null); + // Handle companions Map playerCompanionMap = new HashMap<>(); for (Player player : state.getPlayers().values()) { @@ -1993,7 +1997,7 @@ public abstract class GameImpl implements Game { } if (copyFromPermanent.isPrototyped()) { Abilities abilities = copyFromPermanent.getAbilities(); - for (Ability ability : abilities){ + for (Ability ability : abilities) { if (ability instanceof PrototypeAbility) { ((PrototypeAbility) ability).prototypePermanent(newBluePrint, this); }