1
0
Fork 0
mirror of https://github.com/correl/mage.git synced 2025-04-12 09:11:05 -09:00

* Reworked flashback ability (fixes ).

This commit is contained in:
LevelX2 2018-02-18 02:52:16 +01:00
parent 2e827a50ec
commit d80d588963
9 changed files with 107 additions and 122 deletions
Mage.Sets/src/mage/cards/c
Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords
Mage/src/main/java/mage

View file

@ -30,7 +30,6 @@ package mage.cards.c;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.common.DiscardXTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
@ -82,8 +81,8 @@ class ConflagrateVariableValue implements DynamicValue {
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int xValue = sourceAbility.getManaCostsToPay().getX();
for (Cost cost : sourceAbility.getCosts()) {
if (cost instanceof DiscardTargetCost) {
xValue = ((DiscardTargetCost) cost).getCards().size();
if (cost instanceof DiscardXTargetCost) {
xValue = ((DiscardXTargetCost) cost).getAmount();
}
}
return xValue;

View file

@ -27,9 +27,9 @@
*/
package org.mage.test.cards.abilities.keywords;
import mage.abilities.keyword.TrampleAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -39,6 +39,27 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*/
public class FlashbackTest extends CardTestPlayerBase {
@Test
public void testNormalWildHunger() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// Target creature gets +3/+1 and gains trample until end of turn.
// Flashback {3}{R}
addCard(Zone.GRAVEYARD, playerA, "Wild Hunger");
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback", "Silvercoat Lion");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPowerToughness(playerA, "Silvercoat Lion", 5, 3);
assertAbility(playerA, "Silvercoat Lion", TrampleAbility.getInstance(), true);
assertExileCount("Wild Hunger", 1);
}
/**
* Fracturing Gust is bugged. In a match against Affinity, it worked
* properly when cast from hand. When I cast it from graveyard c/o
@ -219,7 +240,7 @@ public class FlashbackTest extends CardTestPlayerBase {
// Conflagrate deals X damage divided as you choose among any number of target creatures and/or players.
// Flashback-{R}{R}, Discard X cards.
addCard(Zone.HAND, playerA, "Conflagrate", 1);
addCard(Zone.HAND, playerA, "Conflagrate", 1); // Sorcery {X}{X}{R}
addCard(Zone.HAND, playerA, "Forest", 4);
@ -307,20 +328,26 @@ public class FlashbackTest extends CardTestPlayerBase {
public void testAltarsReap() {
addCard(Zone.LIBRARY, playerA, "Island", 2);
addCard(Zone.GRAVEYARD, playerA, "Altar's Reap", 1);
// As an additional cost to cast Altar's Reap, sacrifice a creature.
// Draw two cards.
addCard(Zone.GRAVEYARD, playerA, "Altar's Reap", 1); // Instant {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 4);
addCard(Zone.HAND, playerA, "Snapcaster Mage", 1);
// Flash
// When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn.
// The flashback cost is equal to its mana cost.
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage");
setChoice(playerA, "Altar's Reap");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {1}{B}");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback");
setChoice(playerA, "Snapcaster Mage");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Snapcaster Mage", 1);
assertExileCount(playerA, "Altar's Reap", 1);
}
/**
@ -520,7 +547,6 @@ public class FlashbackTest extends CardTestPlayerBase {
* to a spell, and the flashback cost is already an alternative cost.
*/
@Test
@Ignore
public void testSnapcasterMageSpellWithAlternateCost() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);

View file

@ -44,7 +44,6 @@ import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DynamicManaEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
import mage.cards.SplitCard;
@ -337,9 +336,7 @@ public abstract class AbilityImpl implements Ability {
if (sourceObject != null && this.getAbilityType() != AbilityType.TRIGGERED) { // triggered abilities check this already in playerImpl.triggerAbility
sourceObject.adjustTargets(this, game);
}
// Flashback abilities haven't made the choices the underlying spell might need for targeting.
if (!(this instanceof FlashbackAbility)
&& !getTargets().isEmpty()) {
if (!getTargets().isEmpty()) {
Outcome outcome = getEffects().isEmpty() ? Outcome.Detriment : getEffects().get(0).getOutcome();
if (getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game) == false) {
if ((variableManaCost != null || announceString != null)) {
@ -445,8 +442,15 @@ public abstract class AbilityImpl implements Ability {
@Override
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
if (this instanceof SpellAbility) {
if (((SpellAbility) this).getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
// A player can't apply two alternative methods of casting or two alternative costs to a single spell.
// So can only use alternate costs if the spell is cast in normal mode
return false;
}
}
boolean alternativeCostisUsed = false;
if (sourceObject != null && !(sourceObject instanceof Permanent) && !(this instanceof FlashbackAbility)) {
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
Abilities<Ability> abilities = null;
if (sourceObject instanceof Card) {
abilities = ((Card) sourceObject).getAbilities(game);

View file

@ -217,7 +217,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
this.name = "Cast fused " + cardName;
break;
default:
this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " by " + spellAbilityCastMode.toString() : "");
this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " using " + spellAbilityCastMode.toString() : "");
}
}
@ -230,4 +230,11 @@ public class SpellAbility extends ActivatedAbilityImpl {
setSpellName();
}
public SpellAbility getSpellAbilityToResolve(Game game) {
return this;
}
public void setId(UUID idToUse) {
this.id = idToUse;
}
}

View file

@ -29,7 +29,6 @@ package mage.abilities.costs;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.mana.ManaAbility;
import mage.game.Game;
import mage.game.stack.StackObject;
@ -173,7 +172,6 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
StackObject stackObject = game.getStack().getStackObject(source.getId());
if (controller != null
&& (source instanceof ManaAbility
|| source instanceof FlashbackAbility
|| stackObject != null)) {
xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game),
"Announce the number of " + actionText, game, source, this);

View file

@ -32,14 +32,13 @@ import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.cards.SplitCard;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SpellAbilityCastMode;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
import mage.constants.Zone;
@ -66,23 +65,21 @@ import mage.target.targetpointer.FixedTarget;
public class FlashbackAbility extends SpellAbility {
private String abilityName;
private SpellAbility spellAbilityToResolve;
public FlashbackAbility(Cost cost, TimingRule timingRule) {
super(null, "", Zone.GRAVEYARD);
super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.FLASHBACK);
this.setAdditionalCostsRuleVisible(false);
this.name = "Flashback " + cost.getText();
this.addEffect(new FlashbackEffect());
this.addCost(cost);
this.timing = timingRule;
this.usesStack = false;
this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
setCostModificationActive(false);
}
public FlashbackAbility(final FlashbackAbility ability) {
super(ability);
this.spellAbilityType = ability.spellAbilityType;
this.abilityName = ability.abilityName;
this.spellAbilityToResolve = ability.spellAbilityToResolve;
}
@Override
@ -108,6 +105,47 @@ public class FlashbackAbility extends SpellAbility {
return false;
}
@Override
public SpellAbility getSpellAbilityToResolve(Game game) {
Card card = game.getCard(getSourceId());
if (card != null) {
if (spellAbilityToResolve == null) {
SpellAbility spellAbilityCopy = null;
if (card.isSplitCard()) {
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy();
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy();
}
} else {
spellAbilityCopy = card.getSpellAbility().copy();
}
if (spellAbilityCopy == null) {
return null;
}
spellAbilityCopy.setId(this.getId());
spellAbilityCopy.getManaCosts().clear();
spellAbilityCopy.getManaCostsToPay().clear();
spellAbilityCopy.getCosts().addAll(this.getCosts());
spellAbilityCopy.addCost(this.getManaCosts());
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
spellAbilityToResolve = spellAbilityCopy;
ContinuousEffect effect = new FlashbackReplacementEffect();
effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId())));
game.addEffect(effect, this);
}
}
return spellAbilityToResolve;
}
@Override
public Costs<Cost> getCosts() {
if (spellAbilityToResolve == null) {
return super.getCosts();
}
return spellAbilityToResolve.getCosts();
}
@Override
public FlashbackAbility copy() {
return new FlashbackAbility(this);
@ -144,102 +182,18 @@ public class FlashbackAbility extends SpellAbility {
return sbRule.toString();
}
@Override
public void setSpellAbilityType(SpellAbilityType spellAbilityType) {
this.spellAbilityType = spellAbilityType;
}
@Override
public SpellAbilityType getSpellAbilityType() {
return this.spellAbilityType;
}
/**
* Used for split card sin PlayerImpl method:
* getOtherUseableActivatedAbilities
*
* @param abilityName
*/
public void setAbilityName(String abilityName) {
this.abilityName = abilityName;
}
}
class FlashbackEffect extends OneShotEffect {
public FlashbackEffect() {
super(Outcome.Benefit);
staticText = "";
}
public FlashbackEffect(final FlashbackEffect effect) {
super(effect);
}
@Override
public FlashbackEffect copy() {
return new FlashbackEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = (Card) game.getObject(source.getSourceId());
if (card != null) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
SpellAbility spellAbility;
switch (((FlashbackAbility) source).getSpellAbilityType()) {
case SPLIT_LEFT:
spellAbility = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy();
break;
case SPLIT_RIGHT:
spellAbility = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy();
break;
default:
spellAbility = card.getSpellAbility().copy();
}
spellAbility.clear();
// set the payed flashback costs to the spell ability so abilities like Converge or calculation of {X} values work
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().addAll(source.getManaCosts());
spellAbility.getManaCosts().clear();
spellAbility.getManaCosts().addAll(source.getManaCosts());
// needed to get e.g. paid costs from Conflagrate
for (Cost cost : source.getCosts()) {
if (cost instanceof Costs) {
Costs<Cost> listOfCosts = (Costs<Cost>) cost;
for (Cost singleCost : listOfCosts) {
if (singleCost instanceof ManaCost) {
singleCost.clearPaid();
spellAbility.getManaCosts().add((ManaCost) singleCost);
spellAbility.getManaCostsToPay().add((ManaCost) singleCost);
} else {
spellAbility.getCosts().add(singleCost);
}
}
} else {
if (cost instanceof ManaCost) {
spellAbility.getManaCosts().add((ManaCost) cost);
spellAbility.getManaCostsToPay().add((ManaCost) cost);
} else {
spellAbility.getCosts().add(cost);
}
}
}
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() + " flashbacks " + card.getLogName());
}
if (controller.cast(spellAbility, game, false)) {
ContinuousEffect effect = new FlashbackReplacementEffect();
effect.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId())));
game.addEffect(effect, source);
}
return true;
}
}
return false;
}
}
class FlashbackReplacementEffect extends ReplacementEffectImpl {
public FlashbackReplacementEffect() {
@ -287,7 +241,7 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl {
&& ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) {
int zcc = game.getState().getZoneChangeCounter(source.getSourceId());
if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() == zcc) {
if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc) {
return true;
}

View file

@ -516,7 +516,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
Card mainCard = getMainCard();
ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK);
ZoneChangeInfo.Stack info
= new ZoneChangeInfo.Stack(event, new Spell(this, ability.copy(), controllerId, event.getFromZone()));
= new ZoneChangeInfo.Stack(event, new Spell(this, ability.getSpellAbilityToResolve(game), controllerId, event.getFromZone()));
return ZonesHandler.cast(info, game);
}

View file

@ -33,7 +33,8 @@ package mage.constants;
*/
public enum SpellAbilityCastMode {
NORMAL("Normal"),
MADNESS("Madness");
MADNESS("Madness"),
FLASHBACK("Flashback");
private final String text;

View file

@ -1158,7 +1158,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
} else {
int bookmark = game.bookmarkState();
if (ability.activate(game, ability instanceof FlashbackAbility)) {
if (ability.activate(game, false)) {
ability.resolve(game);
game.removeBookmark(bookmark);
resetStoredBookmark(game);
@ -1219,11 +1219,7 @@ public abstract class PlayerImpl implements Player, Serializable {
result = playManaAbility((ActivatedManaAbilityImpl) ability.copy(), game);
break;
case SPELL:
if (ability instanceof FlashbackAbility) {
result = playAbility(ability.copy(), game);
} else {
result = cast((SpellAbility) ability, game, false);
}
result = cast((SpellAbility) ability.copy(), game, false);
break;
default:
result = playAbility(ability.copy(), game);