Implement Villainous Choice mechanic (#11304)

* [WHO] Implement Great Intelligence's Plan

* [WHO] Implement The Valeyard

* add comment for villainous choice event
This commit is contained in:
Evan Kranzler 2023-10-14 15:27:45 -04:00 committed by GitHub
parent 0f987704bb
commit d705fa0e41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 307 additions and 0 deletions

View file

@ -0,0 +1,96 @@
package mage.cards.g;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.choices.FaceVillainousChoice;
import mage.choices.VillainousChoice;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetOpponent;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class GreatIntelligencesPlan extends CardImpl {
public GreatIntelligencesPlan(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{U}{B}");
// Draw three cards. Then target opponent faces a villainous choice -- They discard three cards, or you may cast a spell from your hand without paying its mana cost.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3));
this.getSpellAbility().addEffect(new GreatIntelligencesPlanEffect());
this.getSpellAbility().addTarget(new TargetOpponent());
}
private GreatIntelligencesPlan(final GreatIntelligencesPlan card) {
super(card);
}
@Override
public GreatIntelligencesPlan copy() {
return new GreatIntelligencesPlan(this);
}
}
class GreatIntelligencesPlanEffect extends OneShotEffect {
private static final FaceVillainousChoice choice = new FaceVillainousChoice(
Outcome.Discard, new GreatIntelligencesPlanFirstChoice(), new GreatIntelligencesPlanSecondChoice()
);
GreatIntelligencesPlanEffect() {
super(Outcome.Benefit);
staticText = "then target opponent " + choice.generateRule();
}
private GreatIntelligencesPlanEffect(final GreatIntelligencesPlanEffect effect) {
super(effect);
}
@Override
public GreatIntelligencesPlanEffect copy() {
return new GreatIntelligencesPlanEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
return player != null && choice.faceChoice(player, game, source);
}
}
class GreatIntelligencesPlanFirstChoice extends VillainousChoice {
GreatIntelligencesPlanFirstChoice() {
super("They discard three cards", "You discard three cards");
}
@Override
public boolean doChoice(Player player, Game game, Ability source) {
return !player.discard(3, false, false, source, game).isEmpty();
}
}
class GreatIntelligencesPlanSecondChoice extends VillainousChoice {
GreatIntelligencesPlanSecondChoice() {
super("you may cast a spell from your hand without paying its mana cost", "{controller} may cast a free spell from their hand");
}
@Override
public boolean doChoice(Player player, Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
return controller != null && !controller.getHand().isEmpty()
&& CardUtil.castSpellWithAttributesForFree(
controller, source, game, new CardsImpl(controller.getHand()), StaticFilters.FILTER_CARD
);
}
}

View file

@ -0,0 +1,111 @@
package mage.cards.t;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.VoteEvent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class TheValeyard extends CardImpl {
public TheValeyard(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.TIME_LORD);
this.subtype.add(SubType.NOBLE);
this.power = new MageInt(4);
this.toughness = new MageInt(5);
// If an opponent would face a villainous choice, they face that choice an additional time.
this.addAbility(new SimpleStaticAbility(new TheValeyardChoiceEffect()));
// While voting, you may vote an additional time.
this.addAbility(new SimpleStaticAbility(new TheValeyardVoteEffect()));
}
private TheValeyard(final TheValeyard card) {
super(card);
}
@Override
public TheValeyard copy() {
return new TheValeyard(this);
}
}
class TheValeyardChoiceEffect extends ReplacementEffectImpl {
TheValeyardChoiceEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "if an opponent would face a villainous choice, they face that choice an additional time";
}
private TheValeyardChoiceEffect(final TheValeyardChoiceEffect effect) {
super(effect);
}
@Override
public TheValeyardChoiceEffect copy() {
return new TheValeyardChoiceEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.FACE_VILLAINOUS_CHOICE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return game.getOpponents(event.getTargetId()).contains(source.getControllerId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(event.getAmount() + 1);
return false;
}
}
class TheValeyardVoteEffect extends ReplacementEffectImpl {
TheValeyardVoteEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "while voting, you may vote an additional time";
}
private TheValeyardVoteEffect(final TheValeyardVoteEffect effect) {
super(effect);
}
@Override
public TheValeyardVoteEffect copy() {
return new TheValeyardVoteEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.VOTE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return source.isControlledBy(event.getTargetId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
((VoteEvent) event).incrementOptionalExtraVotes();
return false;
}
}

View file

@ -93,6 +93,7 @@ public final class DoctorWho extends ExpansionSet {
cards.add(new SetCardInfo("Glacial Fortress", 285, Rarity.RARE, mage.cards.g.GlacialFortress.class));
cards.add(new SetCardInfo("Graham O'Brien", 104, Rarity.RARE, mage.cards.g.GrahamOBrien.class));
cards.add(new SetCardInfo("Grasp of Fate", 208, Rarity.RARE, mage.cards.g.GraspOfFate.class));
cards.add(new SetCardInfo("Great Intelligence's Plan", 133, Rarity.UNCOMMON, mage.cards.g.GreatIntelligencesPlan.class));
cards.add(new SetCardInfo("Growth Spiral", 237, Rarity.COMMON, mage.cards.g.GrowthSpiral.class));
cards.add(new SetCardInfo("Haunted Ridge", 286, Rarity.RARE, mage.cards.h.HauntedRidge.class));
cards.add(new SetCardInfo("Heaven Sent", 134, Rarity.RARE, mage.cards.h.HeavenSent.class));
@ -185,6 +186,7 @@ public final class DoctorWho extends ExpansionSet {
cards.add(new SetCardInfo("The Flux", 86, Rarity.RARE, mage.cards.t.TheFlux.class));
cards.add(new SetCardInfo("The Sixth Doctor", 159, Rarity.RARE, mage.cards.t.TheSixthDoctor.class));
cards.add(new SetCardInfo("The Thirteenth Doctor", 4, Rarity.MYTHIC, mage.cards.t.TheThirteenthDoctor.class));
cards.add(new SetCardInfo("The Valeyard", 165, Rarity.RARE, mage.cards.t.TheValeyard.class));
cards.add(new SetCardInfo("Thespian's Stage", 323, Rarity.RARE, mage.cards.t.ThespiansStage.class));
cards.add(new SetCardInfo("Think Twice", 220, Rarity.COMMON, mage.cards.t.ThinkTwice.class));
cards.add(new SetCardInfo("Thought Vessel", 255, Rarity.UNCOMMON, mage.cards.t.ThoughtVessel.class));

View file

@ -0,0 +1,50 @@
package mage.choices;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
/**
* @author TheElk801
*/
public class FaceVillainousChoice {
private final Outcome outcome;
private final VillainousChoice firstChoice;
private final VillainousChoice secondChoice;
public FaceVillainousChoice(Outcome outcome, VillainousChoice firstChoice, VillainousChoice secondChoice) {
this.outcome = outcome;
this.firstChoice = firstChoice;
this.secondChoice = secondChoice;
}
public boolean faceChoice(Player player, Game game, Ability source) {
GameEvent event = GameEvent.getEvent(
GameEvent.EventType.FACE_VILLAINOUS_CHOICE,
player.getId(), source, source.getControllerId(), 1
);
if (game.replaceEvent(event)) {
return false;
}
for (int i = 0; i < event.getAmount(); i++) {
handleChoice(player, game, source);
}
return true;
}
private boolean handleChoice(Player player, Game game, Ability source) {
VillainousChoice chosenChoice = player.chooseUse(
outcome, "You face a villanous choice:", null,
firstChoice.getMessage(game, source), secondChoice.getMessage(game, source), source, game
) ? firstChoice : secondChoice;
return chosenChoice.doChoice(player, game, source);
}
public String generateRule() {
return "faces a villanous choice &mdash; " + firstChoice.getRule() + ", or " + secondChoice.getRule();
}
}

View file

@ -0,0 +1,40 @@
package mage.choices;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import java.util.Objects;
import java.util.Optional;
/**
* @author TheElk801
*/
public abstract class VillainousChoice {
private final String rule;
private final String message;
protected VillainousChoice(String rule, String message) {
this.rule = rule;
this.message = message;
}
public abstract boolean doChoice(Player player, Game game, Ability source);
public String getRule() {
return rule;
}
public String getMessage(Game game, Ability source) {
if (!message.contains("{controller}")) {
return message;
}
String controllerName = Optional
.ofNullable(game.getPlayer(source.getControllerId()))
.filter(Objects::nonNull)
.map(Player::getName)
.orElse("Opponent");
return message.replace("{controller}", controllerName);
}
}

View file

@ -508,6 +508,14 @@ public class GameEvent implements Serializable {
REMOVED_FROM_COMBAT, // targetId id of permanent removed from combat
FORETOLD, // targetId id of card foretold
FORETELL, // targetId id of card foretell playerId id of the controller
/* villainous choice
targetId player making the choice
sourceId sourceId of the ability forcing the choice
playerId controller of the ability forcing the choice
amount numner of times choice is repeated
flag not used for this event
*/
FACE_VILLAINOUS_CHOICE,
//custom events
CUSTOM_EVENT
}