[LTC] Implement Gilraen, Dunedain Protector (#10728)

* [LTC] Implement Gilraen, Dunedain Protector

* add tests on Gilraen

* apply review
This commit is contained in:
Susucre 2023-08-12 22:16:02 +02:00 committed by GitHub
parent eef8f508e4
commit 2d53668c96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 317 additions and 190 deletions

View file

@ -0,0 +1,128 @@
package mage.cards.g;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.*;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
* @author Susucr
*/
public final class GilraenDunedainProtector extends CardImpl {
public GilraenDunedainProtector(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.NOBLE);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// {2}, {T}: Exile another target creature you control. You may return that card to the battlefield under its owner's control. If you don't, at the beginning of the next end step, return that card to the battlefield under its owner's control with a vigilance counter and a lifelink counter on it.
ActivatedAbility ability = new SimpleActivatedAbility(
new GilraenDunedainProtectorEffect(),
new ManaCostsImpl<>("{2}")
);
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(ability);
}
private GilraenDunedainProtector(final GilraenDunedainProtector card) {
super(card);
}
@Override
public GilraenDunedainProtector copy() {
return new GilraenDunedainProtector(this);
}
}
class GilraenDunedainProtectorEffect extends OneShotEffect {
private static final Counters counters = new Counters();
static {
counters.addCounter(CounterType.VIGILANCE.createInstance())
.addCounter(CounterType.LIFELINK.createInstance());
}
GilraenDunedainProtectorEffect() {
super(Outcome.Benefit);
staticText = "Exile another target creature you control. You may return that card to the battlefield "
+ "under its owner's control. If you don't, at the beginning of the next end step, return "
+ "that card to the battlefield under its owner's control with a vigilance counter and "
+ "a lifelink counter on it.";
}
private GilraenDunedainProtectorEffect(final GilraenDunedainProtectorEffect effect) {
super(effect);
}
@Override
public GilraenDunedainProtectorEffect copy() {
return new GilraenDunedainProtectorEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
Permanent permanent = game.getPermanent(source.getFirstTarget());
Player controller = game.getPlayer(source.getControllerId());
if (permanent == null || controller == null || sourcePermanent == null) {
return false;
}
// Exile another target creature you control.
permanent.moveToExile(source.getSourceId(), sourcePermanent.getName(), source, game);
game.getState().processAction(game);
Card card = game.getExile().getCard(permanent.getId(), game);
boolean choice = controller.chooseUse(Outcome.Neutral, "Return that card to the battlefield now?", source, game);
if (choice) {
// return that card to the battlefield under its owner's control.
if (card != null) {
PutCards.BATTLEFIELD.moveCards(
game.getPlayer(card.getOwnerId()),
new CardsImpl(card),
source,
game
);
}
} else {
// at the beginning of the next end step, return that card to the battlefield under
// its owner's control with a vigilance counter and a lifelink counter on it.
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
new ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(
new MageObjectReference(card, game),
counters,
"a vigilance counter and a lifelink counter"
)
), source);
}
return true;
}
}

View file

@ -1,31 +1,25 @@
package mage.cards.l;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.MeldCard;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class LongRoadHome extends CardImpl {
@ -73,7 +67,12 @@ class LongRoadHomeEffect extends OneShotEffect {
if (card != null) {
//create delayed triggered ability
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
new LongRoadHomeReturnFromExileEffect(new MageObjectReference(card, game))), source);
new ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(
new MageObjectReference(card, game),
CounterType.P1P1.createInstance(),
"a +1/+1 counter"
)
), source);
}
}
return true;
@ -87,88 +86,4 @@ class LongRoadHomeEffect extends OneShotEffect {
return new LongRoadHomeEffect(this);
}
}
class LongRoadHomeReturnFromExileEffect extends OneShotEffect {
MageObjectReference objectToReturn;
public LongRoadHomeReturnFromExileEffect(MageObjectReference objectToReturn) {
super(Outcome.PutCardInPlay);
this.objectToReturn = objectToReturn;
staticText = "return that card to the battlefield under its owner's control with a +1/+1 counter on it";
}
public LongRoadHomeReturnFromExileEffect(final LongRoadHomeReturnFromExileEffect effect) {
super(effect);
this.objectToReturn = effect.objectToReturn;
}
@Override
public LongRoadHomeReturnFromExileEffect copy() {
return new LongRoadHomeReturnFromExileEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(objectToReturn.getSourceId());
if (card != null && objectToReturn.refersTo(card, game)) {
Player owner = game.getPlayer(card.getOwnerId());
if (owner != null) {
if (card instanceof MeldCard) {
MeldCard meldCard = (MeldCard) card;
game.addEffect(new LongRoadHomeEntersBattlefieldEffect(new MageObjectReference(meldCard.getTopHalfCard(), game)), source);
game.addEffect(new LongRoadHomeEntersBattlefieldEffect(new MageObjectReference(meldCard.getBottomHalfCard(), game)), source);
} else {
game.addEffect(new LongRoadHomeEntersBattlefieldEffect(objectToReturn), source);
}
owner.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null);
}
}
return true;
}
}
class LongRoadHomeEntersBattlefieldEffect extends ReplacementEffectImpl {
MageObjectReference objectToReturn;
public LongRoadHomeEntersBattlefieldEffect(MageObjectReference objectToReturn) {
super(Duration.Custom, Outcome.BoostCreature);
this.objectToReturn = objectToReturn;
staticText = "that card to the battlefield under its owner's control with a +1/+1 counter on it";
}
public LongRoadHomeEntersBattlefieldEffect(LongRoadHomeEntersBattlefieldEffect effect) {
super(effect);
this.objectToReturn = effect.objectToReturn;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
return event.getTargetId().equals(objectToReturn.getSourceId());
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
if (permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game, event.getAppliedEffects());
discard(); // use only once
}
return false;
}
@Override
public LongRoadHomeEntersBattlefieldEffect copy() {
return new LongRoadHomeEntersBattlefieldEffect(this);
}
}
}

View file

@ -1,31 +1,25 @@
package mage.cards.o;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author LevelX
*/
@ -75,9 +69,13 @@ class OtherworldlyJourneyEffect extends OneShotEffect {
Card card = game.getCard(permanent.getId());
if (card != null) {
//create delayed triggered ability
DelayedTriggeredAbility delayedAbility
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new OtherworldlyJourneyReturnFromExileEffect(new MageObjectReference(card, game)));
game.addDelayedTriggeredAbility(delayedAbility, source);
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
new ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(
new MageObjectReference(card, game),
CounterType.P1P1.createInstance(),
"a +1/+1 counter"
)
), source);
}
}
return true;
@ -91,82 +89,4 @@ class OtherworldlyJourneyEffect extends OneShotEffect {
return new OtherworldlyJourneyEffect(this);
}
}
class OtherworldlyJourneyReturnFromExileEffect extends OneShotEffect {
MageObjectReference objectToReturn;
public OtherworldlyJourneyReturnFromExileEffect(MageObjectReference objectToReturn) {
super(Outcome.PutCardInPlay);
this.objectToReturn = objectToReturn;
staticText = "return that card to the battlefield under its owner's control with a +1/+1 counter on it";
}
public OtherworldlyJourneyReturnFromExileEffect(final OtherworldlyJourneyReturnFromExileEffect effect) {
super(effect);
this.objectToReturn = effect.objectToReturn;
}
@Override
public OtherworldlyJourneyReturnFromExileEffect copy() {
return new OtherworldlyJourneyReturnFromExileEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(objectToReturn.getSourceId());
if (card != null && objectToReturn.refersTo(card, game)) {
Player owner = game.getPlayer(card.getOwnerId());
if (owner != null) {
game.addEffect(new OtherworldlyJourneyEntersBattlefieldEffect(objectToReturn), source);
owner.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null);
}
}
return true;
}
}
class OtherworldlyJourneyEntersBattlefieldEffect extends ReplacementEffectImpl {
MageObjectReference objectToReturn;
public OtherworldlyJourneyEntersBattlefieldEffect(MageObjectReference objectToReturn) {
super(Duration.Custom, Outcome.BoostCreature);
this.objectToReturn = objectToReturn;
staticText = "that card returns to the battlefield with a +1/+1 counter on it";
}
public OtherworldlyJourneyEntersBattlefieldEffect(OtherworldlyJourneyEntersBattlefieldEffect effect) {
super(effect);
this.objectToReturn = effect.objectToReturn;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return EventType.ENTERS_THE_BATTLEFIELD == event.getType();
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
return event.getTargetId().equals(objectToReturn.getSourceId());
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
if (permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game, event.getAppliedEffects());
discard(); // use only once
}
return false;
}
@Override
public OtherworldlyJourneyEntersBattlefieldEffect copy() {
return new OtherworldlyJourneyEntersBattlefieldEffect(this);
}
}
}

View file

@ -127,6 +127,7 @@ public final class TalesOfMiddleEarthCommander extends ExpansionSet {
cards.add(new SetCardInfo("Genesis Wave", 245, Rarity.RARE, mage.cards.g.GenesisWave.class));
cards.add(new SetCardInfo("Ghost Quarter", 314, Rarity.UNCOMMON, mage.cards.g.GhostQuarter.class));
cards.add(new SetCardInfo("Gilded Goose", 246, Rarity.RARE, mage.cards.g.GildedGoose.class));
cards.add(new SetCardInfo("Gilraen, Dunedain Protector", 13, Rarity.RARE, mage.cards.g.GilraenDunedainProtector.class));
cards.add(new SetCardInfo("Gimli of the Glittering Caves", 32, Rarity.RARE, mage.cards.g.GimliOfTheGlitteringCaves.class));
cards.add(new SetCardInfo("Glacial Fortress", 315, Rarity.RARE, mage.cards.g.GlacialFortress.class));
cards.add(new SetCardInfo("Go for the Throat", 201, Rarity.UNCOMMON, mage.cards.g.GoForTheThroat.class));

View file

@ -0,0 +1,90 @@
package org.mage.test.cards.single.ltc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author LevelX2
*/
public class GilraenDunedainProtectorTest extends CardTestPlayerBase {
/**
* Gilraen, Dúnedain Protector
* {2}{W}
* Legendary Creature Human Noble
* <p>
* {2}, {T}: Exile another target creature you control. You may return that card to the battlefield under its owners control. If you dont, at the beginning of the next end step, return that card to the battlefield under its owners control with a vigilance counter and a lifelink counter on it.
*/
private final String gilraen = "Gilraen, Dunedain Protector";
/**
* Ajani's Welcome
* {W}
* Enchantment
* <p>
* Whenever a creature enters the battlefield under your control, you gain 1 life.
*/
private final String welcome = "Ajani's Welcome";
@Test
public void ReturnNow() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, gilraen);
addCard(Zone.BATTLEFIELD, playerA, welcome);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // 2/2
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}: Exile another target creature you control", "Silvercoat Lion");
setChoice(playerA, true); // return now.
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Silvercoat Lion", 1);
// No counter.
assertCounterCount(playerA, "Silvercoat Lion", CounterType.VIGILANCE, 0);
assertCounterCount(playerA, "Silvercoat Lion", CounterType.LIFELINK, 0);
// Lion did re-enter the battlefield.
assertLife(playerA, 20 + 1);
}
@Test
public void ReturnLater() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, gilraen);
addCard(Zone.BATTLEFIELD, playerA, welcome);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // 2/2
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}: Exile another target creature you control", "Silvercoat Lion");
setChoice(playerA, false); // return at end of turn.
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Silvercoat Lion", 0);
// Lion did not re-enter the battlefield.
assertLife(playerA, 20);
setStopAt(1, PhaseStep.CLEANUP);
execute();
assertPermanentCount(playerA, "Silvercoat Lion", 1);
// With counters
assertCounterCount(playerA, "Silvercoat Lion", CounterType.VIGILANCE, 1);
assertCounterCount(playerA, "Silvercoat Lion", CounterType.LIFELINK, 1);
// Lion did re-enter the battlefield.
assertLife(playerA, 20 + 1);
}
}

View file

@ -0,0 +1,72 @@
package mage.abilities.effects;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.cards.MeldCard;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.Counter;
import mage.counters.Counters;
import mage.game.Game;
import mage.players.Player;
public class ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect extends OneShotEffect {
private final MageObjectReference objectToReturn;
private final Counters counters;
private final String counterText;
public ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(
MageObjectReference objectToReturn,
Counter counter,
String counterText
) {
this(objectToReturn, new Counters().addCounter(counter), counterText);
}
public ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(
MageObjectReference objectToReturn,
Counters counters,
String counterText
) {
super(Outcome.PutCardInPlay);
this.objectToReturn = objectToReturn;
this.counters = counters.copy();
this.counterText = counterText;
this.staticText = "return that card to the battlefield with " + counterText + " on it";
}
private ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(
final ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect effect
) {
super(effect);
this.objectToReturn = effect.objectToReturn;
this.counters = effect.counters.copy();
this.counterText = effect.counterText;
}
@Override
public ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect copy() {
return new ReturnMORToBattlefieldUnderOwnerControlWithCounterEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(objectToReturn.getSourceId());
if (card != null && objectToReturn.refersTo(card, game)) {
Player owner = game.getPlayer(card.getOwnerId());
if (owner != null) {
if (card instanceof MeldCard) {
MeldCard meldCard = (MeldCard) card;
game.setEnterWithCounters(meldCard.getTopHalfCard().getId(), counters);
game.setEnterWithCounters(meldCard.getBottomHalfCard().getId(), counters);
} else {
game.setEnterWithCounters(objectToReturn.getSourceId(), counters);
}
owner.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null);
}
}
return true;
}
}

View file

@ -25,19 +25,20 @@ public class Counters extends HashMap<String, Counter> implements Serializable {
return new Counters(this);
}
public void addCounter(String name, int amount) {
public Counters addCounter(String name, int amount) {
putIfAbsent(name, new Counter(name));
this.get(name).add(amount);
return this;
}
public void addCounter(Counter counter) {
public Counters addCounter(Counter counter) {
if (!containsKey(counter.name)) {
put(counter.name, counter);
} else {
get(counter.name).add(counter.getCount());
}
return this;
}
public boolean removeCounter(String name) {