diff --git a/Mage.Sets/src/mage/cards/r/RielleTheEverwise.java b/Mage.Sets/src/mage/cards/r/RielleTheEverwise.java new file mode 100644 index 0000000000..17b4eb219e --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RielleTheEverwise.java @@ -0,0 +1,124 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RielleTheEverwise extends CardImpl { + + private static final DynamicValue xValue + = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY); + + public RielleTheEverwise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // Rielle, the Everwise gets +1/+0 for each instant and sorcery card in your graveyard. + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect( + xValue, StaticValue.get(0), Duration.WhileOnBattlefield + ).setText("{this} gets +1/+0 for each instant and sorcery card in your graveyard"))); + + // Whenever you discard one or more cards for the first time each turn, draw that many cards. + this.addAbility(new RielleTheEverwiseTriggeredAbility()); + } + + private RielleTheEverwise(final RielleTheEverwise card) { + super(card); + } + + @Override + public RielleTheEverwise copy() { + return new RielleTheEverwise(this); + } +} + +class RielleTheEverwiseTriggeredAbility extends TriggeredAbilityImpl { + + RielleTheEverwiseTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + this.addWatcher(new RielleTheEverwiseWatcher()); + } + + private RielleTheEverwiseTriggeredAbility(final RielleTheEverwiseTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DISCARDED_CARDS; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + RielleTheEverwiseWatcher watcher = game.getState().getWatcher(RielleTheEverwiseWatcher.class); + if (watcher == null + || !watcher.checkDiscarded(event.getPlayerId()) + || !event.getPlayerId().equals(getControllerId()) + || event.getAmount() == 0) { + return false; + } + this.getEffects().clear(); + this.addEffect(new DrawCardSourceControllerEffect(event.getAmount())); + return true; + } + + @Override + public RielleTheEverwiseTriggeredAbility copy() { + return new RielleTheEverwiseTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever you discard one or more cards for the first time each turn, draw that many cards."; + } +} + +class RielleTheEverwiseWatcher extends Watcher { + + private final Map discardedThisTurn = new HashMap(); + + RielleTheEverwiseWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getAmount() > 0) { + discardedThisTurn.compute(event.getPlayerId(), (u, i) -> i == null ? 1 : i + 1); + } + } + + @Override + public void reset() { + super.reset(); + this.discardedThisTurn.clear(); + } + + boolean checkDiscarded(UUID playerId) { + return discardedThisTurn.getOrDefault(playerId, 0) < 2; + } +} diff --git a/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java b/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java index 63d08815e1..cc311f0466 100644 --- a/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java +++ b/Mage.Sets/src/mage/sets/IkoriaLairOfBehemoths.java @@ -270,6 +270,7 @@ public final class IkoriaLairOfBehemoths extends ExpansionSet { cards.add(new SetCardInfo("Reconnaissance Mission", 65, Rarity.UNCOMMON, mage.cards.r.ReconnaissanceMission.class)); cards.add(new SetCardInfo("Regal Leosaur", 202, Rarity.UNCOMMON, mage.cards.r.RegalLeosaur.class)); cards.add(new SetCardInfo("Reptilian Reflection", 132, Rarity.UNCOMMON, mage.cards.r.ReptilianReflection.class)); + cards.add(new SetCardInfo("Rielle, the Everwise", 203, Rarity.MYTHIC, mage.cards.r.RielleTheEverwise.class)); cards.add(new SetCardInfo("Rooting Moloch", 133, Rarity.UNCOMMON, mage.cards.r.RootingMoloch.class)); cards.add(new SetCardInfo("Rugged Highlands", 252, Rarity.COMMON, mage.cards.r.RuggedHighlands.class)); cards.add(new SetCardInfo("Ruinous Ultimatum", 204, Rarity.RARE, mage.cards.r.RuinousUltimatum.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardHandControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardHandControllerEffect.java index 170a63174b..cfc2f09e5d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardHandControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardHandControllerEffect.java @@ -33,12 +33,10 @@ public class DiscardHandControllerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - for (Card card : player.getHand().getCards(game)) { - player.discard(card, source, game); - } - return true; + if (player == null) { + return false; } - return false; + player.discard(player.getHand().size(),false,source,game); + return true; } } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 6060da9c5d..54ea8598b5 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -85,6 +85,7 @@ public class GameEvent implements Serializable { CONVOKED, DISCARD_CARD, DISCARDED_CARD, + DISCARDED_CARDS, CYCLE_CARD, CYCLED_CARD, CYCLE_DRAW, CLASH, CLASHED, DAMAGE_PLAYER, diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 98f3ea5878..aa67d35a62 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -743,6 +743,18 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public Cards discard(int amount, boolean random, Ability source, Game game) { + Cards discardedCards = doDiscard(amount, random, source, game); + if (!discardedCards.isEmpty()) { + UUID sourceId = source == null ? null : source.getSourceId(); + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.DISCARDED_CARDS, sourceId, + sourceId, playerId, discardedCards.size() + )); + } + return discardedCards; + } + + private Cards doDiscard(int amount, boolean random, Ability source, Game game) { Cards discardedCards = new CardsImpl(); if (amount <= 0) { return discardedCards; @@ -752,7 +764,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (this.getHand().size() == 1 || this.getHand().size() == amount) { List cardsToDiscard = new ArrayList<>(this.getHand()); for (UUID id : cardsToDiscard) { - if (discard(this.getHand().get(id, game), source, game)) { + if (doDiscard(this.getHand().get(id, game), source, game, false)) { discardedCards.add(id); } } @@ -762,7 +774,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (random) { for (int i = 0; i < amount; i++) { Card card = this.getHand().getRandom(game); - if (discard(card, source, game)) { + if (doDiscard(card, source, game, false)) { discardedCards.add(card); } } @@ -773,7 +785,7 @@ public abstract class PlayerImpl implements Player, Serializable { + " card" + (possibleAmount > 1 ? "s" : "")), playerId); choose(Outcome.Discard, target, source == null ? null : source.getSourceId(), game); for (UUID cardId : target.getTargets()) { - if (discard(this.getHand().get(cardId, game), source, game)) { + if (doDiscard(this.getHand().get(cardId, game), source, game, false)) { discardedCards.add(cardId); } } @@ -783,6 +795,10 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean discard(Card card, Ability source, Game game) { + return doDiscard(card, source, game, true); + } + + private boolean doDiscard(Card card, Ability source, Game game, boolean fireEvent) { //20100716 - 701.7 /* 701.7. Discard # 701.7a To discard a card, move it from its owner’s hand to that player’s graveyard. @@ -797,29 +813,36 @@ public abstract class PlayerImpl implements Player, Serializable { about the discarded card, that cost payment is illegal; the game returns to the moment before the cost was paid (see rule 717, "Handling Illegal Actions"). */ - if (card != null) { - GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, - card.getId(), source == null - ? null : source.getSourceId(), playerId); - gameEvent.setFlag(source != null); // event from effect or from cost (source == null) - if (!game.replaceEvent(gameEvent, source)) { - // write info to game log first so game log infos from triggered or replacement effects follow in the game log - if (!game.isSimulation()) { - game.informPlayers(getLogName() + " discards " + card.getLogName()); - } - /* If a card is discarded while Rest in Peace is on the battlefield, abilities that function - * when a card is discarded (such as madness) still work, even though that card never reaches - * a graveyard. In addition, spells or abilities that check the characteristics of a discarded - * card (such as Chandra Ablaze's first ability) can find that card in exile. */ - card.moveToZone(Zone.GRAVEYARD, source == null ? null : source.getSourceId(), game, false); - // So discard is also successful if card is moved to another zone by replacement effect! - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, - card.getId(), source == null - ? null : source.getSourceId(), playerId)); - return true; - } + if (card == null) { + return false; } - return false; + GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, + card.getId(), source == null + ? null : source.getSourceId(), playerId); + gameEvent.setFlag(source != null); // event from effect or from cost (source == null) + if (game.replaceEvent(gameEvent, source)) { + return false; + } + // write info to game log first so game log infos from triggered or replacement effects follow in the game log + if (!game.isSimulation()) { + game.informPlayers(getLogName() + " discards " + card.getLogName()); + } + /* If a card is discarded while Rest in Peace is on the battlefield, abilities that function + * when a card is discarded (such as madness) still work, even though that card never reaches + * a graveyard. In addition, spells or abilities that check the characteristics of a discarded + * card (such as Chandra Ablaze's first ability) can find that card in exile. */ + card.moveToZone(Zone.GRAVEYARD, source == null ? null : source.getSourceId(), game, false); + // So discard is also successful if card is moved to another zone by replacement effect! + UUID sourceId = source == null ? null : source.getSourceId(); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, + card.getId(), sourceId, playerId)); + if (fireEvent) { + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.DISCARDED_CARDS, sourceId, + sourceId, playerId, 1 + )); + } + return true; } @Override