mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -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.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||
import mage.abilities.effects.common.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import mage.MageObjectReference;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||
import mage.abilities.effects.common.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import mage.MageObjectReference;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||
import mage.abilities.effects.common.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
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("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("Salvation Swan", 28, Rarity.RARE, mage.cards.s.SalvationSwan.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.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.MeldCard;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -11,6 +12,7 @@ import mage.counters.Counters;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
// TODO: refactor into ReturnToBattlefieldUnderOwnerControlWithCounterTargetEffect
|
||||
public class ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect extends OneShotEffect {
|
||||
|
||||
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