diff --git a/Mage.Sets/src/mage/cards/m/MarvoDeepOperative.java b/Mage.Sets/src/mage/cards/m/MarvoDeepOperative.java new file mode 100644 index 00000000000..bdfd03d764f --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarvoDeepOperative.java @@ -0,0 +1,92 @@ +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ClashTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.cost.CastFromHandForFreeEffect; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * + * @author Grath + */ +public final class MarvoDeepOperative extends CardImpl { + + private static final FilterCard filter = new FilterCard(); + + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 8)); + } + + public MarvoDeepOperative(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.OCTOPUS); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(8); + + // Whenever Marvo, Deep Operative attacks, clash with defending player. + this.addAbility(new AttacksTriggeredAbility(new ClashTargetEffect().setText("clash with defending player"), false, null, SetTargetPointer.PLAYER)); + + // Whenever you win a clash, draw a card. Then you may cast a spell from your hand with mana value 8 or less + // without paying its mana cost. + Ability ability = new MarvoDeepOperativeTriggeredAbility(new DrawCardSourceControllerEffect(1, true)); + ability.addEffect(new CastFromHandForFreeEffect(filter)); + this.addAbility(ability); + + } + + private MarvoDeepOperative(final MarvoDeepOperative card) { + super(card); + } + + @Override + public MarvoDeepOperative copy() { + return new MarvoDeepOperative(this); + } +} + +class MarvoDeepOperativeTriggeredAbility extends TriggeredAbilityImpl { + + MarvoDeepOperativeTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); + } + + private MarvoDeepOperativeTriggeredAbility(final MarvoDeepOperativeTriggeredAbility ability) { + super(ability); + } + + @Override + public MarvoDeepOperativeTriggeredAbility copy() { + return new MarvoDeepOperativeTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CLASHED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()) && event.getFlag(); + } + + @Override + public String getRule() { + return "Whenever you win a clash, draw a card. Then you may cast a spell from your hand with mana value 8 or " + + "less without paying its mana cost."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java index 1f864a1d69e..46c04433a1e 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java @@ -160,6 +160,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Lonis, Cryptozoologist", 215, Rarity.RARE, mage.cards.l.LonisCryptozoologist.class)); cards.add(new SetCardInfo("Loran of the Third Path", 71, Rarity.RARE, mage.cards.l.LoranOfTheThirdPath.class)); cards.add(new SetCardInfo("Martial Impetus", 72, Rarity.UNCOMMON, mage.cards.m.MartialImpetus.class)); + cards.add(new SetCardInfo("Marvo, Deep Operative", 7, Rarity.MYTHIC, mage.cards.m.MarvoDeepOperative.class)); cards.add(new SetCardInfo("Massacre Wurm", 130, Rarity.MYTHIC, mage.cards.m.MassacreWurm.class)); cards.add(new SetCardInfo("Master of Death", 216, Rarity.RARE, mage.cards.m.MasterOfDeath.class)); cards.add(new SetCardInfo("Master of Pearls", 73, Rarity.RARE, mage.cards.m.MasterOfPearls.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java index 65e8951b986..394aaa9d88d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java @@ -3,18 +3,12 @@ package mage.abilities.effects.common; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetOpponent; - -import java.util.UUID; +import mage.target.targetpointer.FixedTarget; /** * 1. The controller of the spell or ability chooses an opponent. (This doesn't @@ -73,9 +67,7 @@ public class ClashEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller == null || sourceObject == null - || game.replaceEvent(GameEvent.getEvent( - GameEvent.EventType.CLASH, controller.getId(), source, controller.getId() - ))) { + ) { return false; } // choose opponent @@ -84,81 +76,6 @@ public class ClashEffect extends OneShotEffect { if (!controller.choose(Outcome.Benefit, target, source, game)) { return false; } - Player opponent = game.getPlayer(target.getFirstTarget()); - if (opponent == null) { - return false; - } - int cmcController = Integer.MIN_VALUE; - Card cardController = null; - boolean topController = true; - int cmcOpponent = Integer.MIN_VALUE; - Card cardOpponent = null; - boolean topOpponent = true; - // Reveal top cards of involved players - StringBuilder message = new StringBuilder("Clash: "); - message.append(controller.getLogName()); - if (controller.getLibrary().hasCards()) { - Cards cards = new CardsImpl(); - cardController = controller.getLibrary().getFromTop(game); - cards.add(cardController); - controller.revealCards(sourceObject.getIdName() + ": Clash card of " + controller.getName(), cards, game); - cmcController = cardController.getManaValue(); - message.append(" (").append(cmcController).append(')'); - } else { - message.append(" no card"); - } - message.append(" vs. ").append(opponent.getLogName()); - if (opponent.getLibrary().hasCards()) { - Cards cards = new CardsImpl(); - cardOpponent = opponent.getLibrary().getFromTop(game); - cards.add(cardOpponent); - opponent.revealCards(sourceObject.getIdName() + ": Clash card of " + opponent.getName(), cards, game); - cmcOpponent = cardOpponent.getManaValue(); - message.append(" (").append(cmcOpponent).append(')'); - } else { - message.append(" no card"); - } - message.append(" - "); - if (cmcController > cmcOpponent) { - message.append(controller.getLogName()).append(" won the clash"); - } else if (cmcController < cmcOpponent) { - message.append(opponent.getLogName()).append(" won the clash"); - } else { - message.append(" no winner "); - } - game.informPlayers(message.toString()); - // decide to put the cards on top or on the bottom of library in turn order beginning with the active player in turn order - for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { - Player player = game.getPlayer(playerId); - if (player == null) { - continue; - } - if (cardController != null && player.getId().equals(controller.getId())) { - topController = player.chooseUse(Outcome.Detriment, "Put " + cardController.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game); - } else if (cardOpponent != null && player.getId().equals(opponent.getId())) { - topOpponent = player.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game); - } - } - // put the cards back to library - if (cardController != null) { - controller.moveCardToLibraryWithInfo(cardController, source, game, Zone.LIBRARY, topController, true); - } - if (cardOpponent != null) { - opponent.moveCardToLibraryWithInfo(cardOpponent, source, game, Zone.LIBRARY, topOpponent, true); - } - - // fire CLASHED events with info about winner (flag is true if playerId won; other player is targetId) - game.fireEvent(new GameEvent( - GameEvent.EventType.CLASHED, opponent.getId(), source, - controller.getId(), 0, cmcController > cmcOpponent - )); - game.fireEvent(new GameEvent( - GameEvent.EventType.CLASHED, controller.getId(), source, - opponent.getId(), 0, cmcOpponent > cmcController - )); - - // set opponent to DoIfClashWonEffect - source.getEffects().setValue("clashOpponent", opponent.getId()); - return cmcController > cmcOpponent; + return new ClashTargetEffect().setTargetPointer(new FixedTarget(target.getFirstTarget())).apply(game, source); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ClashTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ClashTargetEffect.java new file mode 100644 index 00000000000..66cfa6c36d9 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ClashTargetEffect.java @@ -0,0 +1,156 @@ +package mage.abilities.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +import java.util.UUID; + +/** + * 1. The controller of the spell or ability chooses an opponent. (This doesn't + * target the opponent.) 2. Each player involved in the clash reveals the top + * card of their library. 3. The converted mana costs of the revealed cards + * are noted. 4. In turn order, each player involved in the clash chooses to put + * their revealed card on either the top or bottom of their library. + * (Note that the player whose turn it is does this first, not necessarily the + * controller of the clash spell or ability.) When the second player makes this + * decision, they will know what the first player chose. Then all cards are + * moved at the same time. 5. The clash is over. If one player in the clash + * revealed a card with a higher converted mana cost than all other cards + * revealed in the clash, that player wins the clash. 6. If any abilities + * trigger when a player clashes, they trigger and wait to be put on the stack. + * 7. The clash spell or ability finishes resolving. That usually involves a + * bonus gained by the controller of the clash spell or ability if they won + * the clash. 8. Abilities that triggered during the clash are put on the stack. + *
+ * There are no draws or losses in a clash. Either you win it or you don't. Each + * spell or ability with clash says what happens if you (the controller of that + * spell or ability) win the clash. Typically, if you don't win the clash, + * nothing happens. If no one reveals a card with a higher converted mana cost + * (for example, each player reveals a card with converted mana cost 2), no one + * wins the clash. An X in a revealed card's mana cost is treated as 0. A card + * without a mana cost (such as a land) has a converted mana cost of 0. If one + * or more of the clashing players reveals a split card, each of the split + * card's converted mana costs is considered individually. In this way, it's + * possible for multiple players to win a clash. For example, if Player A + * reveals a split card with converted mana costs 1 and 3, and Player B reveals + * a card with converted mana cost 2, they'll both win. (Player A's card has a + * higher converted mana cost than Player B's card, since 3 is greater than 2. + * Player B's card has a higher converted mana cost than Player A's card, since + * 2 is greater than 1.) + * + * @author LevelX2 + */ +public class ClashTargetEffect extends OneShotEffect { + + public ClashTargetEffect() { + super(Outcome.Benefit); + this.staticText = "Clash with an opponent"; + } + + protected ClashTargetEffect(final ClashTargetEffect effect) { + super(effect); + } + + @Override + public ClashTargetEffect copy() { + return new ClashTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null + || sourceObject == null + || game.replaceEvent(GameEvent.getEvent( + GameEvent.EventType.CLASH, controller.getId(), source, controller.getId() + ))) { + return false; + } + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (opponent == null) { + return false; + } + int cmcController = Integer.MIN_VALUE; + Card cardController = null; + boolean topController = true; + int cmcOpponent = Integer.MIN_VALUE; + Card cardOpponent = null; + boolean topOpponent = true; + // Reveal top cards of involved players + StringBuilder message = new StringBuilder("Clash: "); + message.append(controller.getLogName()); + if (controller.getLibrary().hasCards()) { + Cards cards = new CardsImpl(); + cardController = controller.getLibrary().getFromTop(game); + cards.add(cardController); + controller.revealCards(sourceObject.getIdName() + ": Clash card of " + controller.getName(), cards, game); + cmcController = cardController.getManaValue(); + message.append(" (").append(cmcController).append(')'); + } else { + message.append(" no card"); + } + message.append(" vs. ").append(opponent.getLogName()); + if (opponent.getLibrary().hasCards()) { + Cards cards = new CardsImpl(); + cardOpponent = opponent.getLibrary().getFromTop(game); + cards.add(cardOpponent); + opponent.revealCards(sourceObject.getIdName() + ": Clash card of " + opponent.getName(), cards, game); + cmcOpponent = cardOpponent.getManaValue(); + message.append(" (").append(cmcOpponent).append(')'); + } else { + message.append(" no card"); + } + message.append(" - "); + if (cmcController > cmcOpponent) { + message.append(controller.getLogName()).append(" won the clash"); + } else if (cmcController < cmcOpponent) { + message.append(opponent.getLogName()).append(" won the clash"); + } else { + message.append(" no winner "); + } + game.informPlayers(message.toString()); + // decide to put the cards on top or on the bottom of library in turn order beginning with the active player in turn order + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + if (cardController != null && player.getId().equals(controller.getId())) { + topController = player.chooseUse(Outcome.Detriment, "Put " + cardController.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game); + } else if (cardOpponent != null && player.getId().equals(opponent.getId())) { + topOpponent = player.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game); + } + } + // put the cards back to library + if (cardController != null) { + controller.moveCardToLibraryWithInfo(cardController, source, game, Zone.LIBRARY, topController, true); + } + if (cardOpponent != null) { + opponent.moveCardToLibraryWithInfo(cardOpponent, source, game, Zone.LIBRARY, topOpponent, true); + } + + // fire CLASHED events with info about winner (flag is true if playerId won; other player is targetId) + game.fireEvent(new GameEvent( + GameEvent.EventType.CLASHED, opponent.getId(), source, + controller.getId(), 0, cmcController > cmcOpponent + )); + game.fireEvent(new GameEvent( + GameEvent.EventType.CLASHED, controller.getId(), source, + opponent.getId(), 0, cmcOpponent > cmcController + )); + + // set opponent to DoIfClashWonEffect + source.getEffects().setValue("clashOpponent", opponent.getId()); + return cmcController > cmcOpponent; + } +}