* Some rework of card moving after spell countering.

This commit is contained in:
LevelX2 2015-11-05 00:37:30 +01:00
parent 1139495fd7
commit 3de7ff6808
13 changed files with 152 additions and 239 deletions

View file

@ -28,27 +28,12 @@
package mage.sets.championsofkamigawa;
import java.util.UUID;
import mage.abilities.effects.common.CounterTargetWithReplacementEffect;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.Duration;
import mage.constants.PhaseStep;
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 mage.target.TargetSpell;
import mage.target.targetpointer.FixedTarget;
/**
*
@ -61,7 +46,7 @@ public class Hinder extends CardImpl {
this.expansionSetCode = "CHK";
// Counter target spell. If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard.
this.getSpellAbility().addEffect(new HinderEffect());
this.getSpellAbility().addEffect(new CounterTargetWithReplacementEffect(Zone.LIBRARY, true));
this.getSpellAbility().addTarget(new TargetSpell());
}
@ -74,119 +59,3 @@ public class Hinder extends CardImpl {
return new Hinder(this);
}
}
class HinderEffect extends OneShotEffect {
public HinderEffect() {
super(Outcome.Detriment);
this.staticText = "Counter target spell. If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard";
}
public HinderEffect(final HinderEffect effect) {
super(effect);
}
@Override
public HinderEffect copy() {
return new HinderEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
UUID objectId = source.getFirstTarget();
UUID sourceId = source.getSourceId();
// counter code from Spellstack
StackObject stackObject = game.getStack().getStackObject(objectId);
MageObject sourceObject = game.getObject(sourceId);
if (stackObject != null && sourceObject != null) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
if ( stackObject instanceof Spell ) {
game.rememberLKI(objectId, Zone.STACK, (Spell)stackObject);
}
// Hinder specific code
ReplacementEffectImpl effect = new HinderReplacementEffect();
effect.setTargetPointer(new FixedTarget(stackObject.getId()));
game.addEffect(effect, source);
// Hinder specific code end
game.informPlayers(new StringBuilder(stackObject.getName()).append(" is countered by ").append(sourceObject.getLogName()).toString());
game.getStack().remove(stackObject);
stackObject.counter(sourceId, game); // tries to move to graveyard
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
} else {
game.informPlayers(new StringBuilder(stackObject.getName()).append(" could not be countered by ").append(sourceObject.getLogName()).toString());
}
return true;
}
return false;
// counter code from Spellstack end
}
}
class HinderReplacementEffect extends ReplacementEffectImpl {
private PhaseStep phaseStep;
public HinderReplacementEffect() {
super(Duration.OneUse, Outcome.Benefit);
staticText = "If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard";
phaseStep = null;
}
public HinderReplacementEffect(final HinderReplacementEffect effect) {
super(effect);
phaseStep = effect.phaseStep;
}
@Override
public HinderReplacementEffect copy() {
return new HinderReplacementEffect(this);
}
@Override
public boolean isInactive(Ability source, Game game) {
if (!game.getPhase().getStep().getType().equals(phaseStep)) {
return true;
}
return super.isInactive(source, game);
}
@Override
public void init(Ability source, Game game) {
phaseStep = game.getPhase().getStep().getType();
super.init(source, game);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
MageObject targetObject = game.getObject(event.getTargetId());
if (targetObject instanceof Card) {
Card card = (Card) targetObject;
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
boolean top = player.chooseUse(Outcome.Neutral, "Put " + card.getName() + " on top of the library? Otherwise it will be put on the bottom.", source, game);
if (card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, top, event.getAppliedEffects())) {
game.informPlayers(player.getLogName() + " has put " + card.getName() + " on " + (top ? "top" : "the bottom") + " of the library.");
}
return true;
}
}
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == EventType.ZONE_CHANGE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (((ZoneChangeEvent)event).getToZone().equals(Zone.GRAVEYARD)) {
MageObject mageObject = game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK);
if (mageObject instanceof Spell) {
return ((Spell)mageObject).getSourceId().equals(event.getTargetId());
}
}
return false;
}
}

View file

@ -28,14 +28,14 @@
package mage.sets.fifthdawn;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.game.stack.StackObject;
@ -53,7 +53,6 @@ public class FoldIntoAEther extends CardImpl {
super(ownerId, 31, "Fold into AEther", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{2}{U}{U}");
this.expansionSetCode = "5DN";
// Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield.
this.getSpellAbility().addEffect(new FoldIntoAEtherEffect());
this.getSpellAbility().addTarget(new TargetSpell());
@ -87,19 +86,21 @@ class FoldIntoAEtherEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget());
Player player = null;
UUID targetId = getTargetPointer().getFirst(game, source);
StackObject stackObject = game.getStack().getStackObject(targetId);
Player spellController = null;
if (stackObject != null) {
player = game.getPlayer(stackObject.getControllerId());
spellController = game.getPlayer(stackObject.getControllerId());
}
if (game.getStack().counter(source.getFirstTarget(), source.getSourceId(), game)) {
if (game.getStack().counter(targetId, source.getSourceId(), game)) {
TargetCardInHand target = new TargetCardInHand(new FilterCreatureCard());
if (player != null
&& player.chooseUse(Outcome.Neutral, "Put a creature card from your hand in play?", source, game)
&& player.choose(Outcome.PutCreatureInPlay, target, source.getSourceId(), game)) {
if (spellController != null
&& target.canChoose(source.getSourceId(), source.getSourceId(), game)
&& spellController.chooseUse(Outcome.Neutral, "Put a creature card from your hand in play?", source, game)
&& spellController.choose(Outcome.PutCreatureInPlay, target, source.getSourceId(), game)) {
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
card.putOntoBattlefield(game, Zone.HAND, source.getSourceId(), player.getId());
spellController.moveCards(card, Zone.BATTLEFIELD, source, game);
}
}
return true;

View file

@ -28,7 +28,6 @@
package mage.sets.judgment;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousEffect;
@ -42,7 +41,6 @@ import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
@ -92,23 +90,18 @@ class SpelljackEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
UUID objectId = targetPointer.getFirst(game, source);
UUID sourceId = source.getSourceId();
StackObject stackObject = game.getStack().getStackObject(objectId);
if (stackObject != null && !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
game.rememberLKI(objectId, Zone.STACK, stackObject);
game.getStack().remove(stackObject);
if (!((Spell) stackObject).isCopiedSpell()) {
MageObject card = game.getObject(stackObject.getSourceId());
if (card instanceof Card) {
((Card) card).moveToZone(Zone.EXILED, sourceId, game, true);
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
UUID targetId = targetPointer.getFirst(game, source);
StackObject stackObject = game.getStack().getStackObject(targetId);
if (stackObject != null && game.getStack().counter(targetId, source.getSourceId(), game, Zone.EXILED, false, false)) {
Card card = ((Spell) stackObject).getCard();
if (card != null) {
ContinuousEffect effect = new SpelljackCastFromExileEffect();
effect.setTargetPointer(new FixedTarget(card.getId()));
effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId())));
game.addEffect(effect, source);
}
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
return true;
}
return false;
@ -150,8 +143,7 @@ class SpelljackCastFromExileEffect extends AsThoughEffectImpl {
Player player = game.getPlayer(affectedControllerId);
player.setCastSourceIdWithAlternateMana(sourceId, null);
return true;
}
else {
} else {
this.discard();
}
}

View file

@ -46,7 +46,6 @@ import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
@ -109,9 +108,8 @@ class KheruSpellsnatcherEffect extends OneShotEffect {
UUID sourceId = source.getSourceId();
StackObject stackObject = game.getStack().getStackObject(objectId);
if (stackObject != null && !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
game.rememberLKI(objectId, Zone.STACK, stackObject);
game.getStack().remove(stackObject);
if (stackObject != null
&& game.getStack().counter(targetPointer.getFirst(game, source), source.getSourceId(), game, Zone.EXILED, false, false)) {
if (!((Spell) stackObject).isCopiedSpell()) {
MageObject card = game.getObject(stackObject.getSourceId());
if (card instanceof Card) {
@ -121,7 +119,6 @@ class KheruSpellsnatcherEffect extends OneShotEffect {
game.addEffect(effect, source);
}
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
return true;
}
return false;
@ -163,8 +160,7 @@ class KheruSpellsnatcherCastFromExileEffect extends AsThoughEffectImpl {
Player player = game.getPlayer(affectedControllerId);
player.setCastSourceIdWithAlternateMana(sourceId, null);
return true;
}
else {
} else {
this.discard();
}
}

View file

@ -28,10 +28,8 @@
package mage.sets.visions;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
@ -41,9 +39,6 @@ import mage.filter.FilterSpell;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.TargetSpell;
@ -98,29 +93,10 @@ class DesertionEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
UUID objectId = source.getFirstTarget();
UUID sourceId = source.getSourceId();
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
StackObject stackObject = game.getStack().getStackObject(objectId);
if (stackObject != null && !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
if (stackObject instanceof Spell) {
game.rememberLKI(objectId, Zone.STACK, (Spell) stackObject);
game.getStack().remove(stackObject);
if (!((Spell) stackObject).isCopiedSpell() && filter.match(stackObject, source.getSourceId(), source.getControllerId(), game)) {
MageObject card = game.getObject(stackObject.getSourceId());
if (card instanceof Card) {
controller.moveCards((Card) card, Zone.BATTLEFIELD, source, game);
return game.getStack().counter(targetPointer.getFirst(game, source), source.getSourceId(), game, Zone.BATTLEFIELD, false, false);
}
} else {
stackObject.counter(sourceId, game);
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
return true;
}
}
}
return false;
}

View file

@ -0,0 +1,76 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package org.mage.test.cards.abilities.oneshot.counterspell;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class DesertionTest extends CardTestPlayerBase {
/**
* I cast Kozilek, Butcher of Truth from my hand and my opponent cast
* Desertion targeting Kozilek, Butcher of Truth. Desertion resolved but
* Kozilek, Butcher of Truth has disappeared (not in play for my opponent as
* expected and not in my command zone or hand or graveyard or library)
*
*/
@Test
public void testCounterKozilek() {
// When you cast Kozilek, Butcher of Truth, draw four cards.
// Annihilator 4 (Whenever this creature attacks, defending player sacrifices four permanents.)
// When Kozilek is put into a graveyard from anywhere, its owner shuffles his or her graveyard into his or her library.
addCard(Zone.HAND, playerA, "Kozilek, Butcher of Truth"); // {10}
addCard(Zone.BATTLEFIELD, playerA, "Island", 10);
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); // {10}
// Counter target spell. If an artifact or creature spell is countered this way, put that card onto the battlefield under your control instead of into its owner's graveyard.
addCard(Zone.HAND, playerB, "Desertion"); // {3}{U}{U}
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Kozilek, Butcher of Truth");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Desertion", "Kozilek, Butcher of Truth");
setStopAt(1, PhaseStep.CLEANUP);
execute();
assertGraveyardCount(playerB, "Desertion", 1);
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
assertGraveyardCount(playerA, "Kozilek, Butcher of Truth", 0);
assertHandCount(playerA, "Kozilek, Butcher of Truth", 0);
assertPermanentCount(playerB, "Kozilek, Butcher of Truth", 1);
}
}

View file

@ -985,6 +985,12 @@ public class ContinuousEffects implements Serializable {
}
layer = filterLayeredEffects(activeLayerEffects, Layer.PTChangingEffects_7);
for (ContinuousEffect effect : layer) {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a, ability, game);
}
}
for (ContinuousEffect effect : layer) {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {

View file

@ -27,17 +27,12 @@
*/
package mage.abilities.effects.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
/**
@ -77,20 +72,9 @@ public class CounterTargetWithReplacementEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
UUID objectId = source.getFirstTarget();
UUID sourceId = source.getSourceId();
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
StackObject stackObject = game.getStack().getStackObject(objectId);
if (stackObject != null && !game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
if (stackObject instanceof Spell) {
controller.moveCards((Card) stackObject, null, targetZone, source, game);
} else {
game.getStack().remove(stackObject);
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
return true;
}
return game.getStack().counter(targetPointer.getFirst(game, source), source.getSourceId(), game, targetZone, false, flag);
}
return false;
}

View file

@ -558,7 +558,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
break;
}
if (removed) {
game.rememberLKI(objectId, fromZone, lkiObject != null ? lkiObject : this);
game.rememberLKI(lkiObject != null ? lkiObject.getId() : objectId, fromZone, lkiObject != null ? lkiObject : this);
} else {
logger.warn("Couldn't find card in fromZone, card=" + getIdName() + ", fromZone=" + fromZone);
}

View file

@ -44,6 +44,7 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.BestowAbility;
import mage.abilities.keyword.MorphAbility;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.cards.SplitCard;
import mage.constants.CardType;
import mage.constants.Rarity;
@ -328,21 +329,32 @@ public class Spell extends StackObjImpl implements Card {
@Override
public void counter(UUID sourceId, Game game) {
this.counter(sourceId, game, true);
this.counter(sourceId, game, Zone.GRAVEYARD, false, true);
}
@Override
public void counter(UUID sourceId, Game game, boolean moveToGraveyard) {
public void counter(UUID sourceId, Game game, Zone zone, boolean owner, boolean top) {
this.countered = true;
if (!isCopiedSpell() && moveToGraveyard) {
Player player = game.getPlayer(getControllerId());
if (!isCopiedSpell()) {
Player player = game.getPlayer(game.getControllerId(sourceId));
if (player == null) {
player = game.getPlayer(getControllerId());
}
if (player != null) {
Ability counteringAbility = null;
MageObject counteringObject = game.getObject(sourceId);
if (counteringObject instanceof StackObject) {
counteringAbility = ((StackObject) counteringObject).getStackAbility();
}
player.moveCards(card, Zone.GRAVEYARD, counteringAbility, game);
if (zone.equals(Zone.LIBRARY)) {
if (top) {
player.putCardsOnTopOfLibrary(new CardsImpl(card), game, counteringAbility, false);
} else {
player.putCardsOnBottomOfLibrary(new CardsImpl(card), game, counteringAbility, false);
}
} else {
player.moveCards(card, zone, counteringAbility, game, false, false, owner, null);
}
}
}
}

View file

@ -82,6 +82,10 @@ public class SpellStack extends ArrayDeque<StackObject> {
}
public boolean counter(UUID objectId, UUID sourceId, Game game) {
return counter(objectId, sourceId, game, Zone.GRAVEYARD, false, true);
}
public boolean counter(UUID objectId, UUID sourceId, Game game, Zone zone, boolean owner, boolean onTop) {
// the counter logic is copied by some spells to handle replacement effects of the countered spell
// so if logic is changed here check those spells for needed changes too
// Concerned cards to check: Hinder, Spell Crumple
@ -101,12 +105,7 @@ public class SpellStack extends ArrayDeque<StackObject> {
counteredObjectName = "Ability (" + stackObject.getStackAbility().getRule(targetSourceName) + ") of " + targetSourceName;
}
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) {
if (stackObject instanceof Spell) {
game.rememberLKI(objectId, Zone.STACK, (Spell) stackObject);
} else {
this.remove(stackObject);
}
stackObject.counter(sourceId, game);
stackObject.counter(sourceId, game, zone, owner, onTop);
if (!game.isSimulation()) {
game.informPlayers(counteredObjectName + " is countered by " + sourceObject.getLogName());
}

View file

@ -122,11 +122,12 @@ public class StackAbility extends StackObjImpl implements Ability {
@Override
public void counter(UUID sourceId, Game game) {
this.counter(sourceId, game, true);
// zone, owner, top ignored
this.counter(sourceId, game, Zone.GRAVEYARD, true, true);
}
@Override
public void counter(UUID sourceId, Game game, boolean moveToGraveyard) {
public void counter(UUID sourceId, Game game, Zone zone, boolean owner, boolean top) {
//20100716 - 603.8
if (ability instanceof StateTriggeredAbility) {
((StateTriggeredAbility) ability).counter(game);

View file

@ -30,6 +30,7 @@ package mage.game.stack;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Controllable;
import mage.game.Game;
@ -42,7 +43,7 @@ public interface StackObject extends MageObject, Controllable {
void counter(UUID sourceId, Game game);
void counter(UUID sourceId, Game game, boolean moveToGraveyard);
void counter(UUID sourceId, Game game, Zone zone, boolean owner, boolean top);
Ability getStackAbility();