Fix Sevinne's Reclamation. (#6275)

This also handles the rather unique case caused by Sevinne's Reclamation where the original spell resolves before the copy of it.
Also fixes a couple typos.
This commit is contained in:
Samuel Sandeen 2020-02-10 08:18:12 -05:00 committed by GitHub
parent ae7919cd07
commit d56f6b991b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 17 deletions

View file

@ -34,9 +34,11 @@ public final class SevinnesReclamation extends CardImpl {
public SevinnesReclamation(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}");
// Return target permanent card with converted mana cost 3 or less from your graveyard to the battlefield. If this spell was cast from a graveyard, you may copy this spell and may choose a new target for the copy.
this.getSpellAbility().addEffect(new SevinnesReclamationEffect());
// Return target permanent card with converted mana cost 3 or less from your graveyard to the battlefield.
this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect());
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter));
// If this spell was cast from a graveyard, you may copy this spell and may choose a new target for the copy.
this.getSpellAbility().addEffect(new SevinnesReclamationEffect());
// Flashback {4}{W}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{W}"), TimingRule.SORCERY));
@ -54,12 +56,9 @@ public final class SevinnesReclamation extends CardImpl {
class SevinnesReclamationEffect extends OneShotEffect {
private static final Effect effect = new ReturnFromGraveyardToBattlefieldTargetEffect();
SevinnesReclamationEffect() {
super(Outcome.Benefit);
staticText = "Return target permanent card with converted mana cost 3 or less " +
"from your graveyard to the battlefield. If this spell was cast from a graveyard, " +
staticText = "If this spell was cast from a graveyard, " +
"you may copy this spell and may choose a new target for the copy.";
}
@ -74,12 +73,12 @@ class SevinnesReclamationEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Spell spell = (Spell) game.getStack().getStackObject(source.getSourceId());
// If a spell is a copy it wasn't cast from the graveyard.
Spell spell = game.getStack().getSpell(source.getSourceId(), false);
Player player = game.getPlayer(source.getControllerId());
if (spell == null || player == null) {
return false;
}
effect.apply(game, source);
if (spell.getFromZone() == Zone.GRAVEYARD
&& player.chooseUse(outcome, "Copy this spell?", source, game)) {
spell.createCopyOnStack(game, source, source.getControllerId(), true);

View file

@ -375,4 +375,33 @@ public class CopySpellTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Flame Slash", 1);
}
/**
* Sevinne's Reclamation is almost unique in that the original spell resolves before the copy.
* As a result when resolving the original the copy was being removed from the stack instead.
*/
@Test
public void testSevinnesReclamation() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
// Return target permanent card with converted mana cost 3 or less from your graveyard to the battlefield.
// If this spell was cast from a graveyard, you may copy this spell and may choose a new target for the copy.
// Flashback 4W
addCard(Zone.GRAVEYARD, playerA, "Sevinne's Reclamation");
addCard(Zone.GRAVEYARD, playerA, "Mountain");
addCard(Zone.GRAVEYARD, playerA, "Island");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback");
addTarget(playerA, "Mountain");
setChoice(playerA, "Yes"); // Copy
setChoice(playerA, "Yes"); // Choose new target
addTarget(playerA, "Island");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Mountain", 1);
assertPermanentCount(playerA, "Island", 1);
}
}

View file

@ -198,7 +198,7 @@ public abstract class AbilityImpl implements Ability {
/**
* game.applyEffects() has to be done at least for every effect that
* moves cards/permanent between zones, or changes control of
* objects so Static effects work as intened if dependant from the
* objects so Static effects work as intended if dependant from the
* moved objects zone it is in Otherwise for example were static
* abilities with replacement effects deactivated too late Example:
* {@link org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy}
@ -924,7 +924,7 @@ public abstract class AbilityImpl implements Ability {
} else {
parameterSourceId = getSourceId();
}
// check agains shortLKI for effects that move multiple object at the same time (e.g. destroy all)
// check against shortLKI for effects that move multiple object at the same time (e.g. destroy all)
if (game.getShortLivingLKI(getSourceId(), getZone())) {
return true;
}

View file

@ -499,21 +499,21 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
case STACK:
StackObject stackObject;
if (getSpellAbility() != null) {
stackObject = game.getStack().getSpell(getSpellAbility().getId());
stackObject = game.getStack().getSpell(getSpellAbility().getId(), false);
} else {
stackObject = game.getStack().getSpell(this.getId());
stackObject = game.getStack().getSpell(this.getId(), false);
}
if (stackObject == null && (this instanceof SplitCard)) { // handle if half of Split cast is on the stack
stackObject = game.getStack().getSpell(((SplitCard) this).getLeftHalfCard().getId());
stackObject = game.getStack().getSpell(((SplitCard) this).getLeftHalfCard().getId(), false);
if (stackObject == null) {
stackObject = game.getStack().getSpell(((SplitCard) this).getRightHalfCard().getId());
stackObject = game.getStack().getSpell(((SplitCard) this).getRightHalfCard().getId(), false);
}
}
if (stackObject == null && (this instanceof AdventureCard)) {
stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId());
stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false);
}
if (stackObject == null) {
stackObject = game.getStack().getSpell(getId());
stackObject = game.getStack().getSpell(getId(), false);
}
if (stackObject != null) {
removed = game.getStack().remove(stackObject, game);

View file

@ -113,10 +113,16 @@ public class SpellStack extends ArrayDeque<StackObject> {
}
public Spell getSpell(UUID id) {
return getSpell(id, true);
}
public Spell getSpell(UUID id, boolean allowCopies) {
for (StackObject stackObject : this) {
if (stackObject instanceof Spell) {
if (stackObject.getId().equals(id) || stackObject.getSourceId().equals(id)) {
return (Spell) stackObject;
if (allowCopies || !stackObject.isCopy()) {
return (Spell) stackObject;
}
}
}
}