diff --git a/Mage.Sets/src/mage/cards/c/CosmicIntervention.java b/Mage.Sets/src/mage/cards/c/CosmicIntervention.java new file mode 100644 index 0000000000..58a62c6808 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CosmicIntervention.java @@ -0,0 +1,114 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.ExileTargetForSourceEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; +import mage.abilities.keyword.ForetellAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author jeffwadsworth + */ +public final class CosmicIntervention extends CardImpl { + + public CosmicIntervention(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}"); + + // If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step. + this.getSpellAbility().addEffect(new CosmicInterventionReplacementEffect()); + + // Foretell 1W + this.addAbility(new ForetellAbility(this, "{1}{W}")); + + } + + public CosmicIntervention(final CosmicIntervention card) { + super(card); + } + + @Override + public CosmicIntervention copy() { + return new CosmicIntervention(this); + } +} + +class CosmicInterventionReplacementEffect extends ReplacementEffectImpl { + + CosmicInterventionReplacementEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + staticText = "If a permanent you control would be put into a graveyard from the battlefield this turn, exile it instead. Return it to the battlefield under its owner's control at the beginning of the next end step"; + } + + private CosmicInterventionReplacementEffect(final CosmicInterventionReplacementEffect effect) { + super(effect); + } + + @Override + public CosmicInterventionReplacementEffect copy() { + return new CosmicInterventionReplacementEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + Card card = game.getCard(event.getTargetId()); + if (card != null) { + ExileTargetForSourceEffect exileEffect = new ExileTargetForSourceEffect(); + exileEffect.setTargetPointer(new FixedTarget(card.getId(), game)); + exileEffect.apply(game, source); + Effect returnEffect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false); + returnEffect.setTargetPointer(new FixedTarget(card.getId(), game)); + AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnEffect); + game.addDelayedTriggeredAbility(delayedAbility, source); + return true; + } + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD + && (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD)) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null + || controller == null + || permanent.getControllerId() == controller.getId()) { + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java new file mode 100644 index 0000000000..bbd39c2bb8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -0,0 +1,324 @@ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.SpellAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ForetellAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacesCard; +import mage.cards.SplitCard; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SpellAbilityCastMode; +import mage.constants.SpellAbilityType; +import mage.constants.SubLayer; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +/** + * @author jeffwadsworth + */ +public final class EtherealValkyrie extends CardImpl { + + public EtherealValkyrie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{U}"); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Ethereal Valkyrie enters the battlefield or attacks, draw a card, then exile a card from your hand face down. It becomes foretold. Its foretell cost is its mana cost reduced by {2}. + this.addAbility(new EtherealValkyrieTriggeredAbility(new EtherealValkyrieEffect())); + + } + + private EtherealValkyrie(final EtherealValkyrie card) { + super(card); + } + + @Override + public EtherealValkyrie copy() { + return new EtherealValkyrie(this); + } +} + +class EtherealValkyrieTriggeredAbility extends TriggeredAbilityImpl { + + EtherealValkyrieTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, false); + } + + EtherealValkyrieTriggeredAbility(final EtherealValkyrieTriggeredAbility ability) { + super(ability); + } + + @Override + public EtherealValkyrieTriggeredAbility copy() { + return new EtherealValkyrieTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.ATTACKER_DECLARED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent p = game.getPermanent(event.getSourceId()); + Permanent pETB = game.getPermanent(event.getTargetId()); + if (p != null + && p.getId() == sourceId) { + return true; + } + if (pETB != null + && pETB.getId() == sourceId) { + return true; + } + return false; + } + + @Override + public String getRule() { + return "Whenever {this} enters the battlefield or attacks, " + super.getRule(); + } +} + +class EtherealValkyrieEffect extends OneShotEffect { + + public EtherealValkyrieEffect() { + super(Outcome.Benefit); + this.staticText = "draw a card, then exile a card from your hand face down. It becomes foretold. Its foretell cost is its mana cost reduced by {2}"; + } + + public EtherealValkyrieEffect(final EtherealValkyrieEffect effect) { + super(effect); + } + + @Override + public EtherealValkyrieEffect copy() { + return new EtherealValkyrieEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + controller.drawCards(1, source, game); + TargetCardInHand targetCard = new TargetCardInHand(new FilterCard("card to exile face down. It becomes foretold.")); + if (controller.chooseTarget(Outcome.Benefit, targetCard, source, game)) { + Card exileCard = game.getCard(targetCard.getFirstTarget()); + if (exileCard == null) { + return false; + } + String foretellCost = CardUtil.reduceCost(exileCard.getSpellAbility().getManaCostsToPay(), 2).getText(); + game.getState().setValue(exileCard.getId().toString() + "Foretell Cost", foretellCost); + game.getState().setValue(exileCard.getId().toString() + "Foretell Turn Number", game.getTurnNum()); + UUID exileId = CardUtil.getExileZoneId(exileCard.getId().toString() + "foretellAbility", game); + controller.moveCardsToExile(exileCard, source, game, true, exileId, " Foretell Turn Number: " + game.getTurnNum()); + exileCard.setFaceDown(true, game); + ForetellAbility foretellAbility = new ForetellAbility(exileCard, foretellCost); + foretellAbility.setSourceId(exileCard.getId()); + foretellAbility.setControllerId(exileCard.getOwnerId()); + game.getState().addOtherAbility(exileCard, foretellAbility); + foretellAbility.activate(game, true); + game.addEffect(new ForetellAddCostEffect(new MageObjectReference(exileCard, game)), source); + return true; + } + } + return false; + } +} + +class ForetellAddCostEffect extends ContinuousEffectImpl { + + private final MageObjectReference mor; + + public ForetellAddCostEffect(MageObjectReference mor) { + super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.mor = mor; + staticText = "Foretold card"; + } + + public ForetellAddCostEffect(final ForetellAddCostEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = mor.getCard(game); + if (card != null + && game.getState().getZone(card.getId()) == Zone.EXILED) { + String foretellCost = (String) game.getState().getValue(card.getId().toString() + "Foretell Cost"); + Ability ability = new ForetellCostAbility(foretellCost); + ability.setSourceId(card.getId()); + ability.setControllerId(source.getControllerId()); + game.getState().addOtherAbility(card, ability); + } else { + discard(); + } + return true; + } + + @Override + public ForetellAddCostEffect copy() { + return new ForetellAddCostEffect(this); + } +} + +class ForetellCostAbility extends SpellAbility { + + private String abilityName; + private SpellAbility spellAbilityToResolve; + + public ForetellCostAbility(String foretellCost) { + super(null, "Testing", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL); + this.setAdditionalCostsRuleVisible(false); + this.name = "Foretell " + foretellCost; + this.addCost(new ManaCostsImpl(foretellCost)); + } + + public ForetellCostAbility(final ForetellCostAbility ability) { + super(ability); + this.spellAbilityType = ability.spellAbilityType; + this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; + } + + @Override + public ActivatedAbility.ActivationStatus canActivate(UUID playerId, Game game) { + if (super.canActivate(playerId, game).canActivate()) { + Card card = game.getCard(getSourceId()); + if (card != null) { + // Card must be in the exile zone + if (game.getState().getZone(card.getId()) != Zone.EXILED) { + return ActivatedAbility.ActivationStatus.getFalse(); + } + // Card must be Foretold + if (game.getState().getValue(card.getId().toString() + "Foretell Turn Number") == null + && game.getState().getValue(card.getId().toString() + "foretellAbility") == null) { + return ActivatedAbility.ActivationStatus.getFalse(); + } + // Can't be cast if the turn it was Foretold is the same + if ((int) game.getState().getValue(card.getId().toString() + "Foretell Turn Number") == game.getTurnNum()) { + return ActivatedAbility.ActivationStatus.getFalse(); + } + // Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc) + UUID exileId = (UUID) game.getState().getValue(card.getId().toString() + "foretellAbility"); + ExileZone exileZone = game.getState().getExile().getExileZone(exileId); + if (exileZone != null + && exileZone.isEmpty()) { + return ActivatedAbility.ActivationStatus.getFalse(); + } + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof ModalDoubleFacesCard) { + if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } + return card.getSpellAbility().canActivate(playerId, game); + } + } + return ActivatedAbility.ActivationStatus.getFalse(); + } + + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card != null) { + if (spellAbilityToResolve == null) { + SpellAbility spellAbilityCopy = null; + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } + } else if (card instanceof ModalDoubleFacesCard) { + if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().copy(); + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.getManaCosts().clear(); + spellAbilityCopy.getManaCostsToPay().clear(); + spellAbilityCopy.getCosts().addAll(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + } + } + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + + @Override + public ForetellCostAbility copy() { + return new ForetellCostAbility(this); + } + + @Override + public String getRule(boolean all) { + return ""; + } + + /** + * Used for split card in PlayerImpl method: + * getOtherUseableActivatedAbilities + * + * @param abilityName + */ + public void setAbilityName(String abilityName) { + this.abilityName = abilityName; + } + +} diff --git a/Mage.Sets/src/mage/cards/h/HeroOfBretagard.java b/Mage.Sets/src/mage/cards/h/HeroOfBretagard.java new file mode 100644 index 0000000000..2880fbb64e --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeroOfBretagard.java @@ -0,0 +1,195 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.AddCardSubTypeSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.filter.predicate.Predicate; + +/** + * @author jeffwadsworth + */ +public final class HeroOfBretagard extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent(); + + static { + filter.add(test1.instance); + filter2.add(test2.instance); + } + + public HeroOfBretagard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever you exile one or more cards from your hand and/or permanents from the battlefield, put that many +1/+1 counters on Hero of Bretagard. + this.addAbility(new HeroOfBretagardTriggeredAbility(new HeroOfBretagardEffect())); + + // As long as Hero of Bretagard has five or more counters on it, it has flying and is an Angel in addition to its other types. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new ConditionalContinuousEffect(new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), + new SourceMatchesFilterCondition(filter), + "As long as Hero of Bretagard has five or more counters on it, it has flying "))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new ConditionalContinuousEffect(new AddCardSubTypeSourceEffect(Duration.WhileOnBattlefield, SubType.ANGEL), new SourceMatchesFilterCondition(filter), + "and is an Angel in addition to its other types."))); + + // As long as Hero of Bretagard has ten or more counters on it, it has indestructible and is a God in addition to its other types. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), + new SourceMatchesFilterCondition(filter2), + "As long as Hero of Bretagard has ten or more counters on it, it has indestructible "))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new ConditionalContinuousEffect(new AddCardSubTypeSourceEffect(Duration.WhileOnBattlefield, SubType.GOD), new SourceMatchesFilterCondition(filter2), + "and is a God in addition to its other types."))); + + } + + private HeroOfBretagard(final HeroOfBretagard card) { + super(card); + } + + @Override + public HeroOfBretagard copy() { + return new HeroOfBretagard(this); + } +} + +class HeroOfBretagardTriggeredAbility extends TriggeredAbilityImpl { + + HeroOfBretagardTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, false); + } + + HeroOfBretagardTriggeredAbility(final HeroOfBretagardTriggeredAbility ability) { + super(ability); + } + + @Override + public HeroOfBretagardTriggeredAbility copy() { + return new HeroOfBretagardTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent != null + && Zone.HAND == zEvent.getFromZone() + && Zone.EXILED == zEvent.getToZone() + && zEvent.getTargetId() != null) { + Card card = game.getCard(zEvent.getTargetId()); + if (card != null) { + UUID cardOwnerId = card.getOwnerId(); + if (cardOwnerId != null + && card.isOwnedBy(getControllerId())) { + this.getEffects().get(0).setValue("number", 1); + return true; + } + } + } + if (zEvent != null + && Zone.BATTLEFIELD == zEvent.getFromZone() + && Zone.EXILED == zEvent.getToZone() + && zEvent.getTargetId() != null) { + Card card = game.getCard(zEvent.getTargetId()); + if (card != null) { + UUID cardOwnerId = card.getOwnerId(); + if (cardOwnerId != null + && card.isOwnedBy(getControllerId())) { + this.getEffects().get(0).setValue("number", 1); + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you exile one or more cards from your hand and/or permanents from the battlefield, put that many +1/+1 counters on {this}"; + + } +} + +class HeroOfBretagardEffect extends OneShotEffect { + + public HeroOfBretagardEffect() { + super(Outcome.Benefit); + } + + public HeroOfBretagardEffect(final HeroOfBretagardEffect effect) { + super(effect); + } + + @Override + public HeroOfBretagardEffect copy() { + return new HeroOfBretagardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return new AddCountersSourceEffect(CounterType.P1P1.createInstance((Integer) this.getValue("number"))).apply(game, source); + } +} + +enum test1 implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + return input.getCounters(game).values().stream().anyMatch(counter -> counter.getCount() > 4); + } + + @Override + public String toString() { + return "any counter"; + } +} + +enum test2 implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + return input.getCounters(game).values().stream().anyMatch(counter -> counter.getCount() > 9); + } + + @Override + public String toString() { + return "any counter"; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java new file mode 100644 index 0000000000..079dd1ae42 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java @@ -0,0 +1,149 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ForetellAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; + +import java.util.UUID; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.Card; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeGroupEvent; +import mage.game.permanent.token.SpiritWhiteToken; +import mage.watchers.common.ForetoldWatcher; + +/** + * @author jeffwadsworth + */ +public final class RanarTheEverWatchful extends CardImpl { + + public RanarTheEverWatchful(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // The first card you foretell each turn costs 0 to foretell + Ability ability = new SimpleStaticAbility(new RanarTheEverWatchfulCostReductionEffect()); + this.addAbility(ability); + + // Whenever you exile one or more cards from your hand and/or permanents from the battlefield, create a 1/1 white Spirit creature token with flying. + this.addAbility(new RanarTheEverWatchfulTriggeredAbility(new CreateTokenEffect(new SpiritWhiteToken()))); + + } + + private RanarTheEverWatchful(final RanarTheEverWatchful card) { + super(card); + } + + @Override + public RanarTheEverWatchful copy() { + return new RanarTheEverWatchful(this); + } +} + +class RanarTheEverWatchfulCostReductionEffect extends CostModificationEffectImpl { + + RanarTheEverWatchfulCostReductionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral, CostModificationType.REDUCE_COST); + staticText = "The first card you foretell each turn costs 0 to foretell"; + } + + private RanarTheEverWatchfulCostReductionEffect(RanarTheEverWatchfulCostReductionEffect effect) { + super(effect); + } + + @Override + public RanarTheEverWatchfulCostReductionEffect copy() { + return new RanarTheEverWatchfulCostReductionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + abilityToModify.getManaCostsToPay().clear(); + abilityToModify.getManaCostsToPay().addAll(new ManaCostsImpl<>("{0}")); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); + return (watcher != null + && watcher.countNumberForetellThisTurn() == 0 + && abilityToModify.isControlledBy(source.getControllerId()) + && abilityToModify instanceof ForetellAbility); + } +} + +class RanarTheEverWatchfulTriggeredAbility extends TriggeredAbilityImpl { + + RanarTheEverWatchfulTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, false); + } + + RanarTheEverWatchfulTriggeredAbility(final RanarTheEverWatchfulTriggeredAbility ability) { + super(ability); + } + + @Override + public RanarTheEverWatchfulTriggeredAbility copy() { + return new RanarTheEverWatchfulTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event; + if (zEvent != null + && Zone.HAND == zEvent.getFromZone() + && Zone.EXILED == zEvent.getToZone() + && zEvent.getCards() != null) { + for (Card card : zEvent.getCards()) { + if (card != null) { + UUID cardOwnerId = card.getOwnerId(); + if (cardOwnerId != null + && card.isOwnedBy(getControllerId())) { + return true; + } + } + } + } + if (zEvent != null + && Zone.BATTLEFIELD == zEvent.getFromZone() + && Zone.EXILED == zEvent.getToZone() + && zEvent.getCards() != null) { + return true; + + } + return false; + } + + @Override + public String getRule() { + return "Whenever you exile one or more cards from your hand and/or permanents from the battlefield, " + super.getRule(); + } +} diff --git a/Mage.Sets/src/mage/sets/KaldheimCommander.java b/Mage.Sets/src/mage/sets/KaldheimCommander.java index a5698a0b74..630026b0ea 100644 --- a/Mage.Sets/src/mage/sets/KaldheimCommander.java +++ b/Mage.Sets/src/mage/sets/KaldheimCommander.java @@ -39,6 +39,7 @@ public final class KaldheimCommander extends ExpansionSet { cards.add(new SetCardInfo("Cloudgoat Ranger", 21, Rarity.UNCOMMON, mage.cards.c.CloudgoatRanger.class)); cards.add(new SetCardInfo("Command Tower", 108, Rarity.COMMON, mage.cards.c.CommandTower.class)); cards.add(new SetCardInfo("Commander's Sphere", 99, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); + cards.add(new SetCardInfo("Cosmic Intervention", 3, Rarity.RARE, mage.cards.c.CosmicIntervention.class)); cards.add(new SetCardInfo("Crown of Skemfar", 13, Rarity.RARE, mage.cards.c.CrownOfSkemfar.class)); cards.add(new SetCardInfo("Cryptic Caves", 109, Rarity.UNCOMMON, mage.cards.c.CrypticCaves.class)); cards.add(new SetCardInfo("Cultivator of Blades", 55, Rarity.RARE, mage.cards.c.CultivatorOfBlades.class)); @@ -53,6 +54,7 @@ public final class KaldheimCommander extends ExpansionSet { cards.add(new SetCardInfo("Elvish Rejuvenator", 60, Rarity.COMMON, mage.cards.e.ElvishRejuvenator.class)); cards.add(new SetCardInfo("Empyrean Eagle", 85, Rarity.UNCOMMON, mage.cards.e.EmpyreanEagle.class)); cards.add(new SetCardInfo("End-Raze Forerunners", 61, Rarity.RARE, mage.cards.e.EndRazeForerunners.class)); + cards.add(new SetCardInfo("Ethereal Valkyrie", 16, Rarity.RARE, mage.cards.e.EtherealValkyrie.class)); cards.add(new SetCardInfo("Evangel of Heliod", 23, Rarity.UNCOMMON, mage.cards.e.EvangelOfHeliod.class)); cards.add(new SetCardInfo("Eyeblight Cullers", 48, Rarity.COMMON, mage.cards.e.EyeblightCullers.class)); cards.add(new SetCardInfo("Eyeblight Massacre", 49, Rarity.UNCOMMON, mage.cards.e.EyeblightMassacre.class)); @@ -67,6 +69,7 @@ public final class KaldheimCommander extends ExpansionSet { cards.add(new SetCardInfo("Golgari Guildgate", 111, Rarity.COMMON, mage.cards.g.GolgariGuildgate.class)); cards.add(new SetCardInfo("Golgari Rot Farm", 112, Rarity.UNCOMMON, mage.cards.g.GolgariRotFarm.class)); cards.add(new SetCardInfo("Harvest Season", 63, Rarity.RARE, mage.cards.h.HarvestSeason.class)); + cards.add(new SetCardInfo("Hero of Bretagard", 4, Rarity.RARE, mage.cards.h.HeroOfBretagard.class)); cards.add(new SetCardInfo("Imperious Perfect", 64, Rarity.RARE, mage.cards.i.ImperiousPerfect.class)); cards.add(new SetCardInfo("Inspired Sphinx", 40, Rarity.MYTHIC, mage.cards.i.InspiredSphinx.class)); cards.add(new SetCardInfo("Jagged-Scar Archers", 65, Rarity.UNCOMMON, mage.cards.j.JaggedScarArchers.class)); @@ -100,6 +103,7 @@ public final class KaldheimCommander extends ExpansionSet { cards.add(new SetCardInfo("Pride of the Perfect", 52, Rarity.UNCOMMON, mage.cards.p.PrideOfThePerfect.class)); cards.add(new SetCardInfo("Prowess of the Fair", 53, Rarity.UNCOMMON, mage.cards.p.ProwessOfTheFair.class)); cards.add(new SetCardInfo("Putrefy", 91, Rarity.UNCOMMON, mage.cards.p.Putrefy.class)); + cards.add(new SetCardInfo("Ranar the Ever-Watchful", 2, Rarity.RARE, mage.cards.r.RanarTheEverWatchful.class)); cards.add(new SetCardInfo("Reclamation Sage", 72, Rarity.UNCOMMON, mage.cards.r.ReclamationSage.class)); cards.add(new SetCardInfo("Restoration Angel", 31, Rarity.COMMON, mage.cards.r.RestorationAngel.class)); cards.add(new SetCardInfo("Return to Dust", 32, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); diff --git a/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java b/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java index 1495146ba9..c0e63fc866 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java @@ -17,7 +17,7 @@ public enum ForetoldCondition implements Condition { public boolean apply(Game game, Ability source) { ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); if (watcher != null) { - return watcher.foretoldSpellWasCast(source.getSourceId()); + return watcher.cardWasForetold(source.getSourceId()); } return false; } @@ -26,4 +26,4 @@ public enum ForetoldCondition implements Condition { public String toString() { return "this spell was foretold"; } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java index 5108b37c40..0031a47fcf 100644 --- a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java @@ -8,6 +8,8 @@ package mage.watchers.common; import java.util.HashSet; import java.util.Set; import java.util.UUID; +import mage.abilities.keyword.ForetellAbility; +import mage.cards.Card; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; @@ -21,9 +23,9 @@ import mage.watchers.Watcher; * @author jeffwadsworth */ public class ForetoldWatcher extends Watcher { - - // If a card was Foretold during a turn, this list stores it. Cleared at the end of the turn. + // If foretell was activated or a card was Foretold by the controller this turn, this list stores it. Cleared at the end of the turn. + private final Set foretellCardsThisTurn = new HashSet<>(); private final Set foretoldCardsThisTurn = new HashSet<>(); public ForetoldWatcher() { @@ -32,10 +34,19 @@ public class ForetoldWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.TAKEN_SPECIAL_ACTION) { + Card card = game.getCard(event.getSourceId()); + if (card != null + && card.getAbilities(game).containsClass(ForetellAbility.class) + && controllerId == event.getPlayerId()) { + foretellCardsThisTurn.add(card.getId()); + } + } if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone() == Zone.EXILED) { Spell spell = (Spell) game.getObject(event.getTargetId()); - if (spell != null) { + if (spell != null + && controllerId == event.getPlayerId()) { UUID exileId = CardUtil.getExileZoneId(spell.getSourceId().toString() + "foretellAbility", game); if (exileId != null) { foretoldCardsThisTurn.add(spell.getSourceId()); @@ -44,13 +55,26 @@ public class ForetoldWatcher extends Watcher { } } - public boolean foretoldSpellWasCast(UUID sourceId) { + public boolean cardUsedForetell(UUID sourceId) { + return foretellCardsThisTurn.contains(sourceId); + } + + public boolean cardWasForetold(UUID sourceId) { return foretoldCardsThisTurn.contains(sourceId); } + public int countNumberForetellThisTurn() { + return foretellCardsThisTurn.size(); + } + + public int countNumberForetoldThisTurn() { + return foretoldCardsThisTurn.size(); + } + @Override public void reset() { super.reset(); + foretellCardsThisTurn.clear(); foretoldCardsThisTurn.clear(); } }