mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 03:22:00 -08:00
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:
parent
ae7919cd07
commit
d56f6b991b
5 changed files with 51 additions and 17 deletions
|
|
@ -34,9 +34,11 @@ public final class SevinnesReclamation extends CardImpl {
|
||||||
public SevinnesReclamation(UUID ownerId, CardSetInfo setInfo) {
|
public SevinnesReclamation(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}");
|
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.
|
// Return target permanent card with converted mana cost 3 or less from your graveyard to the battlefield.
|
||||||
this.getSpellAbility().addEffect(new SevinnesReclamationEffect());
|
this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect());
|
||||||
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter));
|
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}
|
// Flashback {4}{W}
|
||||||
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{W}"), TimingRule.SORCERY));
|
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{W}"), TimingRule.SORCERY));
|
||||||
|
|
@ -54,12 +56,9 @@ public final class SevinnesReclamation extends CardImpl {
|
||||||
|
|
||||||
class SevinnesReclamationEffect extends OneShotEffect {
|
class SevinnesReclamationEffect extends OneShotEffect {
|
||||||
|
|
||||||
private static final Effect effect = new ReturnFromGraveyardToBattlefieldTargetEffect();
|
|
||||||
|
|
||||||
SevinnesReclamationEffect() {
|
SevinnesReclamationEffect() {
|
||||||
super(Outcome.Benefit);
|
super(Outcome.Benefit);
|
||||||
staticText = "Return target permanent card with converted mana cost 3 or less " +
|
staticText = "If this spell was cast from a graveyard, " +
|
||||||
"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.";
|
"you may copy this spell and may choose a new target for the copy.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,12 +73,12 @@ class SevinnesReclamationEffect extends OneShotEffect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
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());
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
if (spell == null || player == null) {
|
if (spell == null || player == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
effect.apply(game, source);
|
|
||||||
if (spell.getFromZone() == Zone.GRAVEYARD
|
if (spell.getFromZone() == Zone.GRAVEYARD
|
||||||
&& player.chooseUse(outcome, "Copy this spell?", source, game)) {
|
&& player.chooseUse(outcome, "Copy this spell?", source, game)) {
|
||||||
spell.createCopyOnStack(game, source, source.getControllerId(), true);
|
spell.createCopyOnStack(game, source, source.getControllerId(), true);
|
||||||
|
|
|
||||||
|
|
@ -375,4 +375,33 @@ public class CopySpellTest extends CardTestPlayerBase {
|
||||||
assertGraveyardCount(playerB, "Flame Slash", 1);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ public abstract class AbilityImpl implements Ability {
|
||||||
/**
|
/**
|
||||||
* game.applyEffects() has to be done at least for every effect that
|
* game.applyEffects() has to be done at least for every effect that
|
||||||
* moves cards/permanent between zones, or changes control of
|
* 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
|
* moved objects zone it is in Otherwise for example were static
|
||||||
* abilities with replacement effects deactivated too late Example:
|
* abilities with replacement effects deactivated too late Example:
|
||||||
* {@link org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy}
|
* {@link org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy}
|
||||||
|
|
@ -924,7 +924,7 @@ public abstract class AbilityImpl implements Ability {
|
||||||
} else {
|
} else {
|
||||||
parameterSourceId = getSourceId();
|
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())) {
|
if (game.getShortLivingLKI(getSourceId(), getZone())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -499,21 +499,21 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
case STACK:
|
case STACK:
|
||||||
StackObject stackObject;
|
StackObject stackObject;
|
||||||
if (getSpellAbility() != null) {
|
if (getSpellAbility() != null) {
|
||||||
stackObject = game.getStack().getSpell(getSpellAbility().getId());
|
stackObject = game.getStack().getSpell(getSpellAbility().getId(), false);
|
||||||
} else {
|
} 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
|
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) {
|
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)) {
|
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) {
|
if (stackObject == null) {
|
||||||
stackObject = game.getStack().getSpell(getId());
|
stackObject = game.getStack().getSpell(getId(), false);
|
||||||
}
|
}
|
||||||
if (stackObject != null) {
|
if (stackObject != null) {
|
||||||
removed = game.getStack().remove(stackObject, game);
|
removed = game.getStack().remove(stackObject, game);
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,16 @@ public class SpellStack extends ArrayDeque<StackObject> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Spell getSpell(UUID id) {
|
public Spell getSpell(UUID id) {
|
||||||
|
return getSpell(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spell getSpell(UUID id, boolean allowCopies) {
|
||||||
for (StackObject stackObject : this) {
|
for (StackObject stackObject : this) {
|
||||||
if (stackObject instanceof Spell) {
|
if (stackObject instanceof Spell) {
|
||||||
if (stackObject.getId().equals(id) || stackObject.getSourceId().equals(id)) {
|
if (stackObject.getId().equals(id) || stackObject.getSourceId().equals(id)) {
|
||||||
return (Spell) stackObject;
|
if (allowCopies || !stackObject.isCopy()) {
|
||||||
|
return (Spell) stackObject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue