mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
implement [BLB] Salvation Swan
This commit is contained in:
parent
d29c57d2af
commit
62a99a0497
8 changed files with 245 additions and 4 deletions
|
|
@ -9,7 +9,7 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil
|
||||||
import mage.abilities.costs.common.TapSourceCost;
|
import mage.abilities.costs.common.TapSourceCost;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
import mage.abilities.effects.common.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
import mage.abilities.effects.common.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
import mage.abilities.effects.common.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
|
|
|
||||||
120
Mage.Sets/src/mage/cards/s/SalvationSwan.java
Normal file
120
Mage.Sets/src/mage/cards/s/SalvationSwan.java
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
package mage.cards.s;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility;
|
||||||
|
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect;
|
||||||
|
import mage.abilities.keyword.FlashAbility;
|
||||||
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||||
|
import mage.filter.common.FilterControlledPermanent;
|
||||||
|
import mage.filter.predicate.Predicates;
|
||||||
|
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||||
|
import mage.game.ExileZone;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.target.common.TargetControlledCreaturePermanent;
|
||||||
|
import mage.target.targetpointer.FixedTargets;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Susucr
|
||||||
|
*/
|
||||||
|
public final class SalvationSwan extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterControlledPermanent filterBird = new FilterControlledPermanent(SubType.BIRD, "Bird you control");
|
||||||
|
private static final FilterControlledCreaturePermanent filterWithoutFlying = new FilterControlledCreaturePermanent("creature you control without flying");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filterWithoutFlying.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SalvationSwan(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.BIRD);
|
||||||
|
this.subtype.add(SubType.CLERIC);
|
||||||
|
this.power = new MageInt(3);
|
||||||
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
|
// Flash
|
||||||
|
this.addAbility(FlashAbility.getInstance());
|
||||||
|
|
||||||
|
// Flying
|
||||||
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
|
||||||
|
// Whenever Salvation Swan or another Bird you control enters, exile up to one target creature you control without flying. Return it to the battlefield under its owner's control with a flying counter on it at the beginning of the next end step.
|
||||||
|
Ability ability = new EntersBattlefieldThisOrAnotherTriggeredAbility(
|
||||||
|
new SalvationSwanTargetEffect(), filterBird, false, true
|
||||||
|
);
|
||||||
|
ability.addTarget(new TargetControlledCreaturePermanent(0, 1, filterWithoutFlying, false));
|
||||||
|
this.addAbility(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SalvationSwan(final SalvationSwan card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SalvationSwan copy() {
|
||||||
|
return new SalvationSwan(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to Otherwordly Journey
|
||||||
|
class SalvationSwanTargetEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
SalvationSwanTargetEffect() {
|
||||||
|
super(Outcome.Benefit);
|
||||||
|
staticText = " exile up to one target creature you control without flying. "
|
||||||
|
+ "Return it to the battlefield under its owner's control "
|
||||||
|
+ "with a flying counter on it at the beginning of the next end step";
|
||||||
|
}
|
||||||
|
|
||||||
|
private SalvationSwanTargetEffect(final SalvationSwanTargetEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SalvationSwanTargetEffect copy() {
|
||||||
|
return new SalvationSwanTargetEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
Permanent permanent = game.getPermanent(source.getFirstTarget());
|
||||||
|
if (permanent == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We want a unique exile zone for each time this effect resolves.
|
||||||
|
UUID exileId = UUID.randomUUID();
|
||||||
|
String exileName = CardUtil.getSourceIdName(game, source);
|
||||||
|
permanent.moveToExile(exileId, exileName, source, game);
|
||||||
|
|
||||||
|
OneShotEffect effect = new ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect(
|
||||||
|
CounterType.FLYING.createInstance(), true
|
||||||
|
);
|
||||||
|
|
||||||
|
ExileZone exile = game.getExile().getExileZone(exileId);
|
||||||
|
if (exile != null) {
|
||||||
|
exile.setCleanupOnEndTurn(true);
|
||||||
|
effect.setTargetPointer(new FixedTargets(exile.getCards(game), game));
|
||||||
|
}
|
||||||
|
|
||||||
|
// create delayed triggered ability, of note the trigger is created even if no card would be returned.
|
||||||
|
// TODO: There is currently no way to know which cards will be returned by the trigger.
|
||||||
|
// Maybe we need a hint to refer to the content of an exile zone?
|
||||||
|
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ public final class Bloomburrow extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Oakhollow Village", 258, Rarity.UNCOMMON, mage.cards.o.OakhollowVillage.class));
|
cards.add(new SetCardInfo("Oakhollow Village", 258, Rarity.UNCOMMON, mage.cards.o.OakhollowVillage.class));
|
||||||
cards.add(new SetCardInfo("Pearl of Wisdom", 64, Rarity.COMMON, mage.cards.p.PearlOfWisdom.class));
|
cards.add(new SetCardInfo("Pearl of Wisdom", 64, Rarity.COMMON, mage.cards.p.PearlOfWisdom.class));
|
||||||
cards.add(new SetCardInfo("Rockface Village", 259, Rarity.UNCOMMON, mage.cards.r.RockfaceVillage.class));
|
cards.add(new SetCardInfo("Rockface Village", 259, Rarity.UNCOMMON, mage.cards.r.RockfaceVillage.class));
|
||||||
|
cards.add(new SetCardInfo("Salvation Swan", 28, Rarity.RARE, mage.cards.s.SalvationSwan.class));
|
||||||
cards.add(new SetCardInfo("Sunshower Druid", 195, Rarity.COMMON, mage.cards.s.SunshowerDruid.class));
|
cards.add(new SetCardInfo("Sunshower Druid", 195, Rarity.COMMON, mage.cards.s.SunshowerDruid.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.mage.test.cards.single.blb;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Susucr
|
||||||
|
*/
|
||||||
|
public class SalvationSwanTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link mage.cards.s.SalvationSwan Salvation Swan} {3}{W}
|
||||||
|
* <p>
|
||||||
|
* Creature — Bird Cleric
|
||||||
|
* Flash
|
||||||
|
* Flying
|
||||||
|
* Whenever Salvation Swan or another Bird you control enters, exile up to one target creature you control without flying. Return it to the battlefield under its owner’s control with a flying counter on it at the beginning of the next end step.
|
||||||
|
* 3/3
|
||||||
|
*/
|
||||||
|
private static final String swan = "Salvation Swan";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_Simple() {
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
|
||||||
|
addCard(Zone.HAND, playerA, swan);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swan);
|
||||||
|
addTarget(playerA, "Grizzly Bears");
|
||||||
|
|
||||||
|
checkExileCount("Bears in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1);
|
||||||
|
|
||||||
|
setStopAt(2, PhaseStep.UPKEEP);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||||
|
assertCounterCount(playerA, "Grizzly Bears", CounterType.FLYING, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package mage.abilities.effects;
|
package mage.abilities.effects.common;
|
||||||
|
|
||||||
import mage.MageObjectReference;
|
import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.MeldCard;
|
import mage.cards.MeldCard;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
|
|
@ -11,6 +12,7 @@ import mage.counters.Counters;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
|
||||||
|
// TODO: refactor into ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect
|
||||||
public class ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect extends OneShotEffect {
|
public class ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect extends OneShotEffect {
|
||||||
|
|
||||||
private final MageObjectReference objectToReturn;
|
private final MageObjectReference objectToReturn;
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package mage.abilities.effects.common;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.Mode;
|
||||||
|
import mage.counters.Counter;
|
||||||
|
import mage.counters.Counters;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Susucr
|
||||||
|
*/
|
||||||
|
public class ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect extends ReturnToBattlefieldUnderOwnerControlTargetEffect {
|
||||||
|
|
||||||
|
private final Counters counters;
|
||||||
|
private final String counterText;
|
||||||
|
|
||||||
|
public ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect(Counter counter, boolean returnFromExileZoneOnly) {
|
||||||
|
this(counter, false, returnFromExileZoneOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect(Counter counter, boolean additional, boolean returnFromExileZoneOnly) {
|
||||||
|
super(false, returnFromExileZoneOnly);
|
||||||
|
this.counters = new Counters();
|
||||||
|
this.counters.addCounter(counter);
|
||||||
|
this.counterText = makeText(counter, additional);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect(final ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
this.counters = effect.counters.copy();
|
||||||
|
this.counterText = effect.counterText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect copy() {
|
||||||
|
return new ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
|
||||||
|
game.setEnterWithCounters(targetId, counters.copy());
|
||||||
|
}
|
||||||
|
return super.apply(game, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String makeText(Counter counter, boolean additional) {
|
||||||
|
StringBuilder sb = new StringBuilder(" with ");
|
||||||
|
if (additional) {
|
||||||
|
sb.append(CardUtil.numberToText(counter.getCount(), "an"));
|
||||||
|
sb.append(" additional");
|
||||||
|
} else {
|
||||||
|
sb.append(CardUtil.numberToText(counter.getCount(), "a"));
|
||||||
|
}
|
||||||
|
sb.append(' ');
|
||||||
|
sb.append(counter.getName());
|
||||||
|
sb.append(" counter");
|
||||||
|
if (counter.getCount() != 1) {
|
||||||
|
sb.append('s');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText(Mode mode) {
|
||||||
|
if (staticText != null && !staticText.isEmpty()) {
|
||||||
|
return staticText;
|
||||||
|
}
|
||||||
|
return super.getText(mode) + counterText + (getTargetPointer().isPlural(mode.getTargets()) ? " on them" : " on it");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue