* Exploit - Fixed that Exploit also triggered if the creature with Exploit left the battlefield before the first ability of Exploit resolved.

This commit is contained in:
LevelX2 2015-04-07 00:27:07 +02:00
parent a56cccebf9
commit f62d3ac227
17 changed files with 231 additions and 44 deletions

View file

@ -1,7 +1,6 @@
woogerworks (North America/USA) :xmage.woogerworks.com:17171 woogerworks (North America/USA) :xmage.woogerworks.com:17171
Xmage.de 1 (Europe/Germany) :xmage.de:17171 XMage.de 1 (Europe/Germany) fast :xmage.de:17171
XMage.info 1 (Europe/France) :176.31.186.181:17171 XMage.info 2 (Europe/France) slow :176.31.186.181:17000
XMage.info 2 (Europe/France) :176.31.186.181:17000
IceMage (Europe/Netherlands) :ring0.cc:17171 IceMage (Europe/Netherlands) :ring0.cc:17171
Seedds Server (Asia) :115.29.203.80:17171 Seedds Server (Asia) :115.29.203.80:17171
localhost -> connect to your local server (must be started):localhost:17171 localhost -> connect to your local server (must be started):localhost:17171

View file

@ -56,8 +56,6 @@ public class ScoutsWarning extends CardImpl {
super(ownerId, 16, "Scout's Warning", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{W}"); super(ownerId, 16, "Scout's Warning", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{W}");
this.expansionSetCode = "FUT"; this.expansionSetCode = "FUT";
this.color.setWhite(true);
// The next creature card you play this turn can be played as though it had flash. // The next creature card you play this turn can be played as though it had flash.
this.getSpellAbility().addEffect(new ScoutsWarningAsThoughEffect()); this.getSpellAbility().addEffect(new ScoutsWarningAsThoughEffect());
this.getSpellAbility().addWatcher(new ScoutsWarningWatcher()); this.getSpellAbility().addWatcher(new ScoutsWarningWatcher());
@ -117,7 +115,7 @@ class ScoutsWarningAsThoughEffect extends AsThoughEffectImpl {
if (watcher.isScoutsWarningSpellActive(source.getSourceId(), zoneChangeCounter)) { if (watcher.isScoutsWarningSpellActive(source.getSourceId(), zoneChangeCounter)) {
Card card = game.getCard(sourceId); Card card = game.getCard(sourceId);
if (card != null && card.getCardType().contains(CardType.CREATURE) && source.getControllerId().equals(affectedControllerId)) { if (card != null && card.getCardType().contains(CardType.CREATURE) && source.getControllerId().equals(affectedControllerId)) {
return card.getSpellAbility().isInUseableZone(game, card, false); return card.getSpellAbility().isInUseableZone(game, card, null);
} }
} }
return false; return false;

View file

@ -118,7 +118,7 @@ class QuickenAsThoughEffect extends AsThoughEffectImpl {
if (quickenWatcher.isQuickenSpellActive(source.getSourceId(), zoneChangeCounter)) { if (quickenWatcher.isQuickenSpellActive(source.getSourceId(), zoneChangeCounter)) {
Card card = game.getCard(sourceId); Card card = game.getCard(sourceId);
if (card != null && card.getCardType().contains(CardType.SORCERY) && source.getControllerId().equals(affectedControllerId)) { if (card != null && card.getCardType().contains(CardType.SORCERY) && source.getControllerId().equals(affectedControllerId)) {
return card.getSpellAbility().isInUseableZone(game, card, false); return card.getSpellAbility().isInUseableZone(game, card, null);
} }
} }
return false; return false;

View file

@ -132,7 +132,7 @@ class SavageSummoningAsThoughEffect extends AsThoughEffectImpl {
if (watcher.isSavageSummoningSpellActive()) { if (watcher.isSavageSummoningSpellActive()) {
Card card = game.getCard(sourceId); Card card = game.getCard(sourceId);
if (card != null && card.getCardType().contains(CardType.CREATURE) && card.getOwnerId().equals(source.getControllerId())) { if (card != null && card.getCardType().contains(CardType.CREATURE) && card.getOwnerId().equals(source.getControllerId())) {
return card.getSpellAbility().isInUseableZone(game, card, false); return card.getSpellAbility().isInUseableZone(game, card, null);
} }
} }
return false; return false;

View file

@ -0,0 +1,140 @@
/*
* 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.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class ExploitTest extends CardTestPlayerBase {
/**
* 702.109. Exploit
*
* 702.109a Exploit is a triggered ability. Exploit means When this creature enters the battlefield, you may sacrifice a creature.
*
* 702.109b A creature with exploit exploits a creature when the controller of the exploit ability sacrifices a creature as that ability resolves.
*
* You choose whether to sacrifice a creature and which creature to sacrifice as the exploit ability resolves.
* You can sacrifice the creature with exploit if its still on the battlefield. This will cause its other ability to trigger.
* You cant sacrifice more than one creature to any one exploit ability.
*
*/
@Test
public void testNormalUse() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5);
// Exploit (When this creature enters the battlefield, you may sacrifice a creature.)
// When Silumgar Butcher exploits a creature, target creature gets -3/-3 until end of turn.
addCard(Zone.HAND, playerA, "Silumgar Butcher");
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerB, "Thundering Giant"); // 4/3
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silumgar Butcher");
setChoice(playerA, "Yes");
addTarget(playerA, "Silvercoat Lion"); // sacrifice to Exploit
addTarget(playerA, "Thundering Giant"); // Target for the -3/-3
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Silumgar Butcher", 1);
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
assertGraveyardCount(playerB, "Thundering Giant", 1);
}
/**
* Test that the Exploit ability won't trigger if the creature with
* exploit left the battlefiled before the Enters the battlefield
* triggered ability resolves.
*
*/
@Test
public void testExploitTriggerWontGo() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5);
// Exploit (When this creature enters the battlefield, you may sacrifice a creature.)
// When Silumgar Butcher exploits a creature, target creature gets -3/-3 until end of turn.
addCard(Zone.HAND, playerA, "Silumgar Butcher");
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
addCard(Zone.HAND, playerB, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerB, "Thundering Giant"); // 4/3
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silumgar Butcher");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silumgar Butcher");
setChoice(playerA, "Yes");
addTarget(playerA, "Silvercoat Lion"); // sacrifice to Exploit
addTarget(playerA, "Thundering Giant"); // Target for the -3/-3
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertGraveyardCount(playerA, "Silumgar Butcher", 1);
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
assertPermanentCount(playerB, "Thundering Giant", 1);
}
/**
* Test that the Exploit ability won't trigger if the creature with
* exploit left the battlefiled before the Enters the battlefield
* triggered ability resolves.
*
*/
@Test
public void testSacrificeCreatureWithExploit() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
// Exploit (When this creature enters the battlefield, you may sacrifice a creature.)
// When Qarsi Sadist exploits a creature, target opponent loses 2 life and you gain 2 life.
addCard(Zone.HAND, playerA, "Qarsi Sadist");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Qarsi Sadist");
setChoice(playerA, "Yes");
addTarget(playerA, "Qarsi Sadist"); // sacrifice to Exploit
addTarget(playerA, playerB); // Target for lose life
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Qarsi Sadist", 1);
assertLife(playerA, 22);
assertLife(playerB, 18);
}
}

View file

@ -47,6 +47,7 @@ import mage.constants.EffectType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Controllable; import mage.game.Controllable;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.Targets; import mage.target.Targets;
@ -382,10 +383,10 @@ public interface Ability extends Controllable, Serializable {
* *
* @param game * @param game
* @param source * @param source
* @param checkLKI * @param event
* @return * @return
*/ */
boolean isInUseableZone(Game game, MageObject source, boolean checkLKI); boolean isInUseableZone(Game game, MageObject source, GameEvent event);
/** /**
* Returns true if this ability has to be shown as topmost of all the rules of the object * Returns true if this ability has to be shown as topmost of all the rules of the object

View file

@ -67,6 +67,7 @@ import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.command.Emblem; import mage.game.command.Emblem;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ManaEvent; import mage.game.events.ManaEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentCard;
@ -864,11 +865,10 @@ public abstract class AbilityImpl implements Ability {
* *
* @param game * @param game
* @param source * @param source
* @param checkShortLivingLKI if the object was in the needed zone as the effect that's currently applied started, the check returns true
* @return * @return
*/ */
@Override @Override
public boolean isInUseableZone(Game game, MageObject source, boolean checkShortLivingLKI) { public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
if (zone.equals(Zone.COMMAND)) { if (zone.equals(Zone.COMMAND)) {
if (this.getSourceId() == null) { // commander effects if (this.getSourceId() == null) { // commander effects
return true; return true;
@ -880,17 +880,6 @@ public abstract class AbilityImpl implements Ability {
} }
} }
// try LKI first (was the object with the id in the needed zone before)
if (checkShortLivingLKI) {
if (game.getShortLivingLKI(getSourceId(), zone)) {
return true;
}
} else {
if (game.getLastKnownInformation(getSourceId(), zone) != null) {
return true;
}
}
MageObject object; MageObject object;
UUID parameterSourceId; UUID parameterSourceId;
// for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects

View file

@ -28,9 +28,13 @@
package mage.abilities; package mage.abilities;
import mage.MageObject;
import mage.abilities.effects.Effect;
import mage.constants.AbilityType; import mage.constants.AbilityType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.abilities.effects.Effect; import mage.game.Game;
import mage.game.events.GameEvent;
/** /**
* *
@ -49,6 +53,14 @@ public abstract class StaticAbility extends AbilityImpl {
} }
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
if (game.getShortLivingLKI(getSourceId(), zone)) {
return true;
}
return super.isInUseableZone(game, source, event);
}
public StaticAbility(StaticAbility ability) { public StaticAbility(StaticAbility ability) {
super(ability); super(ability);
} }

View file

@ -72,7 +72,7 @@ public class TriggeredAbilities extends ConcurrentHashMap<String, TriggeredAbili
} }
// for effects like when leaves battlefield or destroyed use ShortLKI to check if permanent was in the correct zone before (e.g. Oblivion Ring or Karmic Justice) // for effects like when leaves battlefield or destroyed use ShortLKI to check if permanent was in the correct zone before (e.g. Oblivion Ring or Karmic Justice)
MageObject object = game.getObject(ability.getSourceId()); MageObject object = game.getObject(ability.getSourceId());
if (ability.isInUseableZone(game, object, false)) { if (ability.isInUseableZone(game, object, event)) {
if (!game.getContinuousEffects().preventedByRuleModification(event, ability, game, false)) { if (!game.getContinuousEffects().preventedByRuleModification(event, ability, game, false)) {
if (object != null) { if (object != null) {
boolean controllerSet = false; boolean controllerSet = false;

View file

@ -35,6 +35,7 @@ import mage.constants.AbilityType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player; import mage.players.Player;
/** /**
@ -158,4 +159,36 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
return sb.toString(); return sb.toString();
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
/**
* 603.6. Trigger events that involve objects changing zones are called zone-change triggers.
* Many abilities with zone-change triggers attempt to do something to that object after it
* changes zones. During resolution, these abilities look for the object in the zone that
* it moved to. If the object is unable to be found in the zone it went to, the part of the
* ability attempting to do something to the object will fail to do anything. The ability could
* be unable to find the object because the object never entered the specified zone, because it
* left the zone before the ability resolved, or because it is in a zone that is hidden from
* a player, such as a library or an opponents hand. (This rule applies even if the object
* leaves the zone and returns again before the ability resolves.) The most common zone-change
* triggers are enters-the-battlefield triggers and leaves-the-battlefield triggers.
*/
if (event != null) {
switch (event.getType()) {
case ZONE_CHANGE:
if (source == null && ((ZoneChangeEvent)event).getTarget() != null) {
source = ((ZoneChangeEvent)event).getTarget();
}
case DESTROYED_PERMANENT:
// case LOST_CONTROL:
case PHASED_OUT:
case PHASED_IN:
if (game.getLastKnownInformation(getSourceId(), zone) != null) {
return true;
}
}
}
return super.isInUseableZone(game, source, event);
}
} }

View file

@ -34,6 +34,7 @@ import mage.abilities.keyword.CyclingAbility;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
/** /**
@ -55,7 +56,7 @@ public class CycleTriggeredAbility extends ZoneChangeTriggeredAbility {
} }
@Override @Override
public boolean isInUseableZone(Game game, MageObject source, boolean checkLKI) { public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return true; return true;
} }

View file

@ -55,7 +55,7 @@ public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility {
} }
@Override @Override
public boolean isInUseableZone(Game game, MageObject source, boolean checkLKI) { public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
// check it was previously on battlefield // check it was previously on battlefield
Permanent before = (Permanent) game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD); Permanent before = (Permanent) game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD);
// check now it is in graveyard // check now it is in graveyard

View file

@ -27,6 +27,7 @@
*/ */
package mage.abilities.common; package mage.abilities.common;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer; import mage.constants.SetTargetPointer;
@ -48,7 +49,7 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl {
} }
public ExploitCreatureTriggeredAbility(Effect effect, boolean optional, SetTargetPointer setTargetPointer) { public ExploitCreatureTriggeredAbility(Effect effect, boolean optional, SetTargetPointer setTargetPointer) {
super(Zone.ALL, effect, optional); super(Zone.BATTLEFIELD, effect, optional);
this.setTargetPointer = setTargetPointer; this.setTargetPointer = setTargetPointer;
} }
@ -62,11 +63,20 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl {
return new ExploitCreatureTriggeredAbility(this); return new ExploitCreatureTriggeredAbility(this);
} }
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.EXPLOITED_CREATURE; return event.getType() == GameEvent.EventType.EXPLOITED_CREATURE;
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
if (event.getTargetId().equals(getSourceId()) && event.getSourceId().equals(getSourceId())) {
return true; // if Exploits creature sacrifices itself, exploit triggers
}
return super.isInUseableZone(game, source, event);
}
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(getSourceId())) { if (event.getSourceId().equals(getSourceId())) {

View file

@ -214,7 +214,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId()); HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability: abilities) { for (Ability ability: abilities) {
// If e.g. triggerd abilities (non static) created the effect, the ability must not be in usable zone (e.g. Unearth giving Haste effect) // If e.g. triggerd abilities (non static) created the effect, the ability must not be in usable zone (e.g. Unearth giving Haste effect)
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, true)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
layerEffects.add(effect); layerEffects.add(effect);
break; break;
} }
@ -268,7 +268,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> abilities = requirementEffects.getAbility(effect.getId()); HashSet<Ability> abilities = requirementEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<>(); HashSet<Ability> applicableAbilities = new HashSet<>();
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, ability instanceof MageSingleton ? permanent : null, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, ability instanceof MageSingleton ? permanent : null, null)) {
if (effect.applies(permanent, ability, game)) { if (effect.applies(permanent, ability, game)) {
applicableAbilities.add(ability); applicableAbilities.add(ability);
} }
@ -287,7 +287,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> abilities = restrictionEffects.getAbility(effect.getId()); HashSet<Ability> abilities = restrictionEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<>(); HashSet<Ability> applicableAbilities = new HashSet<>();
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, ability instanceof MageSingleton ? permanent : null, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, ability instanceof MageSingleton ? permanent : null, null)) {
if (effect.applies(permanent, ability, game)) { if (effect.applies(permanent, ability, game)) {
applicableAbilities.add(ability); applicableAbilities.add(ability);
} }
@ -306,7 +306,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> abilities = restrictionUntapNotMoreThanEffects.getAbility(effect.getId()); HashSet<Ability> abilities = restrictionUntapNotMoreThanEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<>(); HashSet<Ability> applicableAbilities = new HashSet<>();
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
if (effect.applies(player, ability, game)) { if (effect.applies(player, ability, game)) {
applicableAbilities.add(ability); applicableAbilities.add(ability);
} }
@ -348,7 +348,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> applicableAbilities = new HashSet<>(); HashSet<Ability> applicableAbilities = new HashSet<>();
for (Ability ability : abilities) { for (Ability ability : abilities) {
// for replacment effects of static abilities do not use LKI to check if to apply // for replacment effects of static abilities do not use LKI to check if to apply
if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, true)) { if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) { if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) {
if (checkAbilityStillExists(ability, effect, event, game)) { // TODO: This is really needed??? if (checkAbilityStillExists(ability, effect, event, game)) { // TODO: This is really needed???
@ -376,7 +376,7 @@ public class ContinuousEffects implements Serializable {
HashSet<Ability> abilities = preventionEffects.getAbility(effect.getId()); HashSet<Ability> abilities = preventionEffects.getAbility(effect.getId());
HashSet<Ability> applicableAbilities = new HashSet<>(); HashSet<Ability> applicableAbilities = new HashSet<>();
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, true)) { if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (effect.applies(event, ability, game)) { if (effect.applies(event, ability, game)) {
applicableAbilities.add(ability); applicableAbilities.add(ability);
@ -443,7 +443,7 @@ public class ContinuousEffects implements Serializable {
for (CostModificationEffect effect: costModificationEffects) { for (CostModificationEffect effect: costModificationEffects) {
HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId()); HashSet<Ability> abilities = costModificationEffects.getAbility(effect.getId());
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
costEffects.add(effect); costEffects.add(effect);
break; break;
@ -466,7 +466,7 @@ public class ContinuousEffects implements Serializable {
for (SpliceCardEffect effect: spliceCardEffects) { for (SpliceCardEffect effect: spliceCardEffects) {
HashSet<Ability> abilities = spliceCardEffects.getAbility(effect.getId()); HashSet<Ability> abilities = spliceCardEffects.getAbility(effect.getId());
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (ability.getControllerId().equals(playerId) && (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false))) { if (ability.getControllerId().equals(playerId) && (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null))) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
spliceEffects.add(effect); spliceEffects.add(effect);
break; break;
@ -516,7 +516,7 @@ public class ContinuousEffects implements Serializable {
for (AsThoughEffect effect: asThoughEffectsMap.get(type)) { for (AsThoughEffect effect: asThoughEffectsMap.get(type)) {
HashSet<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId()); HashSet<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, null)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
asThoughEffectsList.add(effect); asThoughEffectsList.add(effect);
break; break;
@ -660,7 +660,7 @@ public class ContinuousEffects implements Serializable {
continue; continue;
} }
for (Ability sourceAbility : continuousRuleModifyingEffects.getAbility(effect.getId())) { for (Ability sourceAbility : continuousRuleModifyingEffects.getAbility(effect.getId())) {
if (!(sourceAbility instanceof StaticAbility) || sourceAbility.isInUseableZone(game, null, true)) { if (!(sourceAbility instanceof StaticAbility) || sourceAbility.isInUseableZone(game, null, event)) {
if (checkAbilityStillExists(sourceAbility, effect, event, game)) { if (checkAbilityStillExists(sourceAbility, effect, event, game)) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
effect.setValue("targetAbility", targetAbility); effect.setValue("targetAbility", targetAbility);

View file

@ -40,6 +40,7 @@ import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent; import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
@ -158,7 +159,7 @@ class HauntExileAbility extends ZoneChangeTriggeredAbility {
} }
@Override @Override
public boolean isInUseableZone(Game game, MageObject source, boolean checkLKI) { public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
boolean fromOK = true; boolean fromOK = true;
if (creatureHaunt) { if (creatureHaunt) {
// check it was previously on battlefield // check it was previously on battlefield
@ -209,9 +210,10 @@ class HauntEffect extends OneShotEffect {
game.getState().setValue(key, new FixedTarget(targetPointer.getFirst(game, source))); game.getState().setValue(key, new FixedTarget(targetPointer.getFirst(game, source)));
card.addInfo("hauntinfo", new StringBuilder("Haunting ").append(hauntedCreature.getLogName()).toString(), game); card.addInfo("hauntinfo", new StringBuilder("Haunting ").append(hauntedCreature.getLogName()).toString(), game);
hauntedCreature.addInfo("hauntinfo", new StringBuilder("Haunted by ").append(card.getLogName()).toString(), game); hauntedCreature.addInfo("hauntinfo", new StringBuilder("Haunted by ").append(card.getLogName()).toString(), game);
if (!game.isSimulation()) if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(card.getName()).append(" haunting ").append(hauntedCreature.getLogName()).toString()); game.informPlayers(new StringBuilder(card.getName()).append(" haunting ").append(hauntedCreature.getLogName()).toString());
} }
}
return true; return true;
} }
} }

View file

@ -56,6 +56,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.AbilityWord; import mage.constants.AbilityWord;
import mage.game.events.GameEvent;
import mage.players.Player; import mage.players.Player;
import mage.watchers.Watcher; import mage.watchers.Watcher;
@ -100,8 +101,9 @@ public class StackAbility implements StackObject, Ability {
if (ability.getTargets().stillLegal(ability, game)) { if (ability.getTargets().stillLegal(ability, game)) {
return ability.resolve(game); return ability.resolve(game);
} }
if (!game.isSimulation()) if (!game.isSimulation()) {
game.informPlayers("Ability has been fizzled: " + getRule()); game.informPlayers("Ability has been fizzled: " + getRule());
}
counter(null, game); counter(null, game);
return false; return false;
} }
@ -400,7 +402,7 @@ public class StackAbility implements StackObject, Ability {
} }
@Override @Override
public boolean isInUseableZone(Game game, MageObject source, boolean checkLKI) { public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
throw new UnsupportedOperationException("Not supported yet."); throw new UnsupportedOperationException("Not supported yet.");
} }

View file

@ -189,10 +189,10 @@ public class TraceUtil {
for (RestrictionEffect effect: restrictionEffects) { for (RestrictionEffect effect: restrictionEffects) {
log.error(uuid+" effect=" + effect.toString() + " id=" + effect.getId()); log.error(uuid+" effect=" + effect.toString() + " id=" + effect.getId());
for (Ability ability : restrictionEffects.getAbility(effect.getId())) { for (Ability ability : restrictionEffects.getAbility(effect.getId())) {
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, false)) { if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, permanent, null)) {
log.error(uuid+" ability=" + ability + ", applies_to_attacker=" + effect.applies(permanent, ability, game)); log.error(uuid+" ability=" + ability + ", applies_to_attacker=" + effect.applies(permanent, ability, game));
} else { } else {
boolean usable = ability.isInUseableZone(game, permanent, false); boolean usable = ability.isInUseableZone(game, permanent, null);
log.error(uuid+" instanceof StaticAbility: " + (ability instanceof StaticAbility) + ", ability=" + ability); log.error(uuid+" instanceof StaticAbility: " + (ability instanceof StaticAbility) + ", ability=" + ability);
log.error(uuid+" usable zone: " + usable + ", ability=" + ability); log.error(uuid+" usable zone: " + usable + ", ability=" + ability);
if (!usable) { if (!usable) {