Improving implementation of cards which use voting (WIP) (#7566)

* created interface for handling voting

* created class for two choice votes, refactored a card to use it

* refactored all cards which use two choice votes

* updated VoteHandler to an abstract class to encapsulate more of its functions

* refactored cards which vote for more than two things

* [CNS] Implemented Brago's Representative

* [CN2] Implemented Ballot Broker

* [CN2] Implemented Illusion of Choice

* [CNS] Implemented Grudge Keeper

* added vote outcomes

* updated implementation of Illusion of Choice to work correctly in multiples

* added test for voting

* updated implementation of extra votes

* simplified vote message handling

* Improved names, additional comments

* Votes: fixed not working getMostVoted

* Votes: added final vote results to game logs;

* Votes: added additional info for the vote choices;

* Votes: added vote step info in choose dialogs, added AI support example for Tyrant's Choice;

Co-authored-by: Oleg Agafonov <jaydi85@gmail.com>
This commit is contained in:
Evan Kranzler 2021-03-20 10:32:54 -04:00 committed by GitHub
parent 991f154cd7
commit 1cbbcddcc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1615 additions and 786 deletions

View file

@ -0,0 +1,77 @@
package mage.cards.b;
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.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.VoteEvent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BallotBroker extends CardImpl {
public BallotBroker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ADVISOR);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// While voting, you may vote an additional time.
this.addAbility(new SimpleStaticAbility(new BallotBrokerReplacementEffect()));
}
private BallotBroker(final BallotBroker card) {
super(card);
}
@Override
public BallotBroker copy() {
return new BallotBroker(this);
}
}
class BallotBrokerReplacementEffect extends ReplacementEffectImpl {
BallotBrokerReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "while voting, you may vote an additional time";
}
private BallotBrokerReplacementEffect(final BallotBrokerReplacementEffect effect) {
super(effect);
}
@Override
public BallotBrokerReplacementEffect copy() {
return new BallotBrokerReplacementEffect(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

@ -1,15 +1,13 @@
package mage.cards.b;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostOpponentsEffect;
import mage.abilities.effects.common.discard.DiscardEachPlayerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
@ -17,14 +15,15 @@ import mage.constants.TargetController;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class BiteOfTheBlackRose extends CardImpl {
public BiteOfTheBlackRose(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{B}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}");
// Will of the council - Starting with you, each player votes for sickness or psychosis. If sickness gets more votes, creatures your opponents control get -2/-2 until end of turn. If psychosis gets more votes or the vote is tied, each opponent discards two cards.
this.getSpellAbility().addEffect(new BiteOfTheBlackRoseEffect());
@ -44,10 +43,13 @@ class BiteOfTheBlackRoseEffect extends OneShotEffect {
BiteOfTheBlackRoseEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, each player votes for sickness or psychosis. If sickness gets more votes, creatures your opponents control get -2/-2 until end of turn. If psychosis gets more votes or the vote is tied, each opponent discards two cards";
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, " +
"each player votes for sickness or psychosis. If sickness gets more votes, " +
"creatures your opponents control get -2/-2 until end of turn. " +
"If psychosis gets more votes or the vote is tied, each opponent discards two cards";
}
BiteOfTheBlackRoseEffect(final BiteOfTheBlackRoseEffect effect) {
private BiteOfTheBlackRoseEffect(final BiteOfTheBlackRoseEffect effect) {
super(effect);
}
@ -59,29 +61,24 @@ class BiteOfTheBlackRoseEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int sicknessCount = 0;
int psychosisCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.ExtraTurn, "Choose sickness?", source, game)) {
sicknessCount++;
game.informPlayers(player.getLogName() + " has voted for sickness");
} else {
psychosisCount++;
game.informPlayers(player.getLogName() + " has voted for psychosis");
}
}
}
if (sicknessCount > psychosisCount) {
ContinuousEffect effect = new BoostOpponentsEffect(-2, -2, Duration.EndOfTurn);
game.addEffect(effect, source);
} else {
new DiscardEachPlayerEffect(StaticValue.get(2), false, TargetController.OPPONENT).apply(game, source);
}
return true;
if (controller == null) {
return false;
}
return false;
// Outcome.Detriment - AI will discard a card all the time (Psychosis choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Sickness (-2/-2)", "Psychosis (discard cards)", Outcome.Detriment);
vote.doVotes(source, game);
int sicknessCount = vote.getVoteCount(true);
int psychosisCount = vote.getVoteCount(false);
if (sicknessCount > psychosisCount) {
// sickness
game.addEffect(new BoostOpponentsEffect(-2, -2, Duration.EndOfTurn), source);
} else {
// psychosis or tied
new DiscardEachPlayerEffect(StaticValue.get(2), false, TargetController.OPPONENT).apply(game, source);
}
return true;
}
}

View file

@ -0,0 +1,77 @@
package mage.cards.b;
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.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.VoteEvent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BragosRepresentative extends CardImpl {
public BragosRepresentative(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ADVISOR);
this.power = new MageInt(1);
this.toughness = new MageInt(4);
// While voting, you get an additional vote.
this.addAbility(new SimpleStaticAbility(new BragosRepresentativeReplacementEffect()));
}
private BragosRepresentative(final BragosRepresentative card) {
super(card);
}
@Override
public BragosRepresentative copy() {
return new BragosRepresentative(this);
}
}
class BragosRepresentativeReplacementEffect extends ReplacementEffectImpl {
BragosRepresentativeReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "while voting, you get an additional vote";
}
private BragosRepresentativeReplacementEffect(final BragosRepresentativeReplacementEffect effect) {
super(effect);
}
@Override
public BragosRepresentativeReplacementEffect copy() {
return new BragosRepresentativeReplacementEffect(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).incrementExtraVotes();
return false;
}
}

View file

@ -1,24 +1,23 @@
package mage.cards.c;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CouncilsDilemmaVoteEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.SacrificeOpponentsEffect;
import mage.abilities.effects.common.discard.DiscardEachPlayerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author JRHerlehy
* @author JRHerlehy, TheElk801
*/
public final class CapitalPunishment extends CardImpl {
@ -26,7 +25,7 @@ public final class CapitalPunishment extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}");
// <i>Council's dilemma</i> &mdash; Starting with you, each player votes for death or taxes. Each opponent sacrifices a creature for each death vote and discards a card for each taxes vote.
this.getSpellAbility().addEffect(new CapitalPunishmentDilemmaEffect());
this.getSpellAbility().addEffect(new CapitalPunishmentEffect());
}
private CapitalPunishment(final CapitalPunishment card) {
@ -39,45 +38,42 @@ public final class CapitalPunishment extends CardImpl {
}
}
class CapitalPunishmentDilemmaEffect extends CouncilsDilemmaVoteEffect {
class CapitalPunishmentEffect extends OneShotEffect {
public CapitalPunishmentDilemmaEffect() {
super(Outcome.Detriment);
this.staticText = "<i>Council's dilemma</i> &mdash; Starting with you, each player votes for death or taxes. Each opponent sacrifices a creature for each death vote and discards a card for each taxes vote";
CapitalPunishmentEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Council's dilemma</i> &mdash; Starting with you, each player votes for death or taxes. " +
"Each opponent sacrifices a creature for each death vote and discards a card for each taxes vote";
}
public CapitalPunishmentDilemmaEffect(final CapitalPunishmentDilemmaEffect effect) {
private CapitalPunishmentEffect(final CapitalPunishmentEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
//If no controller, exit out here and do not vote.
if (controller == null) {
return false;
}
this.vote("death", "taxes", controller, game, source);
//Death Votes
if (voteOneCount > 0) {
Effect sacrificeEffect = new SacrificeOpponentsEffect(voteOneCount, StaticFilters.FILTER_CONTROLLED_CREATURE);
sacrificeEffect.apply(game, source);
}
//Taxes Votes
if (voteTwoCount > 0) {
Effect discardEffect = new DiscardEachPlayerEffect(StaticValue.get(voteTwoCount), false, TargetController.OPPONENT);
discardEffect.apply(game, source);
}
return true;
public CapitalPunishmentEffect copy() {
return new CapitalPunishmentEffect(this);
}
@Override
public CapitalPunishmentDilemmaEffect copy() {
return new CapitalPunishmentDilemmaEffect(this);
public boolean apply(Game game, Ability source) {
// Outcome.Detriment - AI will discard a card all the time (taxes choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Death (sacrifice creature)", "Taxes (discard card)", Outcome.Detriment);
vote.doVotes(source, game);
int deathCount = vote.getVoteCount(true);
int taxesCount = vote.getVoteCount(false);
if (deathCount > 0) {
new SacrificeOpponentsEffect(
deathCount, StaticFilters.FILTER_CONTROLLED_CREATURE
).apply(game, source);
}
if (taxesCount > 0) {
new DiscardEachPlayerEffect(
StaticValue.get(taxesCount), false, TargetController.OPPONENT
).apply(game, source);
}
return true;
}
}

View file

@ -1,32 +1,39 @@
package mage.cards.c;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DestroyAllEffect;
import mage.abilities.effects.common.SacrificeSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.filter.common.FilterNonlandPermanent;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author fireshoes
* @author fireshoes, TheElk801
*/
public final class CoercivePortal extends CardImpl {
public CoercivePortal(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}");
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
// Will of the council - At the beginning of your upkeep, starting with you, each player votes for carnage or homage. If carnage gets more votes, sacrifice Coercive Portal and destroy all nonland permanents. If homage gets more votes or the vote is tied, draw a card.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CoercivePortalEffect(), TargetController.YOU, false));
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
Zone.BATTLEFIELD, new CoercivePortalEffect(), TargetController.YOU,
false, false, "<i>Will of the council</i> &mdash; " +
"At the beginning of your upkeep, starting with you, each player votes for carnage or homage. " +
"If carnage gets more votes, sacrifice {this} and destroy all nonland permanents. " +
"If homage gets more votes or the vote is tied, draw a card"
));
}
private CoercivePortal(final CoercivePortal card) {
@ -43,10 +50,9 @@ class CoercivePortalEffect extends OneShotEffect {
CoercivePortalEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; At the beginning of your upkeep, starting with you, each player votes for carnage or homage. If carnage gets more votes, sacrifice Coercive Portal and destroy all nonland permanents. If homage gets more votes or the vote is tied, draw a card";
}
CoercivePortalEffect(final CoercivePortalEffect effect) {
private CoercivePortalEffect(final CoercivePortalEffect effect) {
super(effect);
}
@ -57,30 +63,27 @@ class CoercivePortalEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int carnageCount = 0;
int homageCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.DestroyPermanent, "Choose carnage?", source, game)) {
carnageCount++;
game.informPlayers(player.getLogName() + " has voted for carnage");
} else {
homageCount++;
game.informPlayers(player.getLogName() + " has voted for homage");
}
}
// Outcome.Detriment - AI will draw a card all the time (Homage choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Carnage (sacrifice and destroy)", "Homage (draw a card)", Outcome.Detriment);
vote.doVotes(source, game);
int carnageCount = vote.getVoteCount(true);
int homageCount = vote.getVoteCount(false);
if (carnageCount > homageCount) {
// carnage
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent != null && permanent.isControlledBy(source.getControllerId())) {
permanent.sacrifice(source, game);
}
if (carnageCount > homageCount) {
new SacrificeSourceEffect().apply(game, source);
new DestroyAllEffect(new FilterNonlandPermanent()).apply(game, source);
} else {
controller.drawCards(1, source, game);
new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_NON_LAND).apply(game, source);
} else {
// homage or tied
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
player.drawCards(1, source, game);
}
return true;
}
return false;
return true;
}
}

View file

@ -1,7 +1,6 @@
package mage.cards.c;
import mage.MageInt;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
@ -10,6 +9,7 @@ import mage.abilities.keyword.ProtectionAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.ChoiceColor;
import mage.choices.VoteHandler;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
@ -17,12 +17,13 @@ import mage.constants.SubType;
import mage.game.Game;
import mage.players.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author Styxo
* @author Styxo, TheElk801
*/
public final class CouncilGuardian extends CardImpl {
@ -35,8 +36,9 @@ public final class CouncilGuardian extends CardImpl {
this.toughness = new MageInt(5);
// Will of the council - When Council Guardian enters the battlefield, starting with you, each player votes for blue, black, red, or green. Council Guardian gains protection from each color with the most votes or tied for most votes.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CouncilsGuardianEffect(), false, "<i>Will of the council</i> &mdash; "));
this.addAbility(new EntersBattlefieldTriggeredAbility(
new CouncilsGuardianEffect(), false, "<i>Will of the council</i> &mdash; "
));
}
private CouncilGuardian(final CouncilGuardian card) {
@ -51,12 +53,13 @@ public final class CouncilGuardian extends CardImpl {
class CouncilsGuardianEffect extends OneShotEffect {
public CouncilsGuardianEffect() {
CouncilsGuardianEffect() {
super(Outcome.Benefit);
this.staticText = "starting with you, each player votes for blue, black, red, or green. {this} gains protection from each color with the most votes or tied for most votes";
this.staticText = "starting with you, each player votes for blue, black, red, or green. " +
"{this} gains protection from each color with the most votes or tied for most votes";
}
public CouncilsGuardianEffect(final CouncilsGuardianEffect effect) {
private CouncilsGuardianEffect(final CouncilsGuardianEffect effect) {
super(effect);
}
@ -67,45 +70,39 @@ class CouncilsGuardianEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
ChoiceColor choice = new ChoiceColor();
choice.getChoices().remove("White");
if (controller != null) {
Map<ObjectColor, Integer> chosenColors = new HashMap<>(2);
int maxCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
choice.clearChoice();
if (player.choose(Outcome.Detriment, choice, game)) {
ObjectColor color = choice.getColor();
if (color != null) {
if (chosenColors.containsKey(color)) {
int count = chosenColors.get(color) + 1;
if (count > maxCount) {
maxCount = count;
}
chosenColors.put(color, count);
} else {
if (maxCount == 0) {
maxCount = 1;
}
chosenColors.put(color, 1);
}
game.informPlayers(player.getLogName() + " has chosen " + color.getDescription() + '.');
}
}
}
}
CouncilGuardianVote vote = new CouncilGuardianVote();
vote.doVotes(source, game);
for (Map.Entry<ObjectColor, Integer> entry : chosenColors.entrySet()) {
if (entry.getValue() == maxCount) {
ObjectColor color = entry.getKey();
game.addEffect(new GainAbilitySourceEffect(ProtectionAbility.from(color), Duration.Custom), source);
}
for (String color : vote.getMostVoted()) {
if (color == null) {
continue;
}
return true;
game.addEffect(new GainAbilitySourceEffect(
ProtectionAbility.from(ChoiceColor.getColorFromString(color)), Duration.Custom
), source);
}
return false;
return true;
}
}
class CouncilGuardianVote extends VoteHandler<String> {
@Override
protected Set<String> getPossibleVotes(Ability source, Game game) {
return new LinkedHashSet<>(Arrays.asList("Blue", "Black", "Red", "Green"));
}
@Override
public String playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) {
ChoiceColor choice = new ChoiceColor();
choice.getChoices().remove("White");
choice.setSubMessage(voteInfo);
decidingPlayer.choose(Outcome.AIDontUseIt, choice, game); // TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
return choice.getChoice();
}
@Override
protected String voteName(String vote) {
return vote;
}
}

View file

@ -1,16 +1,16 @@
package mage.cards.c;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.choices.VoteHandler;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.ControllerIdPredicate;
@ -18,11 +18,14 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetNonlandPermanent;
import mage.target.TargetPermanent;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author emerald000
* @author emerald000, TheElk801
*/
public final class CouncilsJudgment extends CardImpl {
@ -47,10 +50,11 @@ class CouncilsJudgmentEffect extends OneShotEffect {
CouncilsJudgmentEffect() {
super(Outcome.Exile);
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, each player votes for a nonland permanent you don't control. Exile each permanent with the most votes or tied for most votes";
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, each player votes for a " +
"nonland permanent you don't control. Exile each permanent with the most votes or tied for most votes";
}
CouncilsJudgmentEffect(final CouncilsJudgmentEffect effect) {
private CouncilsJudgmentEffect(final CouncilsJudgmentEffect effect) {
super(effect);
}
@ -61,48 +65,48 @@ class CouncilsJudgmentEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Map<Permanent, Integer> chosenCards = new HashMap<>(2);
int maxCount = 0;
FilterNonlandPermanent filter = new FilterNonlandPermanent("a nonland permanent " + controller.getLogName() + " doesn't control");
filter.add(Predicates.not(new ControllerIdPredicate(controller.getId())));
//Players each choose a legal permanent
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
Target target = new TargetNonlandPermanent(filter);
target.setNotTarget(true);
if (player.choose(Outcome.Exile, target, source.getSourceId(), game)) {
Permanent permanent = game.getPermanent(target.getFirstTarget());
if (permanent != null) {
if (chosenCards.containsKey(permanent)) {
int count = chosenCards.get(permanent) + 1;
if (count > maxCount) {
maxCount = count;
}
chosenCards.put(permanent, count);
} else {
if (maxCount == 0) {
maxCount = 1;
}
chosenCards.put(permanent, 1);
}
game.informPlayers(player.getLogName() + " has chosen: " + permanent.getLogName());
}
}
}
}
//Exile the card(s) with the most votes.
for (Entry<Permanent, Integer> entry : chosenCards.entrySet()) {
if (entry.getValue() == maxCount) {
Permanent permanent = entry.getKey();
controller.moveCardToExileWithInfo(permanent, null, "", source, game, Zone.BATTLEFIELD, true);
}
}
return true;
Player player = game.getPlayer(source.getSourceId());
if (player == null) {
return false;
}
return false;
CouncilsJudgmentVote vote = new CouncilsJudgmentVote(player);
vote.doVotes(source, game);
Cards cards = new CardsImpl();
vote.getMostVoted().stream().forEach(cards::add);
return player.moveCards(cards, Zone.EXILED, source, game);
}
}
class CouncilsJudgmentVote extends VoteHandler<Permanent> {
private final FilterPermanent filter;
CouncilsJudgmentVote(Player controller) {
this.filter = new FilterNonlandPermanent("nonland permanent not controlled by " + controller.getName());
this.filter.add(Predicates.not(new ControllerIdPredicate(controller.getId())));
}
@Override
protected Set<Permanent> getPossibleVotes(Ability source, Game game) {
// too much permanentns on battlefield, so no need to show full list here
return new LinkedHashSet<>();
}
@Override
public Permanent playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) {
if (game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game) < 1) {
return null;
}
TargetPermanent target = new TargetPermanent(1, filter);
target.withChooseHint(voteInfo + " (to exile)");
target.setNotTarget(true);
decidingPlayer.choose(Outcome.Exile, target, source.getSourceId(), game);
return game.getPermanent(target.getFirstTarget());
}
@Override
protected String voteName(Permanent vote) {
return vote.getIdName();
}
}

View file

@ -1,4 +1,3 @@
package mage.cards.c;
import mage.MageInt;
@ -6,29 +5,31 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.*;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.VoteHandler;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInGraveyard;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author LevelX2
* @author LevelX2, TheElk801
*/
public final class CustodiSquire extends CardImpl {
public CustodiSquire(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}");
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.CLERIC);
this.power = new MageInt(3);
@ -37,7 +38,9 @@ public final class CustodiSquire extends CardImpl {
// Flying
this.addAbility(FlyingAbility.getInstance());
// Will of the council - When Custodi Squire enters the battlefield, starting with you, each player votes for an artifact, creature, or enchantment card in your graveyard. Return each card with the most votes or tied for most votes to your hand.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CustodiSquireVoteEffect(), false, true));
this.addAbility(new EntersBattlefieldTriggeredAbility(
new CustodiSquireVoteEffect(), false, true
));
}
private CustodiSquire(final CustodiSquire card) {
@ -52,20 +55,14 @@ public final class CustodiSquire extends CardImpl {
class CustodiSquireVoteEffect extends OneShotEffect {
private static final FilterCard filter = new FilterCard("artifact, creature, or enchantment card from your graveyard");
static {
filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(),
CardType.CREATURE.getPredicate(),
CardType.ENCHANTMENT.getPredicate()));
}
CustodiSquireVoteEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; When {this} enters the battlefield, starting with you, each player votes for an artifact, creature, or enchantment card in your graveyard. Return each card with the most votes or tied for most votes to your hand";
this.staticText = "<i>Will of the council</i> &mdash; When {this} enters the battlefield, " +
"starting with you, each player votes for an artifact, creature, or enchantment card in your graveyard. " +
"Return each card with the most votes or tied for most votes to your hand";
}
CustodiSquireVoteEffect(final CustodiSquireVoteEffect effect) {
private CustodiSquireVoteEffect(final CustodiSquireVoteEffect effect) {
super(effect);
}
@ -76,47 +73,52 @@ class CustodiSquireVoteEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Cards possibleCards = new CardsImpl();
possibleCards.addAll(controller.getGraveyard().getCards(filter, game));
if (!possibleCards.isEmpty()) {
Map<UUID, Integer> cardCounter = new HashMap<>();
TargetCard target = new TargetCard(1, 1, Zone.GRAVEYARD, filter);
int maxCount = 1;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
target.clearChosen();
player.chooseTarget(outcome, possibleCards, target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
game.informPlayers(player.getLogName() + " voted for " + card.getLogName());
if (!cardCounter.containsKey(target.getFirstTarget())) {
cardCounter.put(target.getFirstTarget(), 1);
} else {
int count = cardCounter.get(target.getFirstTarget()) + 1;
if (count > maxCount) {
maxCount = count;
}
cardCounter.put(target.getFirstTarget(), count);
}
}
}
}
Cards cardsToMove = new CardsImpl();
for (UUID uuid : possibleCards) {
if (cardCounter.containsKey(uuid)) {
if (cardCounter.get(uuid) == maxCount) {
cardsToMove.add(uuid);
}
}
}
controller.moveCards(cardsToMove, Zone.HAND, source, game);
}
return true;
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
return false;
CustodiSquireVote vote = new CustodiSquireVote();
vote.doVotes(source, game);
return player.moveCards(vote.getMostVoted(), Zone.HAND, source, game);
}
}
class CustodiSquireVote extends VoteHandler<Card> {
private static final FilterCard filter = new FilterCard("artifact, creature, or enchantment card");
static {
filter.add(Predicates.or(
CardType.ARTIFACT.getPredicate(),
CardType.CREATURE.getPredicate(),
CardType.ENCHANTMENT.getPredicate()
));
}
@Override
protected Set<Card> getPossibleVotes(Ability source, Game game) {
// too much permanentns on battlefield, so no need to show full list here
return new LinkedHashSet<>();
}
@Override
public Card playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null || controller.getGraveyard().count(
filter, source.getSourceId(), source.getControllerId(), game
) < 1) {
return null;
}
TargetCardInGraveyard target = new TargetCardInGraveyard(filter);
target.withChooseHint(voteInfo + " (from graveyard to hand)");
target.setNotTarget(true);
decidingPlayer.choose(Outcome.ReturnToHand, controller.getGraveyard(), target, game);
return controller.getGraveyard().get(target.getFirstTarget(), game);
}
@Override
protected String voteName(Card vote) {
return vote.getIdName();
}
}

View file

@ -1,28 +1,31 @@
package mage.cards.e;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.common.CouncilsDilemmaVoteEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterPermanent;
import mage.filter.predicate.card.OwnerIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.turn.TurnMod;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.targetpointer.FixedTarget;
import mage.target.targetpointer.FixedTargets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* @author JRHerlehy
* @author JRHerlehy, TheElk801
*/
public final class Expropriate extends CardImpl {
@ -31,7 +34,7 @@ public final class Expropriate extends CardImpl {
// <i>Council's dilemma</i> &mdash; Starting with you, each player votes for time or money. For each time vote,
// take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate
this.getSpellAbility().addEffect(new ExpropriateDilemmaEffect());
this.getSpellAbility().addEffect(new ExpropriateEffect());
this.getSpellAbility().addEffect(ExileSpellEffect.getInstance());
}
@ -45,138 +48,69 @@ public final class Expropriate extends CardImpl {
}
}
class ExpropriateDilemmaEffect extends CouncilsDilemmaVoteEffect {
class ExpropriateEffect extends OneShotEffect {
private ArrayList<UUID> choiceTwoVoters = new ArrayList<>();
public ExpropriateDilemmaEffect() {
ExpropriateEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Council's dilemma</i> &mdash; Starting with you, each player votes for time or money. For each time vote, take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it";
staticText = "<i>Council's dilemma</i> &mdash; Starting with you, each player votes for time or money. " +
"For each time vote, take an extra turn after this one. For each money vote, " +
"choose a permanent owned by the voter and gain control of it";
}
public ExpropriateDilemmaEffect(final ExpropriateDilemmaEffect effect) {
private ExpropriateEffect(final ExpropriateEffect effect) {
super(effect);
this.choiceTwoVoters.addAll(effect.choiceTwoVoters);
}
@Override
public ExpropriateEffect copy() {
return new ExpropriateEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
//If not controller, exit out here and do not vote.
if (controller == null) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
this.vote("time", "money", controller, game, source);
// Outcome.Detriment - AI will gain control all the time (Money choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Time (extra turn)", "Money (gain control)", Outcome.Detriment);
vote.doVotes(source, game);
//Time Votes
if (voteOneCount > 0) {
this.turnsForTimeVote(voteOneCount, controller, game, source);
}
//Money Votes
if (voteTwoCount > 0) {
this.controlForMoneyVote(controller, game, source);
}
return true;
}
private void turnsForTimeVote(int timeCount, Player controller, Game game, Ability source) {
if (timeCount == 1) {
game.informPlayers(controller.getLogName() + " will take an extra turn");
} else {
game.informPlayers(controller.getLogName() + " will take " + timeCount + " extra turns");
}
do {
// extra turn
int timeCount = vote.getVoteCount(true);
for (int i = 0; i < timeCount; i++) {
game.getState().getTurnMods().add(new TurnMod(source.getControllerId(), false));
timeCount--;
} while (timeCount > 0);
}
}
private void controlForMoneyVote(Player controller, Game game, Ability source) {
List<Permanent> chosenCards = new ArrayList<>();
for (UUID playerId : choiceTwoVoters) {
FilterPermanent filter = new FilterPermanent("permanent owned by " + game.getPlayer(playerId).getName());
filter.add(new OwnerIdPredicate(playerId));
Target target = new TargetPermanent(filter);
// gain control
if (vote.getVoteCount(false) < 1) {
return true;
}
List<Permanent> toSteal = new ArrayList<>();
for (UUID playerId : vote.getVotedFor(false)) {
int moneyCount = vote.getVotes(playerId).stream().mapToInt(x -> x ? 0 : 1).sum();
FilterPermanent filter = new FilterPermanent();
filter.add(new ControllerIdPredicate(playerId));
moneyCount = Math.min(game.getBattlefield().count(
filter, source.getSourceId(), source.getControllerId(), game
), moneyCount);
if (moneyCount == 0) {
continue;
}
TargetPermanent target = new TargetPermanent(moneyCount, filter);
target.setNotTarget(true);
if (controller != null
&& controller.chooseTarget(Outcome.GainControl, target, source, game)) {
Permanent targetPermanent = game.getPermanent(target.getFirstTarget());
if (targetPermanent != null) {
chosenCards.add(targetPermanent);
}
}
}
if (controller != null) {
for (Permanent permanent : chosenCards) {
ContinuousEffect effect = new ExpropriateControlEffect(controller.getId());
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, source);
game.informPlayers(controller.getLogName() + " gained control of " + permanent.getIdName() + " owned by " + game.getPlayer(permanent.getOwnerId()).getName());
}
}
}
@Override
protected void vote(String choiceOne, String choiceTwo, Player controller, Game game, Ability source) {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.Vote,
"Choose " + choiceOne + " or " + choiceTwo + "?",
source.getRule(), choiceOne, choiceTwo, source, game)) {
voteOneCount++;
game.informPlayers(player.getLogName() + " has voted for " + choiceOne);
} else {
voteTwoCount++;
game.informPlayers(player.getLogName() + " has voted for " + choiceTwo);
choiceTwoVoters.add(player.getId());
}
}
}
}
@Override
public ExpropriateDilemmaEffect copy() {
return new ExpropriateDilemmaEffect(this);
}
}
class ExpropriateControlEffect extends ContinuousEffectImpl {
private final UUID controllerId;
public ExpropriateControlEffect(UUID controllerId) {
super(Duration.EndOfGame, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl);
this.controllerId = controllerId;
}
public ExpropriateControlEffect(final ExpropriateControlEffect effect) {
super(effect);
this.controllerId = effect.controllerId;
}
@Override
public ExpropriateControlEffect copy() {
return new ExpropriateControlEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source));
if (permanent == null || controllerId == null) {
this.discard();
} else {
permanent.changeControllerId(controllerId, game, source);
player.choose(Outcome.GainControl, target, source.getSourceId(), game);
target.getTargets()
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.forEach(toSteal::add);
}
game.addEffect(new GainControlTargetEffect(
Duration.Custom, true, source.getControllerId()
).setTargetPointer(new FixedTargets(toSteal, game)), source);
return true;
}
}

View file

@ -0,0 +1,112 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.VotedEvent;
import mage.players.Player;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class GrudgeKeeper extends CardImpl {
public GrudgeKeeper(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}");
this.subtype.add(SubType.ZOMBIE);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// Whenever players finish voting, each opponent who voted for a choice you didn't vote for loses 2 life.
this.addAbility(new GrudgeKeeperTriggeredAbility());
}
private GrudgeKeeper(final GrudgeKeeper card) {
super(card);
}
@Override
public GrudgeKeeper copy() {
return new GrudgeKeeper(this);
}
}
class GrudgeKeeperTriggeredAbility extends TriggeredAbilityImpl {
GrudgeKeeperTriggeredAbility() {
super(Zone.BATTLEFIELD, null, false);
}
private GrudgeKeeperTriggeredAbility(final GrudgeKeeperTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.VOTED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
VotedEvent votedEvent = (VotedEvent) event;
this.getEffects().clear();
this.addEffect(new GrudgeKeeperEffect(votedEvent.getDidntVote(getControllerId())));
return true;
}
@Override
public GrudgeKeeperTriggeredAbility copy() {
return new GrudgeKeeperTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever players finish voting, each opponent who voted for a choice you didn't vote for loses 2 life.";
}
}
class GrudgeKeeperEffect extends OneShotEffect {
private final Set<UUID> playerIds = new HashSet<>();
GrudgeKeeperEffect(Set<UUID> playerIds) {
super(Outcome.Benefit);
this.playerIds.addAll(playerIds);
}
private GrudgeKeeperEffect(final GrudgeKeeperEffect effect) {
super(effect);
this.playerIds.addAll(effect.playerIds);
}
@Override
public GrudgeKeeperEffect copy() {
return new GrudgeKeeperEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : playerIds) {
Player player = game.getPlayer(playerId);
if (player != null && player.hasOpponent(source.getControllerId(), game)) {
player.loseLife(2, game, source, false);
}
}
return true;
}
}

View file

@ -0,0 +1,67 @@
package mage.cards.i;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class IllusionOfChoice extends CardImpl {
public IllusionOfChoice(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}");
// You choose how each player votes this turn.
this.getSpellAbility().addEffect(new IllusionOfChoiceReplacementEffect());
// Draw a card.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1));
}
private IllusionOfChoice(final IllusionOfChoice card) {
super(card);
}
@Override
public IllusionOfChoice copy() {
return new IllusionOfChoice(this);
}
}
class IllusionOfChoiceReplacementEffect extends ContinuousRuleModifyingEffectImpl {
IllusionOfChoiceReplacementEffect() {
super(Duration.EndOfTurn, Outcome.Benefit);
staticText = "you choose how each player votes this turn";
}
private IllusionOfChoiceReplacementEffect(final IllusionOfChoiceReplacementEffect effect) {
super(effect);
}
@Override
public IllusionOfChoiceReplacementEffect copy() {
return new IllusionOfChoiceReplacementEffect(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) {
event.setPlayerId(source.getControllerId());
return false;
}
}

View file

@ -1,14 +1,12 @@
package mage.cards.l;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CouncilsDilemmaVoteEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
@ -16,11 +14,11 @@ import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.SoldierToken;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author JRHerlehy
* @author JRHerlehy, TheElk801
*/
public final class LieutenantsOfTheGuard extends CardImpl {
@ -35,7 +33,9 @@ public final class LieutenantsOfTheGuard extends CardImpl {
// <i>Council's dilemma</i> &mdash; When Lieutenants of the Guard enters the battlefield, starting with you,
// each player votes for strength or numbers. Put a +1/+1 counter on Lieutenants of the Guard for each
// strength vote and put a 1/1 white Soldier creature token onto the battlefield for each numbers vote.
this.addAbility(new EntersBattlefieldTriggeredAbility(new LieutenantsOfTheGuardDilemmaEffect(), false, "<i>Council's dilemma</i> &mdash; "));
this.addAbility(new EntersBattlefieldTriggeredAbility(
new LieutenantsOfTheGuardEffect(), false, "<i>Council's dilemma</i> &mdash; "
));
}
private LieutenantsOfTheGuard(final LieutenantsOfTheGuard card) {
@ -48,47 +48,40 @@ public final class LieutenantsOfTheGuard extends CardImpl {
}
}
class LieutenantsOfTheGuardDilemmaEffect extends CouncilsDilemmaVoteEffect {
class LieutenantsOfTheGuardEffect extends OneShotEffect {
public LieutenantsOfTheGuardDilemmaEffect() {
LieutenantsOfTheGuardEffect() {
super(Outcome.Benefit);
this.staticText = "starting with you, each player votes for strength or numbers. Put a +1/+1 counter on {this} for each strength vote and put a 1/1 white Soldier creature token onto the battlefield for each numbers vote.";
this.staticText = "starting with you, each player votes for strength or numbers. " +
"Put a +1/+1 counter on {this} for each strength vote " +
"and create a 1/1 white Soldier creature token for each numbers vote.";
}
public LieutenantsOfTheGuardDilemmaEffect(final LieutenantsOfTheGuardDilemmaEffect effect) {
private LieutenantsOfTheGuardEffect(final LieutenantsOfTheGuardEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
//If no controller, exit out here and do not vote.
if (controller == null) {
return false;
}
this.vote("strength", "numbers", controller, game, source);
Permanent permanent = game.getPermanent(source.getSourceId());
//Strength Votes
//If strength received zero votes or the permanent is no longer on the battlefield, do not attempt to put P1P1 counters on it.
if (voteOneCount > 0 && permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(voteOneCount), source.getControllerId(), source, game);
}
//Numbers Votes
if (voteTwoCount > 0) {
Effect tokenEffect = new CreateTokenEffect(new SoldierToken(), voteTwoCount);
tokenEffect.apply(game, source);
}
return true;
public LieutenantsOfTheGuardEffect copy() {
return new LieutenantsOfTheGuardEffect(this);
}
@Override
public LieutenantsOfTheGuardDilemmaEffect copy() {
return new LieutenantsOfTheGuardDilemmaEffect(this);
public boolean apply(Game game, Ability source) {
// Outcome.Benefit - AI will boost all the time (Strength choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Strength (+1/+1 counter)", "Numbers (1/1 token)", Outcome.Benefit);
vote.doVotes(source, game);
int strengthCount = vote.getVoteCount(true);
int numbersCount = vote.getVoteCount(false);
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (strengthCount > 0 && permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(strengthCount), source.getControllerId(), source, game);
}
if (numbersCount > 0) {
new SoldierToken().putOntoBattlefield(numbersCount, game, source, source.getControllerId());
}
return strengthCount + numbersCount > 0;
}
}

View file

@ -1,30 +1,33 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DestroyAllEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.players.Player;
import java.util.Collection;
import java.util.Objects;
import java.util.UUID;
/**
*
* @author fireshoes
* @author fireshoes, TheElk801
*/
public final class MagisterOfWorth extends CardImpl {
@ -38,9 +41,7 @@ public final class MagisterOfWorth extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// Will of the council - When Magister of Worth enters the battlefield, starting with you, each player votes for grace or condemnation. If grace gets more votes, each player returns each creature card from their graveyard to the battlefield. If condemnation gets more votes or the vote is tied, destroy all creatures other than Magister of Worth.
Effect effect = new MagisterOfWorthVoteEffect();
effect.setText("Will of the council - When Magister of Worth enters the battlefield, starting with you, each player votes for grace or condemnation. If grace gets more votes, each player returns each creature card from their graveyard to the battlefield. If condemnation gets more votes or the vote is tied, destroy all creatures other than Magister of Worth");
this.addAbility(new EntersBattlefieldTriggeredAbility(effect, false, true));
this.addAbility(new EntersBattlefieldTriggeredAbility(new MagisterOfWorthEffect(), false, true));
}
private MagisterOfWorth(final MagisterOfWorth card) {
@ -53,83 +54,65 @@ public final class MagisterOfWorth extends CardImpl {
}
}
class MagisterOfWorthVoteEffect extends OneShotEffect {
class MagisterOfWorthEffect extends OneShotEffect {
MagisterOfWorthVoteEffect() {
private static final FilterPermanent filter = new FilterCreaturePermanent();
static {
filter.add(AnotherPredicate.instance);
}
MagisterOfWorthEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; When {this} enters the battlefield, starting with you, each player votes for grace or condemnation. If grace gets more votes, each player returns each creature card from their graveyard to the battlefield. If condemnation gets more votes or the vote is tied, destroy all creatures other than {this}.";
staticText = "<i>Will of the council</i> &mdash; When {this} enters the battlefield, " +
"starting with you, each player votes for grace or condemnation. " +
"If grace gets more votes, each player returns each creature card from their graveyard to the battlefield. " +
"If condemnation gets more votes or the vote is tied, destroy all creatures other than {this}.";
}
MagisterOfWorthVoteEffect(final MagisterOfWorthVoteEffect effect) {
private MagisterOfWorthEffect(final MagisterOfWorthEffect effect) {
super(effect);
}
@Override
public MagisterOfWorthVoteEffect copy() {
return new MagisterOfWorthVoteEffect(this);
public MagisterOfWorthEffect copy() {
return new MagisterOfWorthEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int graceCount = 0;
int condemnationCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.DestroyPermanent, "Choose grace?", source, game)) {
graceCount++;
game.informPlayers(player.getLogName() + " has chosen: grace");
} else {
condemnationCount++;
game.informPlayers(player.getLogName() + " has chosen: condemnation");
}
}
}
if (graceCount > condemnationCount) {
new MagisterOfWorthReturnFromGraveyardEffect().apply(game, source);
} else {
FilterPermanent filter = new FilterCreaturePermanent("creatures other than {this}");
filter.add(AnotherPredicate.instance);
new DestroyAllEffect(filter).apply(game, source);
}
return true;
if (controller == null) {
return false;
}
return false;
// Outcome.Benefit - AI will return from graveyard all the time (Grace choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Grace (return from graveyard)", "Condemnation (destroy all)", Outcome.Benefit);
vote.doVotes(source, game);
int graceCount = vote.getVoteCount(true);
int condemnationCount = vote.getVoteCount(false);
if (condemnationCount >= graceCount) {
return new DestroyAllEffect(filter).apply(game, source);
}
// grace win - each player returns each creature card from their graveyard to the battlefield
Cards cards = new CardsImpl();
game.getState()
.getPlayersInRange(source.getControllerId(), game)
.stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.map(Player::getGraveyard)
.map(g -> g.getCards(game))
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.filter(MageObject::isCreature)
.forEach(cards::add);
return controller.moveCards(
cards.getCards(game), Zone.BATTLEFIELD, source, game,
false, false, true, null
);
}
}
class MagisterOfWorthReturnFromGraveyardEffect extends OneShotEffect {
public MagisterOfWorthReturnFromGraveyardEffect() {
super(Outcome.PutCreatureInPlay);
staticText = "each player returns each creature card from their graveyard to the battlefield";
}
public MagisterOfWorthReturnFromGraveyardEffect(final MagisterOfWorthReturnFromGraveyardEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source.getSourceId());
if (controller != null && sourceObject != null) {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.moveCards(player.getGraveyard().getCards(StaticFilters.FILTER_CARD_CREATURE, game), Zone.BATTLEFIELD, source, game);
}
}
return true;
}
return false;
}
@Override
public MagisterOfWorthReturnFromGraveyardEffect copy() {
return new MagisterOfWorthReturnFromGraveyardEffect(this);
}
}

View file

@ -1,41 +1,42 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CouncilsDilemmaVoteEffect;
import mage.abilities.effects.common.DrawDiscardControllerEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author JRHerlehy
* @author JRHerlehy, TheElk801
*/
public final class MessengerJays extends CardImpl {
public MessengerJays(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}");
this.subtype.add(SubType.BIRD);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// Flying
this.addAbility(FlyingAbility.getInstance());
// <i>Council's dilemma &mdash; When Messenger Jays enters the battlefield, starting with you, each player votes for feather or quill. Put a +1/+1 counter on Messenger Jays for each feather vote and draw a card for each quill vote. For each card drawn this way, discard a card.
this.addAbility(new EntersBattlefieldTriggeredAbility(new MessengerJaysDilemmaEffect(), false, "<i>Council's dilemma</i> &mdash; "));
this.addAbility(new EntersBattlefieldTriggeredAbility(
new MessengerJaysEffect(), false, "<i>Council's dilemma</i> &mdash; "
));
}
private MessengerJays(final MessengerJays card) {
@ -48,45 +49,44 @@ public final class MessengerJays extends CardImpl {
}
}
class MessengerJaysDilemmaEffect extends CouncilsDilemmaVoteEffect {
class MessengerJaysEffect extends OneShotEffect {
public MessengerJaysDilemmaEffect() {
MessengerJaysEffect() {
super(Outcome.Benefit);
this.staticText = "starting with you, each player votes for feather or quill. Put a +1/+1 counter on {this} for each feather vote and draw a card for each quill vote. For each card drawn this way, discard a card.";
staticText = "starting with you, each player votes for feather or quill. " +
"Put a +1/+1 counter on {this} for each feather vote " +
"and draw a card for each quill vote. For each card drawn this way, discard a card.";
}
public MessengerJaysDilemmaEffect(final MessengerJaysDilemmaEffect effect) {
private MessengerJaysEffect(final MessengerJaysEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
//If no controller, exit out here and do not vote.
if (controller == null) return false;
this.vote("feather", "quill", controller, game, source);
Permanent permanent = game.getPermanent(source.getSourceId());
//Feathers Votes
//If feathers received zero votes or the permanent is no longer on the battlefield, do not attempt to put P1P1 counter on it.
if (voteOneCount > 0 && permanent != null)
permanent.addCounters(CounterType.P1P1.createInstance(voteOneCount), source.getControllerId(), source, game);
//Quill Votes
//Only let the controller loot the appropriate amount of cards if it was voted for.
if (voteTwoCount > 0) {
Effect lootCardsEffect = new DrawDiscardControllerEffect(voteTwoCount, voteTwoCount);
lootCardsEffect.apply(game, source);
}
return true;
public MessengerJaysEffect copy() {
return new MessengerJaysEffect(this);
}
@Override
public MessengerJaysDilemmaEffect copy() {
return new MessengerJaysDilemmaEffect(this);
public boolean apply(Game game, Ability source) {
// Outcome.Benefit - AI will boost all the time (Feather choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Feather (+1/+1 counter)", "Quill (draw a card)", Outcome.Benefit);
vote.doVotes(source, game);
int featherCount = vote.getVoteCount(true);
int quillCount = vote.getVoteCount(false);
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (featherCount > 0 && permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(featherCount), source.getControllerId(), source, game);
}
Player player = game.getPlayer(source.getControllerId());
if (quillCount > 0 && player != null) {
int drawn = player.drawCards(quillCount, source, game);
player.discard(drawn, false, false, source, game);
}
return featherCount + quillCount > 0;
}
}

View file

@ -1,38 +1,38 @@
package mage.cards.o;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CouncilsDilemmaVoteEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author JRHerlehy
* @author JRHerlehy, TheElk801
*/
public final class OrchardElemental extends CardImpl {
public OrchardElemental(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}");
this.subtype.add(SubType.ELEMENTAL);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// <i>Council's dilemma</i> &mdash When Orchard Elemental enters the battlefield, starting with you, each player votes for sprout or harvest. Put two +1/+1 counters on Orchard Elemental for each sprout vote. You gain 3 life for each harvest vote.
this.addAbility(new EntersBattlefieldTriggeredAbility(new OrchardElementalDilemmaEffect(), false, "<i>Council's dilemma</i> &mdash; "));
this.addAbility(new EntersBattlefieldTriggeredAbility(
new OrchardElementalEffect(), false, "<i>Council's dilemma</i> &mdash; "
));
}
private OrchardElemental(final OrchardElemental card) {
@ -45,43 +45,42 @@ public final class OrchardElemental extends CardImpl {
}
}
class OrchardElementalDilemmaEffect extends CouncilsDilemmaVoteEffect {
class OrchardElementalEffect extends OneShotEffect {
public OrchardElementalDilemmaEffect() {
OrchardElementalEffect() {
super(Outcome.Benefit);
this.staticText = "starting with you, each player votes for sprout or harvest. Put two +1/+1 counters on Orchard Elemental for each sprout vote. You gain 3 life for each harvest vote";
staticText = "starting with you, each player votes for sprout or harvest. " +
"Put two +1/+1 counters on {this} for each sprout vote. You gain 3 life for each harvest vote";
}
public OrchardElementalDilemmaEffect(final OrchardElementalDilemmaEffect effect) {
private OrchardElementalEffect(final OrchardElementalEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) return false;
this.vote("sprout", "harvest", controller, game, source);
Permanent permanent = game.getPermanent(source.getSourceId());
//Sprout Votes
//If sprout received zero votes or the permanent is no longer on the battlefield, do not attempt to put P1P1 counter on it.
if (voteOneCount > 0 && permanent != null)
permanent.addCounters(CounterType.P1P1.createInstance(voteOneCount * 2), source.getControllerId(), source, game);
//Harvest Votes
if (voteTwoCount > 0) {
Effect gainLifeEffect = new GainLifeEffect(voteTwoCount * 3);
gainLifeEffect.apply(game, source);
}
return true;
public OrchardElementalEffect copy() {
return new OrchardElementalEffect(this);
}
@Override
public OrchardElementalDilemmaEffect copy() {
return new OrchardElementalDilemmaEffect(this);
public boolean apply(Game game, Ability source) {
// Outcome.Benefit - AI will boost all the time (Sprout choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Sprout (two +1/+1 counters)", "Harvest (3 life)", Outcome.Benefit);
vote.doVotes(source, game);
int sproutCount = vote.getVoteCount(true);
int harvestCount = vote.getVoteCount(false);
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (sproutCount > 0 && permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(2 * sproutCount), source.getControllerId(), source, game);
}
Player player = game.getPlayer(source.getControllerId());
if (harvestCount > 0 && player != null) {
player.gainLife(3 * harvestCount, game, source);
}
return sproutCount + harvestCount > 0;
}
}

View file

@ -1,25 +1,25 @@
package mage.cards.p;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author emerald000
* @author emerald000, TheElk801
*/
public final class PleaForPower extends CardImpl {
public PleaForPower(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{U}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}");
// Will of the council - Starting with you, each player votes for time or knowledge. If time gets more votes, take an extra turn after this one. If knowledge gets more votes or the vote is tied, draw three cards.
this.getSpellAbility().addEffect(new PleaForPowerEffect());
@ -39,10 +39,12 @@ class PleaForPowerEffect extends OneShotEffect {
PleaForPowerEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, each player votes for time or knowledge. If time gets more votes, take an extra turn after this one. If knowledge gets more votes or the vote is tied, draw three cards";
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, " +
"each player votes for time or knowledge. If time gets more votes, take an extra turn after this one. " +
"If knowledge gets more votes or the vote is tied, draw three cards";
}
PleaForPowerEffect(final PleaForPowerEffect effect) {
private PleaForPowerEffect(final PleaForPowerEffect effect) {
super(effect);
}
@ -54,28 +56,21 @@ class PleaForPowerEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int timeCount = 0;
int knowledgeCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.ExtraTurn, "Choose time?", source, game)) {
timeCount++;
game.informPlayers(player.getLogName() + " has chosen: time");
} else {
knowledgeCount++;
game.informPlayers(player.getLogName() + " has chosen: knowledge");
}
}
}
if (timeCount > knowledgeCount) {
new AddExtraTurnControllerEffect().apply(game, source);
} else {
controller.drawCards(3, source, game);
}
return true;
if (controller == null) {
return false;
}
// Outcome.Detriment - AI will draw cards all the time (Knowledge choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Time (extra turn)", "Knowledge (draw 3 cards)", Outcome.Detriment);
vote.doVotes(source, game);
int timeCount = vote.getVoteCount(true);
int knowledgeCount = vote.getVoteCount(false);
if (timeCount > knowledgeCount) {
return new AddExtraTurnControllerEffect().apply(game, source);
} else {
return controller.drawCards(3, source, game) > 0;
}
return false;
}
}

View file

@ -1,21 +1,21 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.common.CouncilsDilemmaVoteEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterPermanentCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
/**
*
* @author JRHerlehy
* @author JRHerlehy, TheElk801
*/
public final class SelvalasStampede extends CardImpl {
@ -23,7 +23,7 @@ public final class SelvalasStampede extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}{G}");
// <i>Council's dilemma</i> &mdash Starting with you, each player votes for wild or free. Reveal cards from the top of your library until you reveal a creature card for each wild vote. Put those creature cards onto the battlefield, then shuffle the rest into your library. You may put a permanent card from your hand onto the battlefield for each free vote.
this.getSpellAbility().addEffect(new SelvalasStampedeDilemmaEffect());
this.getSpellAbility().addEffect(new SelvalasStampedeEffect());
}
private SelvalasStampede(final SelvalasStampede card) {
@ -36,63 +36,70 @@ public final class SelvalasStampede extends CardImpl {
}
}
class SelvalasStampedeDilemmaEffect extends CouncilsDilemmaVoteEffect {
class SelvalasStampedeEffect extends OneShotEffect {
public SelvalasStampedeDilemmaEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "<i>Council's dilemma</i> &mdash; Starting with you, each player votes for wild or free. Reveal cards from the top of your library until you reveal a creature card for each wild vote. Put those creature cards onto the battlefield, then shuffle the rest into your library. "
+ "You may put a permanent card from your hand onto the battlefield for each free vote";
SelvalasStampedeEffect() {
super(Outcome.Benefit);
staticText = "<i>Council's dilemma</i> &mdash; Starting with you, each player votes for wild or free. " +
"Reveal cards from the top of your library until you reveal a creature card for each wild vote. " +
"Put those creature cards onto the battlefield, then shuffle the rest into your library. " +
"You may put a permanent card from your hand onto the battlefield for each free vote";
}
public SelvalasStampedeDilemmaEffect(final SelvalasStampedeDilemmaEffect effect) {
private SelvalasStampedeEffect(final SelvalasStampedeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
//If no controller, exit here and do not vote.
if (controller == null) {
return false;
}
this.vote("wild", "free", controller, game, source);
//Wild Votes
if (voteOneCount > 0) {
Cards revealedCards = new CardsImpl();
Cards toBattlefield = new CardsImpl();
for (Card card : controller.getLibrary().getCards(game)) {
revealedCards.add(card);
if (card.isCreature()) {
toBattlefield.add(card);
if (toBattlefield.size() == voteOneCount) {
break;
}
}
}
controller.revealCards(source, revealedCards, game);
controller.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game);
revealedCards.removeAll(toBattlefield);
if (!revealedCards.isEmpty()) {
controller.shuffleLibrary(source, game);
}
}
//Free Votes
if (voteTwoCount > 0) {
TargetCardInHand target = new TargetCardInHand(0, voteTwoCount, new FilterPermanentCard("permanent cards"));
if (controller.choose(outcome, target, source.getSourceId(), game)) {
controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game);
}
}
return true;
public SelvalasStampedeEffect copy() {
return new SelvalasStampedeEffect(this);
}
@Override
public SelvalasStampedeDilemmaEffect copy() {
return new SelvalasStampedeDilemmaEffect(this);
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
// Outcome.Detriment - AI will use library will the time (Free choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote("Wild (from library to battlefield)", "Free (from hand to battlefield)", Outcome.Detriment);
vote.doVotes(source, game);
int wildCount = vote.getVoteCount(true);
int freeCount = vote.getVoteCount(false);
// Reveal cards from the top of your library until you reveal a creature card for each wild vote.
// Put those creature cards onto the battlefield, then shuffle the rest into your library.
Cards toReveal = new CardsImpl();
Cards creatureCards = new CardsImpl();
for (Card card : player.getLibrary().getCards(game)) {
if (creatureCards.size() >= wildCount) {
break;
}
if (card.isCreature()) {
creatureCards.add(card);
}
toReveal.add(card);
}
if (toReveal.size() > 0) {
player.revealCards(source, toReveal, game);
}
if (creatureCards.size() > 0) {
player.moveCards(creatureCards, Zone.BATTLEFIELD, source, game);
}
player.shuffleLibrary(source, game);
// You may put a permanent card from your hand onto the battlefield for each free vote
if (freeCount > 0) {
TargetCardInHand target = new TargetCardInHand(0, freeCount, StaticFilters.FILTER_CARD_PERMANENT);
player.choose(Outcome.PutCreatureInPlay, player.getHand(), target, game);
creatureCards.clear();
creatureCards.addAll(target.getTargets());
player.moveCards(creatureCards, Zone.BATTLEFIELD, source, game);
}
return wildCount + freeCount > 0;
}
}

View file

@ -1,27 +1,27 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CopyTargetSpellEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.game.stack.Spell;
import mage.target.TargetSpell;
import java.util.UUID;
/**
*
* @author fireshoes
* @author fireshoes, TheElk801
*/
public final class SplitDecision extends CardImpl {
public SplitDecision(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}");
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Will of the council - Choose target instant or sorcery spell. Starting with you, each player votes for denial or duplication. If denial gets more votes, counter the spell. If duplication gets more votes or the vote is tied, copy the spell. You may choose new targets for the copy.
this.getSpellAbility().addEffect(new SplitDecisionEffect());
@ -41,11 +41,14 @@ public final class SplitDecision extends CardImpl {
class SplitDecisionEffect extends OneShotEffect {
SplitDecisionEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; Choose target instant or sorcery spell. Starting with you, each player votes for denial or duplication. If denial gets more votes, counter the spell. If duplication gets more votes or the vote is tied, copy the spell. You may choose new targets for the copy";
super(Outcome.Removal); // cause AI votes for counter all the time so it must it target opponent's spell, not own
this.staticText = "<i>Will of the council</i> &mdash; Choose target instant or sorcery spell. " +
"Starting with you, each player votes for denial or duplication. If denial gets more votes, " +
"counter the spell. If duplication gets more votes or the vote is tied, copy the spell. " +
"You may choose new targets for the copy";
}
SplitDecisionEffect(final SplitDecisionEffect effect) {
private SplitDecisionEffect(final SplitDecisionEffect effect) {
super(effect);
}
@ -56,28 +59,26 @@ class SplitDecisionEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int denialCount = 0;
int duplicationCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.ExtraTurn, "Choose denial?", source, game)) {
denialCount++;
game.informPlayers(player.getLogName() + " has voted for denial");
} else {
duplicationCount++;
game.informPlayers(player.getLogName() + " has voted for duplication");
}
}
}
if (denialCount > duplicationCount) {
return game.getStack().counter(getTargetPointer().getFirst(game, source), source, game);
} else {
return new CopyTargetSpellEffect().apply(game, source);
}
Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source));
if (spell == null) {
return false;
}
// Outcome.Benefit - AI will use counter all the time (Denial choice)
// TODO: add AI hint logic in the choice method, see Tyrant's Choice as example
TwoChoiceVote vote = new TwoChoiceVote(
"Denial (counter " + spell.getIdName() + ")",
"Duplication (copy " + spell.getIdName() + ")",
Outcome.Benefit
);
vote.doVotes(source, game);
int denialCount = vote.getVoteCount(true);
int duplicationCount = vote.getVoteCount(false);
if (denialCount > duplicationCount) {
return game.getStack().counter(spell.getId(), source, game);
} else {
return new CopyTargetSpellEffect().apply(game, source);
}
return false;
}
}

View file

@ -1,26 +1,27 @@
package mage.cards.t;
import java.util.UUID;
import com.sun.org.apache.xpath.internal.operations.Bool;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.LoseLifeOpponentsEffect;
import mage.abilities.effects.common.SacrificeOpponentsEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.TwoChoiceVote;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author fireshoes
* @author fireshoes, TheElk801
*/
public final class TyrantsChoice extends CardImpl {
public TyrantsChoice(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{B}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}");
// Will of the council - Starting with you, each player votes for death or torture. If death gets more votes, each opponent sacrifices a creature. If torture gets more votes or the vote is tied, each opponent loses 4 life.
this.getSpellAbility().addEffect(new TyrantsChoiceEffect());
@ -40,10 +41,13 @@ class TyrantsChoiceEffect extends OneShotEffect {
TyrantsChoiceEffect() {
super(Outcome.Benefit);
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, each player votes for death or torture. If death gets more votes, each opponent sacrifices a creature. If torture gets more votes or the vote is tied, each opponent loses 4 life";
this.staticText = "<i>Will of the council</i> &mdash; Starting with you, " +
"each player votes for death or torture. If death gets more votes, " +
"each opponent sacrifices a creature. If torture gets more votes " +
"or the vote is tied, each opponent loses 4 life";
}
TyrantsChoiceEffect(final TyrantsChoiceEffect effect) {
private TyrantsChoiceEffect(final TyrantsChoiceEffect effect) {
super(effect);
}
@ -54,55 +58,24 @@ class TyrantsChoiceEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int deathCount = 0;
int tortureCount = 0;
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.Sacrifice, "Choose death?", source, game)) {
deathCount++;
game.informPlayers(player.getLogName() + " has voted for death");
} else {
tortureCount++;
game.informPlayers(player.getLogName() + " has voted for torture");
}
}
}
if (deathCount > tortureCount) {
new SacrificeOpponentsEffect(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT).apply(game, source);
TwoChoiceVote vote = new TwoChoiceVote("Death (sacrifice a creature)", "Torture (lose 4 life)", Outcome.Benefit);
vote.doVotes(source, game, (voteHandler, aiPlayer, aiDecidingPlayer, aiSource, aiGame) -> {
// ai hint
if (aiSource.isControlledBy(aiDecidingPlayer.getId())) {
// best for controller - lose life
return Boolean.FALSE;
} else {
new TyrantsChoiceLoseLifeEffect().apply(game, source);
// best for opponent - sacrifice
return Boolean.TRUE;
}
return true;
});
int deathCount = vote.getVoteCount(true);
int tortureCount = vote.getVoteCount(false);
if (deathCount > tortureCount) {
return new SacrificeOpponentsEffect(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT).apply(game, source);
} else {
return new LoseLifeOpponentsEffect(4).apply(game, source);
}
return false;
}
}
class TyrantsChoiceLoseLifeEffect extends OneShotEffect {
public TyrantsChoiceLoseLifeEffect() {
super(Outcome.Damage);
staticText = "Each opponent loses 2 life";
}
public TyrantsChoiceLoseLifeEffect(final TyrantsChoiceLoseLifeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID opponentId : game.getOpponents(source.getControllerId())) {
game.getPlayer(opponentId).loseLife(4, game, source, false);
}
return true;
}
@Override
public TyrantsChoiceLoseLifeEffect copy() {
return new TyrantsChoiceLoseLifeEffect(this);
}
}

View file

@ -39,6 +39,7 @@ public final class Conspiracy extends ExpansionSet {
cards.add(new SetCardInfo("Basandra, Battle Seraph", 184, Rarity.RARE, mage.cards.b.BasandraBattleSeraph.class));
cards.add(new SetCardInfo("Bite of the Black Rose", 26, Rarity.UNCOMMON, mage.cards.b.BiteOfTheBlackRose.class));
cards.add(new SetCardInfo("Boldwyr Intimidator", 137, Rarity.UNCOMMON, mage.cards.b.BoldwyrIntimidator.class));
cards.add(new SetCardInfo("Brago's Representative", 14, Rarity.COMMON, mage.cards.b.BragosRepresentative.class));
cards.add(new SetCardInfo("Brago, King Eternal", 41, Rarity.RARE, mage.cards.b.BragoKingEternal.class));
cards.add(new SetCardInfo("Brainstorm", 91, Rarity.COMMON, mage.cards.b.Brainstorm.class));
cards.add(new SetCardInfo("Breakthrough", 92, Rarity.UNCOMMON, mage.cards.b.Breakthrough.class));
@ -90,6 +91,7 @@ public final class Conspiracy extends ExpansionSet {
cards.add(new SetCardInfo("Grenzo, Dungeon Warden", 47, Rarity.RARE, mage.cards.g.GrenzoDungeonWarden.class));
cards.add(new SetCardInfo("Grenzo's Cutthroat", 32, Rarity.COMMON, mage.cards.g.GrenzosCutthroat.class));
cards.add(new SetCardInfo("Grixis Illusionist", 99, Rarity.COMMON, mage.cards.g.GrixisIllusionist.class));
cards.add(new SetCardInfo("Grudge Keeper", 28, Rarity.COMMON, mage.cards.g.GrudgeKeeper.class));
cards.add(new SetCardInfo("Guardian Zendikon", 71, Rarity.COMMON, mage.cards.g.GuardianZendikon.class));
cards.add(new SetCardInfo("Heartless Hidetsugu", 144, Rarity.RARE, mage.cards.h.HeartlessHidetsugu.class));
cards.add(new SetCardInfo("Heckling Fiends", 145, Rarity.UNCOMMON, mage.cards.h.HecklingFiends.class));

View file

@ -33,6 +33,7 @@ public final class ConspiracyTakeTheCrown extends ExpansionSet {
cards.add(new SetCardInfo("Altar's Reap", 127, Rarity.COMMON, mage.cards.a.AltarsReap.class));
cards.add(new SetCardInfo("Ascended Lawmage", 198, Rarity.UNCOMMON, mage.cards.a.AscendedLawmage.class));
cards.add(new SetCardInfo("Avatar of Woe", 128, Rarity.MYTHIC, mage.cards.a.AvatarOfWoe.class));
cards.add(new SetCardInfo("Ballot Broker", 13, Rarity.COMMON, mage.cards.b.BallotBroker.class));
cards.add(new SetCardInfo("Beast Within", 174, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class));
cards.add(new SetCardInfo("Berserk", 175, Rarity.MYTHIC, mage.cards.b.Berserk.class));
cards.add(new SetCardInfo("Besmirch", 49, Rarity.UNCOMMON, mage.cards.b.Besmirch.class));
@ -121,6 +122,7 @@ public final class ConspiracyTakeTheCrown extends ExpansionSet {
cards.add(new SetCardInfo("Hundred-Handed One", 93, Rarity.RARE, mage.cards.h.HundredHandedOne.class));
cards.add(new SetCardInfo("Hurly-Burly", 165, Rarity.COMMON, mage.cards.h.HurlyBurly.class));
cards.add(new SetCardInfo("Ill-Tempered Cyclops", 166, Rarity.COMMON, mage.cards.i.IllTemperedCyclops.class));
cards.add(new SetCardInfo("Illusion of Choice", 31, Rarity.UNCOMMON, mage.cards.i.IllusionOfChoice.class));
cards.add(new SetCardInfo("Infest", 139, Rarity.UNCOMMON, mage.cards.i.Infest.class));
cards.add(new SetCardInfo("Inquisition of Kozilek", 140, Rarity.RARE, mage.cards.i.InquisitionOfKozilek.class));
cards.add(new SetCardInfo("Into the Void", 112, Rarity.UNCOMMON, mage.cards.i.IntoTheVoid.class));

View file

@ -0,0 +1,314 @@
package org.mage.test.multiplayer;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4PlayersWithAIHelps;
/**
* @author TheElk801
*/
public class VotingTest extends CardTestCommander4PlayersWithAIHelps {
// Player order: A -> D -> C -> B
// Councils dilemma When Lieutenants of the Guard enters the battlefield, starting with you,
// each player votes for strength or numbers. Put a +1/+1 counter on Lieutenants of the Guard
// for each strength vote and create a 1/1 white Soldier creature token for each numbers vote.
private static final String lieutenant = "Lieutenants of the Guard";
// While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)
private static final String rep = "Brago's Representative";
// While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
private static final String broker = "Ballot Broker";
// You choose how each player votes this turn.
private static final String illusion = "Illusion of Choice";
// TODO: add test with broker
// rulues:
// The ability only affects spells and abilities that use the word vote. Other cards that involve choices,
// such as Archangel of Strife, are unaffected.
// (2016-08-23)
// Whenever players finish voting, each opponent who voted for a choice you didnt vote for loses 2 life.
private static final String keeper = "Grudge Keeper";
// Will of the council - Starting with you, each player votes for death or torture. If death gets more votes,
// each opponent sacrifices a creature. If torture gets more votes or the vote is tied, each opponent loses 4 life.
private static final String tyrant = "Tyrant's Choice";
private void setChoices(String choice) {
setChoices(choice, choice, choice, choice);
}
private void setChoices(String choiceA, String choiceB, String choiceC, String choiceD) {
setChoice(playerA, choiceA);
setChoice(playerB, choiceB);
setChoice(playerC, choiceC);
setChoice(playerD, choiceD);
}
@Test
public void test_LieutenantsOfTheGuard_1() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("Yes");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_LieutenantsOfTheGuard_2() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 2, 2);
assertPermanentCount(playerA, "Soldier", 4);
}
@Test
public void test_LieutenantsOfTheGuard_3() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("Yes", "Yes", "No", "No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 4, 4);
assertPermanentCount(playerA, "Soldier", 2);
}
@Test
public void test_TyrantsChoice_AI_Normal() {
addCard(Zone.HAND, playerA, tyrant); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
// ai play
// opponents must have more votes so final result is sacrifice (best for opponents)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tyrant);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerB);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerC);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerD);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, tyrant, 1);
assertLife(playerA, 20);
assertLife(playerB, 20);
assertLife(playerC, 20);
assertLife(playerD, 20);
}
@Test
@Ignore // TODO: fix after merge, see player.isComputer
public void test_TyrantsChoice_AI_UnderControl() {
addCard(Zone.HAND, playerA, tyrant); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
//
addCard(Zone.HAND, playerA, illusion, 1); // {U}
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// prepare vote control
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkGraveyardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion, 1);
// ai play
// you control the opponents, so votes result must be lose life (best for controller)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tyrant);
checkStackSize("before resolve", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 1);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerB);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerC);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerD);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, tyrant, 1);
assertLife(playerA, 20);
assertLife(playerB, 20 - 4);
assertLife(playerC, 20 - 4);
assertLife(playerD, 20 - 4);
}
@Test
public void test_BragosRepresentative() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, rep);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "Yes");
setChoices("Yes", "Yes", "No", "No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 5, 5);
assertPermanentCount(playerA, "Soldier", 2);
}
@Test
public void test_BallotBroker() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, broker);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("Yes", "Yes", "No", "No");
setChoice(playerA, "Yes"); // to have an additional vote
setChoice(playerA, "No"); // the additional vote
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 4, 4);
assertPermanentCount(playerA, "Soldier", 3);
}
@Test
public void test_IllusionOfChoice_Single() {
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 6);
addCard(Zone.HAND, playerA, illusion);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "Yes", 4);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_IllusionOfChoice_WithBragosRepresentative() {
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 6);
addCard(Zone.BATTLEFIELD, playerB, rep);
addCard(Zone.HAND, playerA, illusion);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "Yes", 5);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 7, 7);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_IllusionOfChoice_Double() {
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 6);
addCard(Zone.BATTLEFIELD, playerB, "Island");
addCard(Zone.HAND, playerA, illusion);
addCard(Zone.HAND, playerB, illusion);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, illusion);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerB, "Yes", 4);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_GrudgeKeeper_1() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, keeper);
addCard(Zone.BATTLEFIELD, playerB, rep);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerB, "Yes");
setChoices("Yes", "No", "No", "No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 4, 4);
assertPermanentCount(playerA, "Soldier", 3);
assertLife(playerA, 20);
assertLife(playerB, 20 - 2);
assertLife(playerC, 20 - 2);
assertLife(playerD, 20 - 2);
}
@Test
public void test_GrudgeKeeper_2() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, keeper);
addCard(Zone.BATTLEFIELD, playerA, rep);
addCard(Zone.BATTLEFIELD, playerB, rep);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "No");
setChoice(playerB, "No");
setChoices("Yes");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 2);
assertLife(playerA, 20);
assertLife(playerB, 20);
assertLife(playerC, 20);
assertLife(playerD, 20);
}
}

View file

@ -0,0 +1,21 @@
package org.mage.test.serverside.base;
import mage.constants.RangeOfInfluence;
import org.mage.test.player.TestComputerPlayer7;
import org.mage.test.player.TestPlayer;
/**
* See more details in CardTestPlayerBaseWithAIHelps
*
* @author JayDi85
*/
public abstract class CardTestCommander4PlayersWithAIHelps extends CardTestCommander4Players {
@Override
protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) {
// use same RangeOfInfluence.ALL as CardTestCommander4Players do
TestPlayer testPlayer = new TestPlayer(new TestComputerPlayer7(name, RangeOfInfluence.ALL, 6));
testPlayer.setAIPlayer(false); // AI can't play it by itself, use AI commands
return testPlayer;
}
}

View file

@ -5,9 +5,7 @@ import org.mage.test.player.TestComputerPlayerMonteCarlo;
import org.mage.test.player.TestPlayer;
/**
* Base class but with Monte Carlo computer player to test single AI commands (it's different from full AI simulation from CardTestPlayerBaseAI):
* 1. AI don't play normal priorities (you must use ai*** commands to play it);
* 2. AI will choose in non strict mode (it's simulated ComputerPlayerMCTS, not simple ComputerPlayer from basic tests)
* See more details in CardTestPlayerBaseWithAIHelps
*
* @author JayDi85
*/

View file

@ -1,42 +0,0 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
/**
* @author JRHerlehy
*/
public abstract class CouncilsDilemmaVoteEffect extends OneShotEffect {
protected int voteOneCount = 0, voteTwoCount = 0;
public CouncilsDilemmaVoteEffect(Outcome outcome) {
super(outcome);
}
public CouncilsDilemmaVoteEffect(final CouncilsDilemmaVoteEffect effect) {
super(effect);
}
protected void vote(String choiceOne, String choiceTwo, Player controller, Game game, Ability source) {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.Vote,
"Choose " + choiceOne + " or " + choiceTwo + "?",
source.getRule(), choiceOne, choiceTwo, source, game)) {
voteOneCount++;
game.informPlayers(player.getLogName() + " has voted for " + choiceOne);
} else {
voteTwoCount++;
game.informPlayers(player.getLogName() + " has voted for " + choiceTwo);
}
}
}
}
}

View file

@ -63,11 +63,15 @@ public class ChoiceColor extends ChoiceImpl {
}
public ObjectColor getColor() {
if (choice == null) {
return getColorFromString(choice);
}
public static ObjectColor getColorFromString(String colorString) {
if (colorString == null) {
return null;
}
ObjectColor color = new ObjectColor();
switch (choice) {
switch (colorString) {
case "Black":
color.setBlack(true);
break;

View file

@ -0,0 +1,41 @@
package mage.choices;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author TheElk801
*/
public class TwoChoiceVote extends VoteHandler<Boolean> {
private final String choice1;
private final String choice2;
private final Outcome outcome;
public TwoChoiceVote(String choice1, String choice2, Outcome outcome) {
this.choice1 = choice1;
this.choice2 = choice2;
this.outcome = outcome;
}
@Override
protected Set<Boolean> getPossibleVotes(Ability source, Game game) {
return new LinkedHashSet<>(Arrays.asList(Boolean.TRUE, Boolean.FALSE));
}
@Override
public Boolean playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) {
return decidingPlayer.chooseUse(outcome, voteInfo, null, choice1, choice2, source, game);
}
@Override
protected String voteName(Boolean vote) {
return (vote ? choice1 : choice2);
}
}

View file

@ -0,0 +1,183 @@
package mage.choices;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.VoteEvent;
import mage.game.events.VotedEvent;
import mage.players.Player;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public abstract class VoteHandler<T> {
protected final Map<UUID, List<T>> playerMap = new HashMap<>();
protected VoteHandlerAI<T> aiVoteHint = null;
public void doVotes(Ability source, Game game) {
doVotes(source, game, null);
}
public void doVotes(Ability source, Game game, VoteHandlerAI<T> aiVoteHint) {
this.aiVoteHint = aiVoteHint;
this.playerMap.clear();
int stepCurrent = 0;
int stepTotal = game.getState().getPlayersInRange(source.getControllerId(), game).size();
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
stepCurrent++;
VoteEvent event = new VoteEvent(playerId, source);
game.replaceEvent(event);
Player player = game.getPlayer(event.getTargetId());
Player decidingPlayer = game.getPlayer(event.getPlayerId());
if (player == null || decidingPlayer == null) {
continue;
}
int voteCount = event.getExtraVotes() + event.getOptionalExtraVotes() + 1;
for (int i = 0; i < voteCount; i++) {
// Decision for extra choice goes from original player, not from deciding.
// Rules from Illusion of Choice:
// If another player controls Ballot Broker, that player first takes their normal vote
// with you choosing the result, then that player decides whether they are taking the
// additional vote. If there is an additional vote, you again choose the result.
// (2016-08-23)
// Outcome.Benefit - AI must use extra vote all the time
if (i > event.getExtraVotes() && !player.chooseUse(
Outcome.Benefit, "Use an extra vote?", source, game
)) {
continue;
}
String stepName = (i > 0 ? "extra step" : "step");
String voteInfo = String.format("Vote, %s %d of %d", stepName, stepCurrent, stepTotal);
T vote;
if (!decidingPlayer.isHuman() && !decidingPlayer.isTestMode() && this.aiVoteHint != null) {
// TODO: add isComputer after PR
// ai choose
vote = this.aiVoteHint.makeChoice(this, player, decidingPlayer, source, game);
} else {
// human choose
vote = playerChoose(voteInfo, player, decidingPlayer, source, game);
}
if (vote == null) {
continue;
}
String message = voteInfo + ": " + player.getName() + " voted for " + voteName(vote);
if (!Objects.equals(player, decidingPlayer)) {
message += " (chosen by " + decidingPlayer.getName() + ')';
}
game.informPlayers(message);
this.playerMap.computeIfAbsent(playerId, x -> new ArrayList<>()).add(vote);
}
}
// show final results to players
Map<T, Integer> totalVotes = new LinkedHashMap<>();
// fill by possible choices
this.getPossibleVotes(source, game).forEach(vote -> {
totalVotes.putIfAbsent(vote, 0);
});
// fill by real choices
playerMap.entrySet()
.stream()
.flatMap(votesList -> votesList.getValue().stream())
.forEach(vote -> {
totalVotes.compute(vote, (u, i) -> i == null ? 1 : Integer.sum(i, 1));
});
Set<T> winners = this.getMostVoted();
String totalVotesStr = totalVotes.entrySet()
.stream()
.map(entry -> (winners.contains(entry.getKey()) ? " -win- " : " -lose- ") + voteName(entry.getKey()) + ": " + entry.getValue())
.sorted()
.collect(Collectors.joining("<br>"));
game.informPlayers("Vote results:<br>" + totalVotesStr);
game.fireEvent(new VotedEvent(source, this));
}
/**
* Return possible votes. Uses for info only (final results).
*
* @param source
* @param game
* @return
*/
protected abstract Set<T> getPossibleVotes(Ability source, Game game);
/**
* Choose dialog for voting. Another player can choose it (example: Illusion of Choice)
*
* @param player
* @param decidingPlayer
* @param source
* @param game
* @return
*/
protected abstract T playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game);
/**
* Show readable choice name
*
* @param vote
* @return
*/
protected abstract String voteName(T vote);
public List<T> getVotes(UUID playerId) {
return playerMap.computeIfAbsent(playerId, x -> new ArrayList<>());
}
public int getVoteCount(T vote) {
return playerMap
.values()
.stream()
.flatMap(Collection::stream)
.map(vote::equals)
.mapToInt(x -> x ? 1 : 0)
.sum();
}
public List<UUID> getVotedFor(T vote) {
return playerMap
.entrySet()
.stream()
.filter(entry -> entry.getValue() != null && entry.getValue().contains(vote))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
public Set<T> getMostVoted() {
Map<T, Integer> map = new HashMap<>();
playerMap
.values()
.stream()
.flatMap(Collection::stream)
.forEach(t -> map.compute(t, (s, i) -> i == null ? 1 : Integer.sum(i, 1)));
int max = map.values().stream().mapToInt(x -> x).max().orElse(0);
return map
.entrySet()
.stream()
.filter(e -> e.getValue() >= max)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
public Set<UUID> getDidntVote(UUID playerId) {
if (playerMap.computeIfAbsent(playerId, x -> new ArrayList<>()).isEmpty()) {
return playerMap.keySet();
}
return playerMap
.entrySet()
.stream()
.filter(e -> e.getValue() != null && !e.getValue().isEmpty())
.filter(e -> !e.getValue().stream().allMatch(playerMap.get(playerId)::contains))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
}

View file

@ -0,0 +1,23 @@
package mage.choices;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
/**
* @author JayDi85
*/
@FunctionalInterface
public interface VoteHandlerAI<T> {
/**
* AI choosing hints for votes
*
* @param voteHandler voting handler for choosing
* @param aiPlayer player who must choose
* @param aiDecidingPlayer real player who make a choice (cab be changed by another effect, example: Illusion of Choice)
* @param aiSource
* @param aiGame
*/
T makeChoice(VoteHandler<T> voteHandler, Player aiPlayer, Player aiDecidingPlayer, Ability aiSource, Game aiGame);
}

View file

@ -437,6 +437,14 @@ public class GameEvent implements Serializable {
//combat events
COMBAT_DAMAGE_APPLIED,
SELECTED_ATTACKER, SELECTED_BLOCKER,
/* voting
targetId player who voting
sourceId sourceId of the effect doing the voting
playerId player who deciding about voting, can be changed by replace events
amount not used for this event
flag not used for this event
*/
VOTE, VOTED,
//custom events
CUSTOM_EVENT
}

View file

@ -0,0 +1,34 @@
package mage.game.events;
import mage.abilities.Ability;
import java.util.UUID;
/**
* @author TheElk801
*/
public class VoteEvent extends GameEvent {
private int extraVotes = 0; // example: you get an additional vote
private int optionalExtraVotes = 0; // example: you may vote an additional time
public VoteEvent(UUID playerId, Ability source) {
super(EventType.VOTE, playerId, source, playerId);
}
public void incrementExtraVotes() {
extraVotes++;
}
public void incrementOptionalExtraVotes() {
optionalExtraVotes++;
}
public int getExtraVotes() {
return extraVotes;
}
public int getOptionalExtraVotes() {
return optionalExtraVotes;
}
}

View file

@ -0,0 +1,24 @@
package mage.game.events;
import mage.abilities.Ability;
import mage.choices.VoteHandler;
import java.util.Set;
import java.util.UUID;
/**
* @author TheElk801
*/
public class VotedEvent extends GameEvent {
private final VoteHandler voteHandler;
public VotedEvent(Ability source, VoteHandler voteHandler) {
super(EventType.VOTED, source.getSourceId(), source, source.getControllerId());
this.voteHandler = voteHandler;
}
public Set<UUID> getDidntVote(UUID playerId) {
return voteHandler.getDidntVote(playerId);
}
}