* Storm - Fixed handling of countered Storm spells. * Reworked Rebound more rule conform. * Fixed that zone change counter was not raised if a card is moved to stack.

This commit is contained in:
LevelX2 2015-04-29 17:37:54 +02:00
parent 4b6993f398
commit 830765996f
15 changed files with 276 additions and 264 deletions

View file

@ -39,6 +39,7 @@ import mage.target.TargetSpell;
import mage.watchers.common.CastSpellLastTurnWatcher;
import java.util.UUID;
import mage.MageObjectReference;
/**
*
@ -79,7 +80,7 @@ class SecondSpellPredicate implements Predicate<Spell> {
public boolean apply(Spell input, Game game) {
CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher");
if (watcher.getSpellOrder(input, game) == 2) {
if (watcher.getSpellOrder(new MageObjectReference(input.getId(), game), game) == 2) {
return true;
}

View file

@ -34,6 +34,7 @@ import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
@ -46,8 +47,10 @@ import mage.cards.CardImpl;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.SubLayer;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
@ -77,8 +80,6 @@ public class NarsetTranscendent extends CardImpl {
// -2: When you cast your next instant or sorcery spell from your hand this turn, it gains rebound.
this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect(new NarsetTranscendentTriggeredAbility()), -2));
// -9:You get an emblem with "Your opponents can't cast noncreature spells."
this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new NarsetTranscendentEmblem()), -9));
}
@ -132,11 +133,10 @@ class NarsetTranscendentEffect1 extends OneShotEffect {
}
}
class NarsetTranscendentTriggeredAbility extends DelayedTriggeredAbility {
public NarsetTranscendentTriggeredAbility() {
super(new NarsetTranscendentGainAbilityEffect(), Duration.EndOfTurn, true);
super(new NarsetTranscendentGainReboundEffect(), Duration.EndOfTurn, true);
}
private NarsetTranscendentTriggeredAbility(final NarsetTranscendentTriggeredAbility ability) {
@ -175,33 +175,52 @@ class NarsetTranscendentTriggeredAbility extends DelayedTriggeredAbility {
}
}
class NarsetTranscendentGainAbilityEffect extends OneShotEffect {
class NarsetTranscendentGainReboundEffect extends ContinuousEffectImpl {
private final Ability ability;
public NarsetTranscendentGainAbilityEffect() {
super(Outcome.AddAbility);
this.ability = new ReboundAbility();
public NarsetTranscendentGainReboundEffect() {
super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = "it gains rebound";
}
public NarsetTranscendentGainAbilityEffect(final NarsetTranscendentGainAbilityEffect effect) {
public NarsetTranscendentGainReboundEffect(final NarsetTranscendentGainReboundEffect effect) {
super(effect);
this.ability = effect.ability;
}
@Override
public NarsetTranscendentGainAbilityEffect copy() {
return new NarsetTranscendentGainAbilityEffect(this);
public NarsetTranscendentGainReboundEffect copy() {
return new NarsetTranscendentGainReboundEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Spell spell = game.getState().getStack().getSpell(getTargetPointer().getFirst(game, source));
if (spell != null) {
ReboundAbility.addReboundEffectToSpellIfMissing(spell);
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source));
if (spell != null) {
Card card = spell.getCard();
if (card != null) {
addReboundAbility(card, source, game);
}
} else {
discard();
}
return true;
}
return false;
}
private void addReboundAbility(Card card, Ability source, Game game) {
boolean found = false;
for (Ability ability : card.getAbilities()) {
if (ability instanceof ReboundAbility) {
found = true;
break;
}
}
if (!found) {
Ability ability = new ReboundAbility();
game.getState().addOtherAbility(card, ability);
}
return true;
}
}

View file

@ -27,29 +27,30 @@
*/
package mage.sets.riseoftheeldrazi;
import mage.constants.CardType;
import mage.constants.Rarity;
import java.util.Iterator;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.ReboundAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.SubLayer;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.watchers.Watcher;
import java.util.Iterator;
import java.util.UUID;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
/**
* @author magenoxx_at_gmail.com
@ -132,51 +133,8 @@ class GainReboundEffect extends ContinuousEffectImpl {
}
if (!found) {
Ability ability = new ReboundAbility();
// card.addAbility(ability);
ability.setControllerId(source.getControllerId());
ability.setSourceId(card.getId());
game.getState().addOtherAbility(card, ability);
}
}
}
}
//class AttachedReboundAbility extends ReboundAbility {}
//class LeavesBattlefieldWatcher extends Watcher {
//
// public LeavesBattlefieldWatcher() {
// super("LeavesBattlefieldWatcher", WatcherScope.CARD);
// }
//
// public LeavesBattlefieldWatcher(final LeavesBattlefieldWatcher watcher) {
// super(watcher);
// }
//
// @Override
// public void watch(GameEvent event, Game game) {
// if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(this.getSourceId())) {
// ZoneChangeEvent zEvent = (ZoneChangeEvent)event;
// if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
// Player player = game.getPlayer(this.getControllerId());
// if (player != null) {
// for (Card card : player.getHand().getCards(CastThroughTime.filter, game)) {
// Iterator<Ability> it = card.getAbilities().iterator();
// while (it.hasNext()) {
// if (it.next() instanceof AttachedReboundAbility) {
// it.remove();
// }
// }
// }
// }
// }
// }
// }
//
// @Override
// public LeavesBattlefieldWatcher copy() {
// return new LeavesBattlefieldWatcher(this);
// }
//
//}

View file

@ -214,10 +214,16 @@ class PsychicIntrusionSpendAnyManaEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (objectId.equals(getTargetPointer().getFirst(game, source))) {
if (objectId.equals(((FixedTarget)getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget)getTargetPointer()).getZoneChangeCounter() +1) {
if (affectedControllerId.equals(source.getControllerId())) {
return true;
// if the card moved from exile to spell the zone change counter is increased by 1
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget)getTargetPointer()).getZoneChangeCounter() +1) {
return true;
}
}
} else {
if (((FixedTarget)getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted

View file

@ -15,9 +15,16 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*/
public class ReboundTest extends CardTestPlayerBase{
/**
* Test that the spell with rebound is moved to exile if
* the spell resolves
*/
@Test
public void testCastFromHandMovedToExile() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// Target creature gets +1/+0 until end of turn and is unblockable this turn.
addCard(Zone.HAND, playerA, "Distortion Strike");
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1);
@ -28,8 +35,90 @@ public class ReboundTest extends CardTestPlayerBase{
execute();
//check exile and graveyard
assertExileCount("Distortion Strike", 1);
assertGraveyardCount(playerA, 0);
}
/**
* Test that the spell with rebound can be cast again
* on the beginning of the next upkeep without paying mana costs
*/
@Test
public void testRecastFromExile() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// Target creature gets +1/+0 until end of turn and is unblockable this turn.
addCard(Zone.HAND, playerA, "Distortion Strike");
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Distortion Strike", "Memnite");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
//check exile and graveyard
assertPowerToughness(playerA, "Memnite", 2, 1);
assertExileCount("Distortion Strike", 0);
assertGraveyardCount(playerA, "Distortion Strike", 1);
}
/**
* Check that a countered spell with rebound
* is not cast again
*/
@Test
public void testDontRecastAfterCounter() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// Target creature gets +1/+0 until end of turn and is unblockable this turn.
addCard(Zone.HAND, playerA, "Distortion Strike");
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
addCard(Zone.HAND, playerB, "Counterspell");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Distortion Strike", "Memnite");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Distortion Strike");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
//check exile and graveyard
assertGraveyardCount(playerB, "Counterspell", 1);
assertGraveyardCount(playerA, "Distortion Strike", 1);
assertPowerToughness(playerA, "Memnite", 1, 1);
assertExileCount("Distortion Strike", 0);
}
/**
* Check that a fizzled spell with rebound
* is not cast again on the next controllers upkeep
*/
@Test
public void testDontRecastAfterFizzling() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// Target creature gets +1/+0 until end of turn and is unblockable this turn.
addCard(Zone.HAND, playerA, "Distortion Strike");
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
addCard(Zone.HAND, playerB, "Lightning Bolt", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Distortion Strike", "Memnite");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Memnite","Distortion Strike");
setStopAt(1, PhaseStep.END_TURN);
execute();
//check exile and graveyard
assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertGraveyardCount(playerA, "Distortion Strike", 1);
assertGraveyardCount(playerA, "Memnite", 1);
}
}

View file

@ -30,7 +30,6 @@ package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -137,4 +136,32 @@ public class StormTest extends CardTestPlayerBase {
assertLife(playerB, 19);
}
/**
* If a spell with storm gets countered, the strom trigger is also stifled, which isn't how its supposed to work.
* For example a Chalic of the Void set to 1 counters Flusterstorm and also counters the storm trigger, which shouldn't happen
*/
@Test
public void testStormSpellCountered() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Grapeshot deals 1 damage to target creature or player.
// Storm (When you cast this spell, copy it for each spell cast before it this turn. You may choose new targets for the copies.)
addCard(Zone.HAND, playerA, "Grapeshot");
addCard(Zone.HAND, playerA, "Lightning Bolt");
addCard(Zone.HAND, playerB, "Counterspell");
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grapeshot", playerB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Counterspell", "Grapeshot");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 16); // 3 (Lightning Bolt) + 1 from Storm copied Grapeshot
}
}

View file

@ -32,6 +32,8 @@ public class CastThroughTimeTest extends CardTestPlayerBase {
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertLife(playerA, 20);
assertLife(playerB, 14);
}

View file

@ -28,30 +28,25 @@
package mage.abilities.keyword;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.MageObject;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import java.util.UUID;
/**
* This ability has no effect by default and will always return false on the call
* to apply. This is because of how the {@link ReboundEffect} works. It will
@ -73,163 +68,34 @@ import java.util.UUID;
*
* @author maurer.it_at_gmail.com, noxx
*/
public class ReboundAbility extends TriggeredAbilityImpl {
//20101001 - 702.85
private boolean installReboundEffect;
private static String reboundText = "Rebound <i>(If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)</i>";
public class ReboundAbility extends SimpleStaticAbility {
public ReboundAbility() {
super(Zone.STACK, null);
this.installReboundEffect = false;
super(Zone.STACK, new ReboundCastFromHandReplacementEffect());
}
public ReboundAbility(final ReboundAbility ability) {
public ReboundAbility(ReboundAbility ability) {
super(ability);
this.installReboundEffect = ability.installReboundEffect;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
//Something hit the stack from the hand, see if its a spell with this ability.
if (event.getType() == EventType.ZONE_CHANGE &&
((ZoneChangeEvent) event).getFromZone() == Zone.HAND &&
((ZoneChangeEvent) event).getToZone() == Zone.STACK) {
Card card = (Card) game.getObject(event.getTargetId());
if (card.getAbilities(game).contains(this)) {
this.installReboundEffect = true;
}
}
//Only 'install' the effect on a successfully cast spell otherwise the user
//may cancel before paying its costs and potentially having two copies rebound
if (event.getType() == EventType.SPELL_CAST && this.installReboundEffect) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && spell.getSourceId().equals(this.getSourceId())) {
addReboundEffectToSpellIfMissing(spell);
this.installReboundEffect = false;
}
}
return false;
}
@Override
public String getRule() {
return reboundText;
}
@Override
public ReboundAbility copy() {
return new ReboundAbility(this);
}
static public void addReboundEffectToSpellIfMissing(Spell spell) {
Effect reboundEffect = new ReboundEffect();
boolean found = false;
for (Effect effect : spell.getSpellAbility().getEffects()) {
if (effect instanceof ReboundEffect) {
found = true;
break;
}
}
if (!found) {
spell.getSpellAbility().addEffect(reboundEffect);
}
return new ReboundAbility(this);
}
}
/**
* Upon successful resolution of a spell with the {@link ReboundAbility} this effect
* will setup a {@link ReboundCastFromHandReplacementEffect replacement effect} which
* will only work once. It will then setup a {@link ReboundEffectCastFromExileDelayedTrigger delayed trigger}
* which will fire upon the controllers next upkeep.
*
* @author maurer.it_at_gmail.com
*/
class ReboundEffect extends OneShotEffect {
public ReboundEffect() {
super(Outcome.Benefit);
}
public ReboundEffect(ReboundEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Spell sourceSpell = (Spell) game.getObject(source.getId());
if (sourceSpell == null || !sourceSpell.isCopiedSpell()) {
MageObject mageObject = game.getObject(source.getSourceId());
if (mageObject instanceof StackObject) {
StackObject sourceCard = (StackObject) mageObject;
ReboundEffectCastFromExileDelayedTrigger trigger = new ReboundEffectCastFromExileDelayedTrigger(sourceCard.getSourceId(), sourceCard.getSourceId());
trigger.setControllerId(source.getControllerId());
game.addDelayedTriggeredAbility(trigger);
game.getContinuousEffects().addEffect(new ReboundCastFromHandReplacementEffect(source.getSourceId()), source);
return true;
}
}
return false;
}
@Override
public ReboundEffect copy() {
return new ReboundEffect(this);
}
}
/**
* This replacement effect needs to be created only when the spell with rebound
* successfully resolves. This will help to ensure that interactions with Leyline of the Void
* or any other such effect will not get in the way. This should take precendence
* in such interactions.
*
* @author maurer.it_at_gmail.com
*/
class ReboundCastFromHandReplacementEffect extends ReplacementEffectImpl {
private static String replacementText = "Rebound - If you cast {this} from your hand, exile it as it resolves";
private UUID cardId;
ReboundCastFromHandReplacementEffect(UUID cardId) {
super(Duration.OneUse, Outcome.Exile);
this.cardId = cardId;
this.staticText = replacementText;
ReboundCastFromHandReplacementEffect() {
super(Duration.WhileOnStack, Outcome.Benefit);
this.staticText = "Rebound <i>(If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)</i>";
}
ReboundCastFromHandReplacementEffect(ReboundCastFromHandReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public boolean apply(Game game, Ability source) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public ReboundCastFromHandReplacementEffect copy() {
return new ReboundCastFromHandReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Spell sourceSpell = (Spell) game.getObject(source.getId());
if (sourceSpell != null && sourceSpell.isCopiedSpell()) {
return false;
} else {
Card sourceCard = (Card) game.getObject(source.getSourceId());
Player player = game.getPlayer(sourceCard.getOwnerId());
if (player != null) {
player.moveCardToExileWithInfo(sourceCard, this.cardId, new StringBuilder(player.getName()).append(" Rebound").toString(), source.getSourceId(), game, Zone.HAND, true);
this.used = true;
return true;
}
}
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
@ -239,26 +105,50 @@ class ReboundCastFromHandReplacementEffect extends ReplacementEffectImpl {
public boolean applies(GameEvent event, Ability source, Game game) {
if (((ZoneChangeEvent) event).getFromZone() == Zone.STACK &&
((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD &&
source.getSourceId() == this.cardId) {
return true;
event.getSourceId() == source.getSourceId()) { // if countered the source.sourceId is different or null if it fizzles
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && spell.getFromZone().equals(Zone.HAND)) {
return true;
}
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Spell sourceSpell = game.getStack().getSpell(source.getSourceId());
if (sourceSpell != null && sourceSpell.isCopiedSpell()) {
return false;
} else {
Card sourceCard = game.getCard(source.getSourceId());
if (sourceCard != null) {
Player player = game.getPlayer(sourceCard.getOwnerId());
if (player != null) {
// Add the delayed triggered effect
ReboundEffectCastFromExileDelayedTrigger trigger = new ReboundEffectCastFromExileDelayedTrigger(source.getSourceId(), source.getSourceId());
trigger.setControllerId(source.getControllerId());
trigger.setSourceObject(source.getSourceObject(game), game);
game.addDelayedTriggeredAbility(trigger);
player.moveCardToExileWithInfo(sourceCard, sourceCard.getId(), player.getName() + " Rebound", source.getSourceId(), game, Zone.STACK, true);
return true;
}
}
}
return false;
}
@Override
public ReboundCastFromHandReplacementEffect copy() {
return new ReboundCastFromHandReplacementEffect(this);
}
}
/**
* This delayed trigger will tell the framework when its ok to kick off the
* {@link ReboundCastSpellFromExileEffect} by checking the step and the controller
* of this ability. When it is the controllers upkeep step the framework will then
* be told it can kick off the effect.
*
* @author maurer.it_at_gmail.com
*/
class ReboundEffectCastFromExileDelayedTrigger extends DelayedTriggeredAbility {
ReboundEffectCastFromExileDelayedTrigger(UUID cardId, UUID sourceId) {
super(new ReboundCastSpellFromExileEffect(cardId));
super(new ReboundCastSpellFromExileEffect());
setSourceId(sourceId);
this.optional = true;
}
@ -296,26 +186,23 @@ class ReboundEffectCastFromExileDelayedTrigger extends DelayedTriggeredAbility {
class ReboundCastSpellFromExileEffect extends OneShotEffect {
private static String castFromExileText = "Rebound - You may cast {this} from exile without paying its mana cost";
private final UUID cardId;
ReboundCastSpellFromExileEffect(UUID cardId) {
ReboundCastSpellFromExileEffect() {
super(Outcome.Benefit);
this.cardId = cardId;
staticText = castFromExileText;
}
ReboundCastSpellFromExileEffect(ReboundCastSpellFromExileEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public boolean apply(Game game, Ability source) {
ExileZone zone = game.getExile().getExileZone(this.cardId);
ExileZone zone = game.getExile().getExileZone(source.getSourceId());
if (zone == null || zone.isEmpty()) {
return false;
}
Card reboundCard = zone.get(this.cardId, game);
Card reboundCard = zone.get(source.getSourceId(), game);
Player player = game.getPlayer(source.getControllerId());
if (player != null && reboundCard != null) {
SpellAbility ability = reboundCard.getSpellAbility();

View file

@ -28,6 +28,7 @@
package mage.abilities.keyword;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
@ -73,6 +74,7 @@ public class StormAbility extends TriggeredAbilityImpl {
if (spell instanceof Spell) {
for (Effect effect : this.getEffects()) {
effect.setValue("StormSpell", spell);
effect.setValue("StormSpellRef", new MageObjectReference(spell.getId(), game));
}
return true;
}
@ -98,20 +100,23 @@ class StormEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Spell spell = (Spell) this.getValue("StormSpell");
if (spell != null) {
MageObjectReference spellRef = (MageObjectReference) this.getValue("StormSpellRef");
if (spellRef != null) {
CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher");
int stormCount = watcher.getSpellOrder(spell, game) - 1;
int stormCount = watcher.getSpellOrder(spellRef, game) - 1;
if (stormCount > 0) {
if (!game.isSimulation())
game.informPlayers("Storm: " + spell.getName() + " will be copied " + stormCount + " time" + (stormCount > 1 ?"s":""));
for (int i = 0; i < stormCount; i++) {
Spell copy = spell.copySpell();
copy.setControllerId(source.getControllerId());
copy.setCopiedSpell(true);
game.getStack().push(copy);
copy.chooseNewTargets(game, source.getControllerId());
Spell spell = (Spell) this.getValue("StormSpell");
if (spell != null) {
if (!game.isSimulation()) {
game.informPlayers("Storm: " + spell.getName() + " will be copied " + stormCount + " time" + (stormCount > 1 ?"s":""));
}
for (int i = 0; i < stormCount; i++) {
Spell copy = spell.copySpell();
copy.setControllerId(source.getControllerId());
copy.setCopiedSpell(true);
game.getStack().push(copy);
copy.chooseNewTargets(game, source.getControllerId());
}
}
}
return true;

View file

@ -461,6 +461,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
game.rememberLKI(mainCard.getId(), event.getFromZone(), this);
}
game.getStack().push(new Spell(this, ability.copy(), controllerId, event.getFromZone()));
updateZoneChangeCounter(game);
setZone(event.getToZone(), game);
game.fireEvent(event);
return game.getState().getZone(mainCard.getId()) == Zone.STACK;

View file

@ -99,7 +99,17 @@ public class GameEvent {
GAIN_LIFE, GAINED_LIFE,
LOSE_LIFE, LOST_LIFE,
PLAY_LAND, LAND_PLAYED,
CAST_SPELL, SPELL_CAST,
CAST_SPELL,
/* SPELL_CAST
targetId id of the spell that's cast
sourceId sourceId of the spell that's cast
playerId player that casts the spell
amount not used for this event
flag not used for this event
*/
SPELL_CAST,
ACTIVATE_ABILITY, ACTIVATED_ABILITY,
ADD_MANA, MANA_ADDED,
@ -108,7 +118,7 @@ public class GameEvent {
sourceId sourceId of the mana source
playerId controller of the ability the mana was paid for
amount not used for this event
flag indicates a special condition (e.g. TRUE if it's a colored mana from Cavern of Souls)
flag indicates a special condition
*/
MANA_PAYED,

View file

@ -101,8 +101,9 @@ public class SpellStack extends ArrayDeque<StackObject> {
this.remove(stackObject);
}
stackObject.counter(sourceId, game);
if (!game.isSimulation())
if (!game.isSimulation()) {
game.informPlayers(counteredObjectName + " is countered by " + sourceObject.getLogName());
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
} else if (!game.isSimulation()) {
game.informPlayers(counteredObjectName + " could not be countered by " + sourceObject.getLogName());

View file

@ -931,8 +931,9 @@ public abstract class PlayerImpl implements Player, Serializable {
GameEvent event = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId);
event.setZone(fromZone);
game.fireEvent(event);
if (!game.isSimulation())
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(name).append(spell.getActivatedMessage(game)).toString());
}
game.removeBookmark(bookmark);
resetStoredBookmark(game);
return true;

View file

@ -65,4 +65,9 @@ public class FixedTarget implements TargetPointer {
public UUID getTarget() {
return target;
}
public int getZoneChangeCounter() {
return zoneChangeCounter;
}
}

View file

@ -115,11 +115,11 @@ public class CastSpellLastTurnWatcher extends Watcher {
}
}
public int getSpellOrder(Spell spell, Game game) {
public int getSpellOrder(MageObjectReference spell, Game game) {
int index = 0;
for (MageObjectReference mor : spellsCastThisTurnInOrder) {
index++;
if (mor.refersTo(spell, game)) {
if (mor.equals(spell)) {
return index;
}
}