diff --git a/Mage.Sets/src/mage/cards/c/CirdanTheShipwright.java b/Mage.Sets/src/mage/cards/c/CirdanTheShipwright.java new file mode 100644 index 00000000000..5aaf7a8a861 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CirdanTheShipwright.java @@ -0,0 +1,138 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.VoteHandler; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPlayer; +import mage.target.common.TargetCardInHand; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class CirdanTheShipwright extends CardImpl { + + public CirdanTheShipwright(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Secret council -- Whenever Cirdan the Shipwright enters the battlefield or attacks, each player secretly votes for a player, then those votes are revealed. Each player draws a card for each vote they received. Each player who received no votes may put a permanent card from their hand onto the battlefield. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new CirdanTheShipwrightEffect()).setAbilityWord(AbilityWord.SECRET_COUNCIL)); + } + + private CirdanTheShipwright(final CirdanTheShipwright card) { + super(card); + } + + @Override + public CirdanTheShipwright copy() { + return new CirdanTheShipwright(this); + } +} + +class CirdanTheShipwrightEffect extends OneShotEffect { + + CirdanTheShipwrightEffect() { + super(Outcome.Benefit); + staticText = "each player secretly votes for a player, then those votes are revealed. " + + "Each player draws a card for each vote they received. " + + "Each player who received no votes may put a permanent card from their hand onto the battlefield"; + } + + private CirdanTheShipwrightEffect(final CirdanTheShipwrightEffect effect) { + super(effect); + } + + @Override + public CirdanTheShipwrightEffect copy() { + return new CirdanTheShipwrightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + CirdanTheShipwrightVote vote = new CirdanTheShipwrightVote(); + vote.doVotes(source, game); + Map playerMap = vote.getVotesPerPlayer(game); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + int amount = playerMap.getOrDefault(playerId, 0); + if (player != null && amount > 0) { + player.drawCards(amount, source, game); + } + } + Map voteless = new HashMap<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null && !playerMap.containsKey(playerId)) { + TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_PERMANENT); + target.withChooseHint("to put onto the battlefield"); + player.choose(outcome, player.getHand(), target, source, game); + voteless.put(player, game.getCard(target.getFirstTarget())); + } + } + for (Map.Entry entry : voteless.entrySet()) { + if (entry.getValue() != null) { + entry.getKey().moveCards(entry.getValue(), Zone.BATTLEFIELD, source, game); + } + } + return true; + } +} + +class CirdanTheShipwrightVote extends VoteHandler { + + @Override + protected Set getPossibleVotes(Ability source, Game game) { + return game + .getState() + .getPlayersInRange(source.getControllerId(), game) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + @Override + protected Player playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) { + TargetPlayer target = new TargetPlayer(); + target.setNotTarget(true); + target.withChooseHint("to vote for"); + decidingPlayer.choose(Outcome.Benefit, target, source, game); + return game.getPlayer(target.getFirstTarget()); + } + + @Override + protected String voteName(Player vote) { + return vote.getName(); + } + + Map getVotesPerPlayer(Game game) { + return playerMap + .values() + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toMap(MageItem::getId, x -> 1, Integer::sum)); + } +} diff --git a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java index f7e2ae18689..950e297ddec 100644 --- a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java +++ b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java @@ -48,6 +48,7 @@ public final class TalesOfMiddleEarthCommander extends ExpansionSet { cards.add(new SetCardInfo("Cavern of Souls", 362, Rarity.MYTHIC, mage.cards.c.CavernOfSouls.class)); cards.add(new SetCardInfo("Choked Estuary", 299, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); cards.add(new SetCardInfo("Chromatic Lantern", 275, Rarity.RARE, mage.cards.c.ChromaticLantern.class)); + cards.add(new SetCardInfo("Cirdan the Shipwright", 50, Rarity.RARE, mage.cards.c.CirdanTheShipwright.class)); cards.add(new SetCardInfo("Clifftop Retreat", 300, Rarity.RARE, mage.cards.c.ClifftopRetreat.class)); cards.add(new SetCardInfo("Cloudstone Curio", 349, Rarity.MYTHIC, mage.cards.c.CloudstoneCurio.class)); cards.add(new SetCardInfo("Colossal Whale", 186, Rarity.RARE, mage.cards.c.ColossalWhale.class)); diff --git a/Mage/src/main/java/mage/choices/VoteHandler.java b/Mage/src/main/java/mage/choices/VoteHandler.java index dffd2f41f5c..e086fd08680 100644 --- a/Mage/src/main/java/mage/choices/VoteHandler.java +++ b/Mage/src/main/java/mage/choices/VoteHandler.java @@ -18,6 +18,7 @@ public abstract class VoteHandler { protected final Map> playerMap = new HashMap<>(); protected VoteHandlerAI aiVoteHint = null; + private boolean secret = false; public void doVotes(Ability source, Game game) { doVotes(source, game, null); @@ -28,6 +29,7 @@ public abstract class VoteHandler { this.playerMap.clear(); int stepCurrent = 0; int stepTotal = game.getState().getPlayersInRange(source.getControllerId(), game).size(); + List messages = new ArrayList<>(); for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { stepCurrent++; VoteEvent event = new VoteEvent(playerId, source); @@ -70,18 +72,25 @@ public abstract class VoteHandler { if (!Objects.equals(player, decidingPlayer)) { message += " (chosen by " + decidingPlayer.getName() + ')'; } - game.informPlayers(message); + if (secret) { + messages.add(message); + } else { + game.informPlayers(message); + } this.playerMap.computeIfAbsent(playerId, x -> new ArrayList<>()).add(vote); } } // show final results to players + if (secret) { + for (String message : messages) { + game.informPlayers(message); + } + } Map totalVotes = new LinkedHashMap<>(); // fill by possible choices - this.getPossibleVotes(source, game).forEach(vote -> { - totalVotes.putIfAbsent(vote, 0); - }); + this.getPossibleVotes(source, game).forEach(vote -> totalVotes.putIfAbsent(vote, 0)); // fill by real choices playerMap.entrySet() .stream() @@ -180,4 +189,8 @@ public abstract class VoteHandler { .map(Map.Entry::getKey) .collect(Collectors.toSet()); } + + public void setSecret(boolean secret) { + this.secret = secret; + } } diff --git a/Mage/src/main/java/mage/constants/AbilityWord.java b/Mage/src/main/java/mage/constants/AbilityWord.java index 91d7b988125..467171211f4 100644 --- a/Mage/src/main/java/mage/constants/AbilityWord.java +++ b/Mage/src/main/java/mage/constants/AbilityWord.java @@ -46,6 +46,7 @@ public enum AbilityWord { RAID("Raid"), RALLY("Rally"), REVOLT("Revolt"), + SECRET_COUNCIL("Secret council"), SPELL_MASTERY("Spell mastery"), STRIVE("Strive"), SWEEP("Sweep"),