diff --git a/Mage.Sets/src/mage/cards/f/ForceProjection.java b/Mage.Sets/src/mage/cards/f/ForceProjection.java index a29ec11982c..6770afe6c72 100644 --- a/Mage.Sets/src/mage/cards/f/ForceProjection.java +++ b/Mage.Sets/src/mage/cards/f/ForceProjection.java @@ -1,7 +1,6 @@ package mage.cards.f; import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.common.BecomesTargetTriggeredAbility; @@ -15,13 +14,11 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterSpell; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; /** @@ -32,7 +29,6 @@ public final class ForceProjection extends CardImpl { public ForceProjection(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}{U}"); - // Create a token that is a copy of target creature you control except that it is an Illusion in addition to its other types and gains "When this creature becomes the target of a spell, sacrifice it." this.getSpellAbility().addEffect(new ForceProjectionEffect()); @@ -56,8 +52,8 @@ class ForceProjectionEffect extends OneShotEffect { public ForceProjectionEffect() { super(Outcome.Copy); - this.staticText = "Create a token that is a copy of target creature you control except that it is an Illusion " + - "in addition to its other types and gains \"When this creature becomes the target of a spell, sacrifice it.\""; + this.staticText = "Create a token that is a copy of target creature you control except that it is an Illusion " + + "in addition to its other types and gains \"When this creature becomes the target of a spell, sacrifice it.\""; } public ForceProjectionEffect(final ForceProjectionEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java b/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java new file mode 100644 index 00000000000..f0db706fc49 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java @@ -0,0 +1,260 @@ +package mage.cards.t; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.ManaType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author LevelX2 + */ +public final class ThiefOfSanity extends CardImpl { + + protected static final String VALUE_PREFIX = "ExileZones"; + + public ThiefOfSanity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}"); + + this.subtype.add(SubType.SPECTER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Thief of Sanity deals combat damage to a player, look at the top three cards of that player's library, exile one of them face down, then put the rest into their graveyard. For as long as that card remains exiled, you may look at it, you may cast it, and you may spend mana as though it were mana of any type to cast it. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new ThiefOfSanityEffect(), false, true)); + + Ability ability = new SimpleStaticAbility(Zone.ALL, new ThiefOfSanityLookEffect()); + ability.setRuleVisible(false); + this.addAbility(ability); + } + + public ThiefOfSanity(final ThiefOfSanity card) { + super(card); + } + + @Override + public ThiefOfSanity copy() { + return new ThiefOfSanity(this); + } +} + +class ThiefOfSanityEffect extends OneShotEffect { + + public ThiefOfSanityEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top three cards of that player's library, exile one of them face down, then put the rest into their graveyard. For as long as that card remains exiled, you may look at it, you may cast it, and you may spend mana as though it were mana of any type to cast it"; + } + + public ThiefOfSanityEffect(final ThiefOfSanityEffect effect) { + super(effect); + } + + @Override + public ThiefOfSanityEffect copy() { + return new ThiefOfSanityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player damagedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && damagedPlayer != null && sourceObject != null) { + Cards topCards = new CardsImpl(); + topCards.addAll(damagedPlayer.getLibrary().getTopCards(game, 3)); + TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile face down")); + if (controller.choose(outcome, topCards, target, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + topCards.remove(card); + // move card to exile + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + card.setFaceDown(true, game); + if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { + card.setFaceDown(true, game); + Set exileZones = (Set) game.getState().getValue(ThiefOfSanity.VALUE_PREFIX + source.getSourceId().toString()); + if (exileZones == null) { + exileZones = new HashSet<>(); + game.getState().setValue(ThiefOfSanity.VALUE_PREFIX + source.getSourceId().toString(), exileZones); + } + exileZones.add(exileZoneId); + // allow to cast the card + ContinuousEffect effect = new ThiefOfSanityCastFromExileEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // and you may spend mana as though it were mana of any color to cast it + effect = new ThiefOfSanitySpendAnyManaEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + } + } + // put the rest into their graveyard + controller.moveCards(topCards, Zone.GRAVEYARD, source, game); + return true; + } + + return false; + } +} + +class ThiefOfSanityCastFromExileEffect extends AsThoughEffectImpl { + + public ThiefOfSanityCastFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + staticText = "You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell"; + } + + public ThiefOfSanityCastFromExileEffect(final ThiefOfSanityCastFromExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ThiefOfSanityCastFromExileEffect copy() { + return new ThiefOfSanityCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID targetId = getTargetPointer().getFirst(game, source); + if (targetId == null) { + this.discard(); + } else if (objectId.equals(targetId) + && affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + // TODO: Allow to cast Zoetic Cavern face down + return card != null && !card.isLand(); + } + return false; + } +} + +class ThiefOfSanitySpendAnyManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + public ThiefOfSanitySpendAnyManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.Custom, Outcome.Benefit); + staticText = "you may spend mana as though it were mana of any color to cast it"; + } + + public ThiefOfSanitySpendAnyManaEffect(final ThiefOfSanitySpendAnyManaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ThiefOfSanitySpendAnyManaEffect copy() { + return new ThiefOfSanitySpendAnyManaEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + 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 + this.discard(); + } + return false; + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } +} + +class ThiefOfSanityLookEffect extends AsThoughEffectImpl { + + public ThiefOfSanityLookEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + staticText = "You may look at the cards exiled with {this}"; + } + + public ThiefOfSanityLookEffect(final ThiefOfSanityLookEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ThiefOfSanityLookEffect copy() { + return new ThiefOfSanityLookEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId()) && game.getState().getZone(objectId) == Zone.EXILED) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + Card card = game.getCard(objectId); + if (card != null && card.isFaceDown(game)) { + Set exileZones = (Set) game.getState().getValue(ThiefOfSanity.VALUE_PREFIX + source.getSourceId().toString()); + if (exileZones != null) { + for (ExileZone exileZone : game.getExile().getExileZones()) { + if (exileZone.contains(objectId)) { + if (!exileZones.contains(exileZone.getId())) { + return false; + } + } + } + return true; + } + } + } + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java index 44c7c14824a..1c58869db36 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java @@ -12,7 +12,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 affectedAbility, Ability source, Game game, UUID playerId); boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game); diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java index 020d995cce7..03ed5c0b3d9 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java @@ -1,4 +1,3 @@ - package mage.abilities.effects; import java.util.UUID; @@ -29,8 +28,12 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements } @Override - public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game) { - return applies(objectId, source, affectedAbility.getControllerId(), game); + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) { + return applies(objectId, source, playerId, game); + } else { + return applies(objectId, source, affectedAbility.getControllerId(), game); + } } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 0fd1aee39b3..2e9b42d980c 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -1,4 +1,3 @@ - package mage.abilities.effects; import java.io.Serializable; @@ -496,7 +495,7 @@ public class ContinuousEffects implements Serializable { if (effect.applies(objectId, ability, controllerId, game)) { return new MageObjectReference(ability.getSourceObject(game), game); } - } else if (effect.applies(objectId, affectedAbility, ability, game)) { + } else if (effect.applies(objectId, affectedAbility, ability, game, controllerId)) { return new MageObjectReference(ability.getSourceObject(game), game); } } @@ -512,7 +511,7 @@ public class ContinuousEffects implements Serializable { Set abilities = asThoughEffectsMap.get(AsThoughEffectType.SPEND_ONLY_MANA).getAbility(effect.getId()); for (Ability ability : abilities) { if ((affectedAbility == null && effect.applies(objectId, ability, controllerId, game)) - || effect.applies(objectId, affectedAbility, ability, game)) { + || effect.applies(objectId, affectedAbility, ability, game, controllerId)) { if (((AsThoughManaEffect) effect).getAsThoughManaType(manaType, mana, controllerId, ability, game) == null) { return null; } @@ -525,7 +524,7 @@ public class ContinuousEffects implements Serializable { Set abilities = asThoughEffectsMap.get(AsThoughEffectType.SPEND_OTHER_MANA).getAbility(effect.getId()); for (Ability ability : abilities) { if ((affectedAbility == null && effect.applies(objectId, ability, controllerId, game)) - || effect.applies(objectId, affectedAbility, ability, game)) { + || effect.applies(objectId, affectedAbility, ability, game, controllerId)) { ManaType usableManaType = ((AsThoughManaEffect) effect).getAsThoughManaType(manaType, mana, controllerId, ability, game); if (usableManaType != null) { return usableManaType; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/ActivateAbilitiesAnyTimeYouCouldCastInstantEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/ActivateAbilitiesAnyTimeYouCouldCastInstantEffect.java index 4f76c81b722..d15adb8048b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/ActivateAbilitiesAnyTimeYouCouldCastInstantEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/ActivateAbilitiesAnyTimeYouCouldCastInstantEffect.java @@ -43,7 +43,7 @@ public class ActivateAbilitiesAnyTimeYouCouldCastInstantEffect extends AsThoughE } @Override - public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game) { + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { return affectedAbility.isControlledBy(source.getControllerId()) && activatedAbility.isInstance(affectedAbility); } diff --git a/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java b/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java index 9a1de9fd982..6ce501563f3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java @@ -104,7 +104,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl { } @Override - public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game) { + public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game, UUID playerId) { if (sourceId.equals(source.getSourceId())) { Card card = game.getCard(sourceId); if (!card.isOwnedBy(source.getControllerId())) {