mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 11:32:00 -08:00
Add finality counters (#11379)
* [LCI] Implement Soulcoil Viper * add finality counter test * fix bug, add extra test * [LCI] Implement Uchbenbak, the Great Mistake
This commit is contained in:
parent
6a33c68bb2
commit
595955a3cc
7 changed files with 280 additions and 1 deletions
50
Mage.Sets/src/mage/cards/s/SoulcoilViper.java
Normal file
50
Mage.Sets/src/mage/cards/s/SoulcoilViper.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
52
Mage.Sets/src/mage/cards/u/UchbenbakTheGreatMistake.java
Normal file
52
Mage.Sets/src/mage/cards/u/UchbenbakTheGreatMistake.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,7 @@ public enum CounterType {
|
|||
FEATHER("feather"),
|
||||
FETCH("fetch"),
|
||||
FILIBUSTER("filibuster"),
|
||||
FINALITY("finality"),
|
||||
FIRST_STRIKE("first strike"),
|
||||
FLAME("flame"),
|
||||
FLOOD("flood"),
|
||||
|
|
|
|||
|
|
@ -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<Player, Card> playerCompanionMap = new HashMap<>();
|
||||
for (Player player : state.getPlayers().values()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue