diff --git a/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java b/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java index 3d855dbaad6..b20ba06b561 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/Tromokratis.java @@ -114,7 +114,7 @@ class CantBeBlockedUnlessAllEffect extends RestrictionEffect { // check if all creatures of defender are able to block this permanent // permanent.canBlock() can't be used because causing recursive call for (Permanent permanent: game.getBattlefield().getAllActivePermanents(filter, blocker.getControllerId(), game)) { - if (permanent.isTapped() && !game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, blocker.getControllerId(), game)) { + if (permanent.isTapped() && !game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, source, blocker.getControllerId(), game)) { return false; } // check blocker restrictions diff --git a/Mage.Sets/src/mage/sets/innistrad/RooftopStorm.java b/Mage.Sets/src/mage/sets/innistrad/RooftopStorm.java index 1a061928fc7..5ebe0a57873 100644 --- a/Mage.Sets/src/mage/sets/innistrad/RooftopStorm.java +++ b/Mage.Sets/src/mage/sets/innistrad/RooftopStorm.java @@ -42,6 +42,7 @@ import mage.players.Player; import mage.util.CardUtil; import java.util.UUID; +import mage.abilities.ActivatedAbility; /** * @@ -100,9 +101,10 @@ class RooftopStormCostReductionEffect extends CostModificationEffectImpl { Card sourceCard = game.getCard(spell.getSourceId()); if (sourceCard != null && sourceCard.hasSubtype("Zombie")) { Player player = game.getPlayer(spell.getControllerId()); - if (player != null && player.chooseUse(Outcome.Benefit, "Pay {0} rather than pay the mana cost for Zombie creature", game)) { + if (player != null && + (CardUtil.isCheckPlayableMode(spell) || player.chooseUse(Outcome.Benefit, "Pay {0} rather than pay the mana cost for Zombie creature", game))) { spell.getManaCostsToPay().clear(); - spell.getManaCostsToPay().addAll(new ManaCostsImpl("{0}")); + spell.getManaCostsToPay().addAll(new ManaCostsImpl<>("{0}")); return true; } } diff --git a/Mage.Sets/src/mage/sets/magic2013/Omniscience.java b/Mage.Sets/src/mage/sets/magic2013/Omniscience.java index bb88a9303f4..6a9ad57922c 100644 --- a/Mage.Sets/src/mage/sets/magic2013/Omniscience.java +++ b/Mage.Sets/src/mage/sets/magic2013/Omniscience.java @@ -42,6 +42,7 @@ import mage.game.stack.StackObject; import mage.players.Player; import java.util.UUID; +import mage.util.CardUtil; /** * @@ -99,7 +100,8 @@ class OmniscienceEffect extends CostModificationEffectImpl { && !sourceCard.getCardType().contains(CardType.LAND)) { Player player = game.getPlayer(source.getControllerId()); String message = "Cast " + sourceCard.getName() + " without paying its mana costs?"; - if (player != null && player.chooseUse(outcome, message, game)) { + if (player != null && + (CardUtil.isCheckPlayableMode(abilityToModify) || player.chooseUse(outcome, message, game))) { return true; } } diff --git a/Mage.Sets/src/mage/sets/tempest/Aluren.java b/Mage.Sets/src/mage/sets/tempest/Aluren.java index a976e87a39f..2e39029a9df 100644 --- a/Mage.Sets/src/mage/sets/tempest/Aluren.java +++ b/Mage.Sets/src/mage/sets/tempest/Aluren.java @@ -49,6 +49,7 @@ import mage.game.Game; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; +import mage.util.CardUtil; /** * @@ -113,7 +114,8 @@ class AlurenEffect extends CostModificationEffectImpl { if (sourceCard != null && sourceCard.getCardType().contains(CardType.CREATURE) && sourceCard.getManaCost().convertedManaCost() <= 3) { Player player = game.getPlayer(stackObject.getControllerId()); String message = "Cast " + sourceCard.getName() + " without paying its mana costs?"; - if (player != null && player.chooseUse(outcome, message, game)) { + if (player != null && + (CardUtil.isCheckPlayableMode(abilityToModify) || player.chooseUse(outcome, message, game))) { return true; } } diff --git a/Mage/src/mage/abilities/ActivatedAbility.java b/Mage/src/mage/abilities/ActivatedAbility.java index 84e0150c70a..b8908a85873 100644 --- a/Mage/src/mage/abilities/ActivatedAbility.java +++ b/Mage/src/mage/abilities/ActivatedAbility.java @@ -38,4 +38,20 @@ import mage.game.Game; public interface ActivatedAbility extends Ability { boolean canActivate(UUID playerId, Game game); + + /** + * Creates a fresh copy of this activated ability. + * + * @return A new copy of this ability. + */ + @Override + ActivatedAbility copy(); + + /** + * Set a flag to know, that the ability is only created adn used to check + * what's playbable for the player. + */ + void setCheckPlayableMode(); + + boolean isCheckPlayableMode(); } diff --git a/Mage/src/mage/abilities/ActivatedAbilityImpl.java b/Mage/src/mage/abilities/ActivatedAbilityImpl.java index 8f799183034..d3c14ecc299 100644 --- a/Mage/src/mage/abilities/ActivatedAbilityImpl.java +++ b/Mage/src/mage/abilities/ActivatedAbilityImpl.java @@ -53,9 +53,11 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa protected TimingRule timing = TimingRule.INSTANT; protected TargetController mayActivate = TargetController.YOU; protected UUID activatorId; + protected boolean checkPlayableMode; protected ActivatedAbilityImpl(AbilityType abilityType, Zone zone) { super(abilityType, zone); + this.checkPlayableMode = false; } public ActivatedAbilityImpl(ActivatedAbilityImpl ability) { @@ -63,6 +65,7 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa timing = ability.timing; mayActivate = ability.mayActivate; activatorId = ability.activatorId; + checkPlayableMode = ability.checkPlayableMode; } public ActivatedAbilityImpl(Zone zone) { @@ -216,5 +219,15 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa public void setTiming(TimingRule timing) { this.timing = timing; } - + + @Override + public void setCheckPlayableMode() { + checkPlayableMode = true; + } + + @Override + public boolean isCheckPlayableMode() { + return checkPlayableMode; + } + } diff --git a/Mage/src/mage/abilities/SpellAbility.java b/Mage/src/mage/abilities/SpellAbility.java index 028b4b7cb34..a33ec526f8a 100644 --- a/Mage/src/mage/abilities/SpellAbility.java +++ b/Mage/src/mage/abilities/SpellAbility.java @@ -84,8 +84,8 @@ public class SpellAbility extends ActivatedAbilityImpl { @Override public boolean canActivate(UUID playerId, Game game) { - if (this.spellCanBeActivatedRegularlyNow(playerId, game) || - game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, playerId, game)) { + if (game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, this, playerId, game) // check this first to allow Offering in main phase + || this.spellCanBeActivatedRegularlyNow(playerId, game)) { if (spellAbilityType.equals(SpellAbilityType.SPLIT)) { return false; } diff --git a/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java b/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java index 6f4b788a388..b6b73034a93 100644 --- a/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java +++ b/Mage/src/mage/abilities/costs/AlternativeCost2Impl.java @@ -57,7 +57,7 @@ public class AlternativeCost2Impl > extends Co if (reminderText != null) { this.reminderText = new StringBuilder("").append(reminderText).append("").toString(); } - this.add((Cost) cost); + this.add(cost); } public AlternativeCost2Impl(final AlternativeCost2Impl cost) { diff --git a/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java index 8c6159244d2..3d9f61cfde2 100644 --- a/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -28,8 +28,6 @@ package mage.abilities.costs; import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; diff --git a/Mage/src/mage/abilities/effects/AsThoughEffect.java b/Mage/src/mage/abilities/effects/AsThoughEffect.java index 45c846f0abf..a8fe74f8c68 100644 --- a/Mage/src/mage/abilities/effects/AsThoughEffect.java +++ b/Mage/src/mage/abilities/effects/AsThoughEffect.java @@ -39,6 +39,7 @@ import mage.game.Game; */ public interface AsThoughEffect extends ContinuousEffect { + boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game); boolean applies(UUID sourceId, Ability source, Game game); AsThoughEffectType getAsThoughEffectType(); diff --git a/Mage/src/mage/abilities/effects/AsThoughEffectImpl.java b/Mage/src/mage/abilities/effects/AsThoughEffectImpl.java index 7c268204e28..316548a4569 100644 --- a/Mage/src/mage/abilities/effects/AsThoughEffectImpl.java +++ b/Mage/src/mage/abilities/effects/AsThoughEffectImpl.java @@ -28,10 +28,13 @@ package mage.abilities.effects; +import java.util.UUID; +import mage.abilities.Ability; import mage.constants.AsThoughEffectType; import mage.constants.Duration; import mage.constants.EffectType; import mage.constants.Outcome; +import mage.game.Game; /** * @@ -51,6 +54,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements super(effect); this.type = effect.type; } + + @Override + public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game) { + return applies(sourceId, source, game); + } @Override public AsThoughEffectType getAsThoughEffectType() { diff --git a/Mage/src/mage/abilities/effects/ContinuousEffects.java b/Mage/src/mage/abilities/effects/ContinuousEffects.java index 4804a99552f..73e65ea6ec1 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffects.java @@ -420,19 +420,31 @@ public class ContinuousEffects implements Serializable { } public boolean asThough(UUID objectId, AsThoughEffectType type, UUID controllerId, Game game) { + return asThough(objectId, type, null, controllerId, game); + } + + public boolean asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) { List asThoughEffectsList = getApplicableAsThoughEffects(type, game); for (AsThoughEffect effect: asThoughEffectsList) { HashSet abilities = asThoughEffectsMap.get(type).getAbility(effect.getId()); for (Ability ability : abilities) { if (controllerId.equals(ability.getControllerId())) { - if (effect.applies(objectId, ability, game)) { - return true; + if (affectedAbility == null) { + if (effect.applies(objectId, ability, game)) { + return true; + } + } else { + if (effect.applies(objectId, affectedAbility, ability, game)) { + return true; + } } } } } return false; + } + /** * Filters out asThough effects that are not active. diff --git a/Mage/src/mage/abilities/effects/common/cost/CostModificationEffectImpl.java b/Mage/src/mage/abilities/effects/common/cost/CostModificationEffectImpl.java index 7792856ccdf..eafd452cf9f 100644 --- a/Mage/src/mage/abilities/effects/common/cost/CostModificationEffectImpl.java +++ b/Mage/src/mage/abilities/effects/common/cost/CostModificationEffectImpl.java @@ -32,13 +32,10 @@ import mage.constants.Duration; import mage.constants.EffectType; import mage.constants.Outcome; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.CostModificationEffect; -import mage.cards.Card; import mage.constants.CostModificationType; import mage.game.Game; -import mage.game.stack.Spell; /** * Simple implementation of a {@link CostModificationEffect} offering simplified diff --git a/Mage/src/mage/abilities/keyword/DelveAbility.java b/Mage/src/mage/abilities/keyword/DelveAbility.java index a1769b614b7..439d2edce40 100644 --- a/Mage/src/mage/abilities/keyword/DelveAbility.java +++ b/Mage/src/mage/abilities/keyword/DelveAbility.java @@ -85,7 +85,7 @@ import mage.util.CardUtil; if (!target.canChoose(sourceId, controllerId, game)) { return; } - if (player.chooseUse(Outcome.Detriment, "Delve cards from your graveyard?", game)) { + if (!CardUtil.isCheckPlayableMode(ability) && player.chooseUse(Outcome.Detriment, "Delve cards from your graveyard?", game)) { player.chooseTarget(Outcome.Detriment, target, ability, game); if (target.getTargets().size() > 0) { int adjCost = 0; @@ -94,11 +94,11 @@ import mage.util.CardUtil; if (card == null) { continue; } - card.moveToExile(null, null, this.getSourceId(), game); + player.moveCardToExileWithInfo(card, null, "", getSourceId(), game, Zone.GRAVEYARD); ++adjCost; } - game.informPlayers(new StringBuilder(player.getName()).append(" delved ") - .append(adjCost).append(" creature").append(adjCost != 1?"s":"").append(" from his or her graveyard").toString()); + game.informPlayers(new StringBuilder("Delve: ").append(player.getName()).append(" exiled ") + .append(adjCost).append(" card").append(adjCost != 1?"s":"").append(" from his or her graveyard").toString()); CardUtil.adjustCost((SpellAbility)ability, adjCost); } } diff --git a/Mage/src/mage/abilities/keyword/OfferingAbility.java b/Mage/src/mage/abilities/keyword/OfferingAbility.java index a0218a9a4d4..414b51c6a82 100644 --- a/Mage/src/mage/abilities/keyword/OfferingAbility.java +++ b/Mage/src/mage/abilities/keyword/OfferingAbility.java @@ -128,6 +128,11 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID sourceId, Ability source, Game game) { + return false; + } + + @Override + public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game) { if (sourceId.equals(source.getSourceId())) { Card card = game.getCard(sourceId); if (!card.getOwnerId().equals(source.getControllerId())) { @@ -139,10 +144,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl { Object alreadyConfirmed = game.getState().getValue("offering_ok_" + card.getId()); game.getState().setValue("offering_" + card.getId(), null); game.getState().setValue("offering_ok_" + card.getId(), null); - if (alreadyConfirmed != null) { - return true; - } - return false; + return alreadyConfirmed != null; } else { // first call -> remove previous Ids game.getState().setValue("offering_Id_" + card.getId(), null); @@ -153,7 +155,8 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl { FilterControlledCreaturePermanent filter = ((OfferingAbility) source).getFilter(); Card spellToCast = game.getCard(source.getSourceId()); Player player = game.getPlayer(source.getControllerId()); - if (player != null && player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + "?", game)) { + if (player != null && !CardUtil.isCheckPlayableMode(affectedAbility) && + player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + "?", game)) { Target target = new TargetControlledCreaturePermanent(1,1,filter,true); player.chooseTarget(Outcome.Sacrifice, target, source, game); if (!target.isChosen()) { @@ -185,9 +188,9 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl { class OfferingCostReductionEffect extends CostModificationEffectImpl { - private UUID spellAbilityId; - private UUID activationId; - private ManaCosts manaCostsToReduce; + private final UUID spellAbilityId; + private final UUID activationId; + private final ManaCosts manaCostsToReduce; OfferingCostReductionEffect (UUID spellAbilityId, ManaCosts manaCostsToReduce, UUID activationId) { super(Duration.OneUse, Outcome.Benefit, CostModificationType.REDUCE_COST); @@ -231,4 +234,4 @@ class OfferingCostReductionEffect extends CostModificationEffectImpl { public OfferingCostReductionEffect copy() { return new OfferingCostReductionEffect(this); } -} \ No newline at end of file +} diff --git a/Mage/src/mage/abilities/keyword/ShadowAbility.java b/Mage/src/mage/abilities/keyword/ShadowAbility.java index 338cec22bce..e405628d642 100644 --- a/Mage/src/mage/abilities/keyword/ShadowAbility.java +++ b/Mage/src/mage/abilities/keyword/ShadowAbility.java @@ -62,7 +62,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton { @Override public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) { if (blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId()) - || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, blocker.getControllerId(), game)) { + || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game)) { return true; } return false; diff --git a/Mage/src/mage/abilities/keyword/SuspendAbility.java b/Mage/src/mage/abilities/keyword/SuspendAbility.java index 74936cde3e5..4ec089c643b 100644 --- a/Mage/src/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/mage/abilities/keyword/SuspendAbility.java @@ -193,7 +193,7 @@ public class SuspendAbility extends ActivatedAbilityImpl { MageObject object = game.getObject(sourceId); return (object.getCardType().contains(CardType.INSTANT) || object.hasAbility(FlashAbility.getInstance().getId(), game) || - game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, playerId, game) || + game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, this, playerId, game) || game.canPlaySorcery(playerId)); } diff --git a/Mage/src/mage/players/ManaPool.java b/Mage/src/mage/players/ManaPool.java index 18c3894ce3e..b3566c6fe78 100644 --- a/Mage/src/mage/players/ManaPool.java +++ b/Mage/src/mage/players/ManaPool.java @@ -118,7 +118,7 @@ public class ManaPool implements Serializable { // check if any mana can be spend to cast the mana cost of an ability private boolean spendAnyMana(Ability ability, Game game) { - return game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_ANY_MANA, ability.getControllerId(), game); + return game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_ANY_MANA, ability, ability.getControllerId(), game); } public int get(ManaType manaType) { diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 1b4c450ce8d..3302da9d434 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -1834,8 +1834,12 @@ public abstract class PlayerImpl implements Player, Serializable { } protected boolean canPlay(ActivatedAbility ability, ManaOptions available, Game game) { - if (!(ability instanceof ManaAbility) && ability.canActivate(playerId, game)) { - Ability copy = ability.copy(); + if (!(ability instanceof ManaAbility)) { + ActivatedAbility copy = ability.copy(); + copy.setCheckPlayableMode(); // prevents from endless loops for asking player to use effects by checking this mode + if (!copy.canActivate(playerId, game)) { + return false; + } game.getContinuousEffects().costModification(copy, game); Card card = game.getCard(ability.getSourceId()); diff --git a/Mage/src/mage/util/CardUtil.java b/Mage/src/mage/util/CardUtil.java index 3b440a2dbaf..2dd0e4ebbac 100644 --- a/Mage/src/mage/util/CardUtil.java +++ b/Mage/src/mage/util/CardUtil.java @@ -33,6 +33,7 @@ import java.util.UUID; import mage.Mana; import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; import mage.abilities.costs.AlternativeCost; import mage.abilities.costs.AlternativeCostImpl; @@ -490,4 +491,17 @@ public class CardUtil { } return uniqueString.toString(); } + + /** + * Returns if the ability is used to check which cards + * are playable on hand. (Issue #457) + * @param ability - ability to check + * @return + */ + public static boolean isCheckPlayableMode(Ability ability) { + if (ability instanceof ActivatedAbility) { + return ((ActivatedAbility) ability).isCheckPlayableMode(); + } + return false; + } }