[MKC] Implement Marvo, Deep Operative (plus refactor ClashEffect to allow for designated Clashes)

This commit is contained in:
Grath 2024-08-08 21:37:35 -04:00 committed by GitHub
parent 3c8b1b3233
commit ebca45987a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 252 additions and 86 deletions

View file

@ -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.";
}
}

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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.
* <p>
* 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;
}
}