mirror of
https://github.com/correl/mage.git
synced 2025-04-12 09:11:05 -09:00
This commit is contained in:
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
abilities
cards
constants
players
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ package mage.constants;
|
|||
*/
|
||||
public enum SpellAbilityCastMode {
|
||||
NORMAL("Normal"),
|
||||
MADNESS("Madness");
|
||||
MADNESS("Madness"),
|
||||
FLASHBACK("Flashback");
|
||||
|
||||
private final String text;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue