forked from External/mage
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:
parent
0f987704bb
commit
d705fa0e41
6 changed files with 307 additions and 0 deletions
96
Mage.Sets/src/mage/cards/g/GreatIntelligencesPlan.java
Normal file
96
Mage.Sets/src/mage/cards/g/GreatIntelligencesPlan.java
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
111
Mage.Sets/src/mage/cards/t/TheValeyard.java
Normal file
111
Mage.Sets/src/mage/cards/t/TheValeyard.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
50
Mage/src/main/java/mage/choices/FaceVillainousChoice.java
Normal file
50
Mage/src/main/java/mage/choices/FaceVillainousChoice.java
Normal 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 — " + firstChoice.getRule() + ", or " + secondChoice.getRule();
|
||||
}
|
||||
}
|
||||
|
||||
40
Mage/src/main/java/mage/choices/VillainousChoice.java
Normal file
40
Mage/src/main/java/mage/choices/VillainousChoice.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue