mirror of
https://github.com/correl/mage.git
synced 2024-11-15 11:09:30 +00:00
* 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:
parent
4b6993f398
commit
830765996f
15 changed files with 276 additions and 264 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -76,9 +79,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,4 +281,4 @@ class NarsetTranscendentCantCastEffect extends ContinuousRuleModifyingEffectImpl
|
|||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
|
|
|
@ -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 (affectedControllerId.equals(source.getControllerId())) {
|
||||
return true;
|
||||
if (objectId.equals(((FixedTarget)getTargetPointer()).getTarget())
|
||||
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget)getTargetPointer()).getZoneChangeCounter() +1) {
|
||||
|
||||
if (affectedControllerId.equals(source.getControllerId())) {
|
||||
// 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
|
||||
|
|
|
@ -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);
|
||||
|
@ -27,9 +34,91 @@ public class ReboundTest extends CardTestPlayerBase{
|
|||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
//check exile and graveyard
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -65,4 +65,9 @@ public class FixedTarget implements TargetPointer {
|
|||
public UUID getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public int getZoneChangeCounter() {
|
||||
return zoneChangeCounter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue