From 02017b9a8827ed094d7def422dfc6c0955db6c54 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 17 Mar 2022 18:02:29 -0400 Subject: [PATCH] Implementing The Prismatic Piper (Ready for review) (#8164) * [CMR] Implemented The Prismatic Piper * updated commander validation to handle The Prismatic Piper * created abstract base class for commander variants * added ability to prismatic piper * added game init handling * small revert * small reorganization of tests * added some validation tests for piper * added more tests for piper * add another test * added decklist comments to tests * added some more piper tests * added another test * added mana option tests * added a companion test * fix conflict * updated abstract commander to work with Friends forever * merge fix * Deck: added details for illegal validation of companion card; Co-authored-by: Oleg Agafonov --- .../src/mage/deck/AbstractCommander.java | 1055 +++++++++++++++++ .../src/mage/deck/Brawl.java | 3 +- .../src/mage/deck/Commander.java | 933 +-------------- .../src/mage/deck/FreeformCommander.java | 147 +-- .../src/mage/deck/PennyDreadfulCommander.java | 172 +-- .../src/mage/cards/t/ThePrismaticPiper.java | 42 + Mage.Sets/src/mage/sets/CommanderLegends.java | 2 + Mage.Tests/piperDecks/ThePrismaticPiper1.dck | 3 + Mage.Tests/piperDecks/ThePrismaticPiper2.dck | 5 + Mage.Tests/piperDecks/ThePrismaticPiper3.dck | 5 + Mage.Tests/piperDecks/ThePrismaticPiper4.dck | 4 + Mage.Tests/piperDecks/ThePrismaticPiper5.dck | 6 + Mage.Tests/piperDecks/ThePrismaticPiper6.dck | 7 + Mage.Tests/piperDecks/ThePrismaticPiper7.dck | 4 + Mage.Tests/piperDecks/ThePrismaticPiper8.dck | 7 + .../piper/ThePrismaticPiperBaseTest.java | 16 + .../piper/ThePrismaticPiperTest1.java | 33 + .../piper/ThePrismaticPiperTest2.java | 53 + .../piper/ThePrismaticPiperTest3.java | 36 + .../piper/ThePrismaticPiperTest4.java | 25 + .../piper/ThePrismaticPiperTest5.java | 36 + .../piper/ThePrismaticPiperTest6.java | 26 + .../piper/ThePrismaticPiperTest7.java | 62 + .../piper/ThePrismaticPiperTest8.java | 60 + .../deck/CommanderDeckValidationTest.java | 74 ++ ....java => CompanionDeckValidationTest.java} | 2 +- .../serverside/deck/DeckValidatorTest.java | 12 - .../common/CommanderChooseColorAbility.java | 33 + .../abilities/keyword/CompanionAbility.java | 6 +- .../java/mage/cards/decks/Constructed.java | 2 +- .../src/main/java/mage/filter/FilterMana.java | 60 + .../java/mage/game/GameCommanderImpl.java | 91 +- Mage/src/main/java/mage/game/GameImpl.java | 3 +- 33 files changed, 1753 insertions(+), 1272 deletions(-) create mode 100644 Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java create mode 100644 Mage.Sets/src/mage/cards/t/ThePrismaticPiper.java create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper1.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper2.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper3.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper4.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper5.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper6.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper7.dck create mode 100644 Mage.Tests/piperDecks/ThePrismaticPiper8.dck create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperBaseTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest1.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest2.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest3.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest4.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest5.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest6.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest7.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest8.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java rename Mage.Tests/src/test/java/org/mage/test/serverside/deck/{CompanionTest.java => CompanionDeckValidationTest.java} (98%) create mode 100644 Mage/src/main/java/mage/abilities/common/CommanderChooseColorAbility.java diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java new file mode 100644 index 0000000000..6f9264a825 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java @@ -0,0 +1,1055 @@ +package mage.deck; + +import mage.MageObject; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.CanBeYourCommanderAbility; +import mage.abilities.common.CommanderChooseColorAbility; +import mage.abilities.keyword.CompanionAbility; +import mage.abilities.keyword.FriendsForeverAbility; +import mage.abilities.keyword.PartnerAbility; +import mage.abilities.keyword.PartnerWithAbility; +import mage.cards.Card; +import mage.cards.ExpansionSet; +import mage.cards.Sets; +import mage.cards.decks.Constructed; +import mage.cards.decks.Deck; +import mage.cards.decks.DeckValidatorErrorType; +import mage.constants.CardType; +import mage.filter.FilterMana; +import mage.util.CardUtil; +import mage.util.ManaUtil; + +import java.util.*; +import java.util.stream.Stream; + +/** + * @author TheElk801 + */ +public abstract class AbstractCommander extends Constructed { + + protected final List bannedCommander = new ArrayList<>(); + protected final List bannedPartner = new ArrayList<>(); + protected boolean partnerAllowed = true; + + public AbstractCommander() { + super("Commander"); + for (ExpansionSet set : Sets.getInstance().values()) { + if (set.getSetType().isEternalLegal()) { + setCodes.add(set.getCode()); + } + } + banned.add("Ancestral Recall"); + banned.add("Balance"); + banned.add("Biorhythm"); + banned.add("Black Lotus"); + banned.add("Braids, Cabal Minion"); + banned.add("Channel"); + banned.add("Coalition Victory"); + banned.add("Emrakul, the Aeons Torn"); + banned.add("Erayo, Soratami Ascendant"); + banned.add("Fastbond"); + banned.add("Flash"); + banned.add("Gifts Ungiven"); + banned.add("Griselbrand"); + banned.add("Hullbreacher"); + banned.add("Iona, Shield of Emeria"); + banned.add("Karakas"); + banned.add("Leovold, Emissary of Trest"); + banned.add("Library of Alexandria"); + banned.add("Limited Resources"); + banned.add("Lutri, the Spellchaser"); + banned.add("Mox Emerald"); + banned.add("Mox Jet"); + banned.add("Mox Pearl"); + banned.add("Mox Ruby"); + banned.add("Mox Sapphire"); + banned.add("Panoptic Mirror"); + banned.add("Paradox Engine"); + banned.add("Primeval Titan"); + banned.add("Prophet of Kruphix"); + banned.add("Recurring Nightmare"); + banned.add("Rofellos, Llanowar Emissary"); + banned.add("Sundering Titan"); + banned.add("Sway of the Stars"); + banned.add("Sylvan Primordial"); + banned.add("Time Vault"); + banned.add("Time Walk"); + banned.add("Tinker"); + banned.add("Tolarian Academy"); + banned.add("Trade Secrets"); + banned.add("Upheaval"); + banned.add("Worldfire"); + banned.add("Yawgmoth's Bargain"); + } + + public AbstractCommander(String name) { + super(name); + } + + public AbstractCommander(String name, String shortName) { + super(name, shortName); + } + + @Override + public int getDeckMinSize() { + return 98; + } + + @Override + public int getSideboardMinSize() { + return 1; + } + + protected abstract boolean checkBanned(Map counts); + + protected boolean checkCommander(Card commander) { + if ((!commander.hasCardTypeForDeckbuilding(CardType.CREATURE) || !commander.isLegendary()) + && !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) { + addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander invalid (" + commander.getName() + ')', true); + return false; + } + return true; + } + + private boolean checkColorIdentity(Deck deck, FilterMana colorIdentity, Set commanders) { + int piperCount = commanders + .stream() + .filter(CommanderChooseColorAbility::checkCard) + .mapToInt(x -> 1) + .sum(); + if (piperCount == 0) { + boolean valid = true; + for (Card card : deck.getCards()) { + if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { + addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); + valid = false; + } + } + for (Card card : deck.getSideboard()) { + if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { + addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); + valid = false; + } + } + return valid; + } + FilterMana filterMana = new FilterMana(); + Stream.concat( + deck.getCards().stream(), + deck.getSideboard().stream() + ).map(Card::getColorIdentity).forEach(filterMana::addAll); + if (colorIdentity.getColorCount() + piperCount >= filterMana.getColorCount()) { + return true; + } + StringBuilder sb = new StringBuilder() + .append("Invalid color, commander color identity has ") + .append(colorIdentity.getColorCount()) + .append(" color") + .append(colorIdentity.getColorCount() > 1 ? "s" : "") + .append(", plus ") + .append(piperCount) + .append(" cop") + .append(piperCount > 1 ? "ies" : "y") + .append(" of The Prismatic Piper, but the total amount of colors in the deck is ") + .append(filterMana.getColorCount()); + addError(DeckValidatorErrorType.OTHER, "The Prismatic Piper", sb.toString()); + return false; + } + + @Override + public boolean validate(Deck deck) { + boolean valid = true; + errorsList.clear(); + FilterMana colorIdentity = new FilterMana(); + Set commanders = new HashSet<>(); + Card companion; + + int sbsize = deck.getSideboard().size(); + Card card1; + Card card2; + Card card3; + Iterator iter; + switch (deck.getSideboard().size()) { + case 1: + companion = null; + commanders.add(deck.getSideboard().iterator().next()); + break; + case 2: + iter = deck.getSideboard().iterator(); + card1 = iter.next(); + card2 = iter.next(); + if (card1.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { + companion = card1; + commanders.add(card2); + } else if (card2.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { + companion = card2; + commanders.add(card1); + } else { + companion = null; + commanders.add(card1); + commanders.add(card2); + } + break; + case 3: + iter = deck.getSideboard().iterator(); + card1 = iter.next(); + card2 = iter.next(); + card3 = iter.next(); + if (card1.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { + companion = card1; + commanders.add(card2); + commanders.add(card3); + } else if (card2.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { + companion = card2; + commanders.add(card1); + commanders.add(card3); + } else if (card3.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { + companion = card3; + commanders.add(card1); + commanders.add(card2); + } else { + companion = null; + addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); + valid = false; + } + break; + default: + companion = null; + addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); + valid = false; + } + + if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) { + addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); + valid = false; + } else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) { + addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); + valid = false; + } + + Map counts = new HashMap<>(); + countCards(counts, deck.getCards()); + countCards(counts, deck.getSideboard()); + valid = checkCounts(1, counts) && valid; + + valid = checkBanned(counts) && valid; + + Set commanderNames = new HashSet<>(); + for (Card commander : commanders) { + commanderNames.add(commander.getName()); + } + if (commanders.size() == 2 + && commanders + .stream() + .map(MageObject::getAbilities) + .filter(abilities -> abilities.contains(PartnerAbility.getInstance())) + .count() != 2 + && commanders + .stream() + .map(MageObject::getAbilities) + .filter(abilities -> abilities.contains(FriendsForeverAbility.getInstance())) + .count() != 2 + && !CardUtil + .castStream(commanders.stream().map(MageObject::getAbilities), PartnerWithAbility.class) + .map(PartnerWithAbility::getPartnerName) + .allMatch(commanderNames::contains)) { + for (Card commander : commanders) { + addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander with invalid Partner (" + commander.getName() + ')', true); + valid = false; + } + } + for (Card commander : commanders) { + if (bannedCommander.contains(commander.getName())) { + addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander banned (" + commander.getName() + ')', true); + valid = false; + } + if ((!commander.hasCardTypeForDeckbuilding(CardType.CREATURE) || !commander.isLegendary()) + && !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) { + addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander invalid (" + commander.getName() + ')', true); + valid = false; + } + if (commanders.size() == 2 && bannedPartner.contains(commander.getName())) { + addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander Partner banned (" + commander.getName() + ')', true); + valid = false; + } + ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity()); + } + + // no needs in cards check on wrong commanders + if (!valid) { + return false; + } + + valid = checkColorIdentity(deck, colorIdentity, commanders); + + for (Card card : deck.getCards()) { + if (!isSetAllowed(card.getExpansionSetCode())) { + if (!legalSets(card)) { + addError(DeckValidatorErrorType.WRONG_SET, card.getName(), "Not allowed Set: " + card.getExpansionSetCode(), true); + valid = false; + } + } + } + for (Card card : deck.getSideboard()) { + if (!isSetAllowed(card.getExpansionSetCode())) { + if (!legalSets(card)) { + addError(DeckValidatorErrorType.WRONG_SET, card.getName(), "Not allowed Set: " + card.getExpansionSetCode(), true); + valid = false; + } + } + } + // Check for companion legality + if (companion != null) { + Set cards = new HashSet<>(deck.getCards()); + cards.addAll(commanders); + for (Ability ability : companion.getAbilities()) { + if (ability instanceof CompanionAbility) { + CompanionAbility companionAbility = (CompanionAbility) ability; + if (!companionAbility.isLegal(cards, getDeckMinSize())) { + addError(DeckValidatorErrorType.PRIMARY, companion.getName(), + String.format("Commander companion illegal: %s", companionAbility.getLegalRule()), true); + valid = false; + } + break; + } + } + } + return valid; + } + + @Override + public int getEdhPowerLevel(Deck deck) { + if (deck == null) { + return 0; + } + + int edhPowerLevel = 0; + int commanderColors = 0; + int numberInfinitePieces = 0; + + for (Card card : deck.getCards()) { + + int thisMaxPower = 0; + + // Examine rules to work out most egregious functions in edh + boolean anyNumberOfTarget = false; + boolean annihilator = false; + boolean buyback = false; + boolean cascade = false; + boolean cantBe = false; + boolean cantUntap = false; + boolean copy = false; + boolean costLessEach = false; + boolean createToken = false; + boolean dredge = false; + boolean exile = false; + boolean exileAll = false; + boolean counter = false; + boolean destroy = false; + boolean destroyAll = false; + boolean each = false; + boolean exalted = false; + boolean doesntUntap = false; + boolean drawCards = false; + boolean evoke = false; + boolean extraTurns = false; + boolean flash = false; + boolean flashback = false; + boolean flicker = false; + boolean gainControl = false; + boolean hexproof = false; + boolean infect = false; + boolean lifeTotalBecomes = false; + boolean mayCastForFree = false; + boolean menace = false; + boolean miracle = false; + boolean overload = false; + boolean persist = false; + boolean preventDamage = false; + boolean proliferate = false; + boolean protection = false; + boolean putUnderYourControl = false; + boolean retrace = false; + boolean returnFromYourGY = false; + boolean sacrifice = false; + boolean shroud = false; + boolean skip = false; + boolean sliver = false; + boolean storm = false; + boolean trample = false; + boolean tutor = false; + boolean tutorBasic = false; + boolean twiceAs = false; + boolean unblockable = false; + boolean undying = false; + boolean untapTarget = false; + boolean wheneverEnters = false; + boolean whenCounterThatSpell = false; + boolean xCost = false; + boolean youControlTarget = false; + boolean yourOpponentsControl = false; + boolean whenYouCast = false; + + for (String str : card.getRules()) { + String s = str.toLowerCase(Locale.ENGLISH); + annihilator |= s.contains("annihilator"); + anyNumberOfTarget |= s.contains("any number"); + buyback |= s.contains("buyback"); + cantUntap |= s.contains("can't untap") || s.contains("don't untap"); + cantBe |= s.contains("can't be"); + cascade |= s.contains("cascade"); + copy |= s.contains("copy"); + costLessEach |= s.contains("cost") || s.contains("less") || s.contains("each"); + counter |= s.contains("counter") && s.contains("target"); + createToken |= s.contains("create") && s.contains("token"); + destroy |= s.contains("destroy"); + destroyAll |= s.contains("destroy all"); + doesntUntap |= s.contains("doesn't untap"); + doesntUntap |= s.contains("don't untap"); + drawCards |= s.contains("draw cards"); + dredge |= s.contains("dredge"); + each |= s.contains("each"); + evoke |= s.contains("evoke"); + exalted |= s.contains("exalted"); + exile |= s.contains("exile"); + exileAll |= s.contains("exile") && s.contains(" all "); + extraTurns |= s.contains("extra turn"); + flicker |= s.contains("exile") && s.contains("return") && s.contains("to the battlefield under"); + flash |= s.contains("flash"); + flashback |= s.contains("flashback"); + gainControl |= s.contains("gain control"); + hexproof |= s.contains("hexproof"); + infect |= s.contains("infect"); + lifeTotalBecomes |= s.contains("life total becomes"); + mayCastForFree |= s.contains("may cast") && s.contains("without paying"); + menace |= s.contains("menace"); + miracle |= s.contains("miracle"); + overload |= s.contains("overload"); + persist |= s.contains("persist"); + preventDamage |= s.contains("prevent") && s.contains("all") && s.contains("damage"); + proliferate |= s.contains("proliferate"); + protection |= s.contains("protection"); + putUnderYourControl |= s.contains("put") && s.contains("under your control"); + retrace |= s.contains("retrace"); + returnFromYourGY |= s.contains("return") && s.contains("from your graveyard"); + sacrifice |= s.contains("sacrifice"); + shroud |= s.contains("shroud"); + skip |= s.contains("skip"); + sliver |= s.contains("sliver"); + storm |= s.contains("storm"); + trample |= s.contains("trample"); + tutor |= s.contains("search your library") && !s.contains("basic land"); + tutorBasic |= s.contains("search your library") && s.contains("basic land"); + twiceAs |= s.contains("twice that many") || s.contains("twice as much"); + unblockable |= s.contains("can't be blocked"); + undying |= s.contains("undying"); + untapTarget |= s.contains("untap target"); + whenCounterThatSpell |= s.contains("when") && s.contains("counter that spell"); + wheneverEnters |= s.contains("when") && s.contains("another") && s.contains("enters"); + youControlTarget |= s.contains("you control target"); + yourOpponentsControl |= s.contains("your opponents control"); + whenYouCast |= s.contains("when you cast") || s.contains("whenever you cast"); + } + + for (String s : card.getManaCostSymbols()) { + if (s.contains("X")) { + xCost = true; + } + } + for (Ability a : card.getAbilities()) { + for (String s : a.getManaCostSymbols()) { + if (s.contains("X")) { + xCost = true; + } + } + } + + if (extraTurns) { + thisMaxPower = Math.max(thisMaxPower, 7); + } + if (buyback) { + thisMaxPower = Math.max(thisMaxPower, 6); + } + if (tutor) { + thisMaxPower = Math.max(thisMaxPower, 6); + } + if (annihilator) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (cantUntap) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (costLessEach) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (infect) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (overload) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (twiceAs) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (cascade) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (doesntUntap) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (each) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (exileAll) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (flash) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (flashback) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (flicker) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (gainControl) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (lifeTotalBecomes) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (mayCastForFree) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (preventDamage) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (proliferate) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (protection) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (putUnderYourControl) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (returnFromYourGY) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (sacrifice) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (skip) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (storm) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (unblockable) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (whenCounterThatSpell) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (wheneverEnters) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (xCost) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (youControlTarget) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (yourOpponentsControl) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (whenYouCast) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (anyNumberOfTarget) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (createToken) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (destroyAll) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (dredge) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (hexproof) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (shroud) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (undying) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (persist) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (cantBe) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (evoke) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (exile) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (menace) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (miracle) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (sliver) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (untapTarget) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (copy) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (counter) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (destroy) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (drawCards) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (exalted) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (retrace) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (trample) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (tutorBasic) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + + if (card.isPlaneswalker()) { + thisMaxPower = Math.max(thisMaxPower, 6); + } + + String cn = card.getName().toLowerCase(Locale.ENGLISH); + if (cn.equals("ancient tomb") + || cn.equals("anafenza, the foremost") + || cn.equals("arcum dagsson") + || cn.equals("armageddon") + || cn.equals("aura shards") + || cn.equals("azami, lady of scrolls") + || cn.equals("azusa, lost but seeking") + || cn.equals("back to basics") + || cn.equals("bane of progress") + || cn.equals("basalt monolith") + || cn.equals("blightsteel collossus") + || cn.equals("blood moon") + || cn.equals("braids, cabal minion") + || cn.equals("cabal coffers") + || cn.equals("captain sisay") + || cn.equals("celestial dawn") + || cn.equals("child of alara") + || cn.equals("coalition relic") + || cn.equals("craterhoof behemoth") + || cn.equals("deepglow skate") + || cn.equals("derevi, empyrial tactician") + || cn.equals("dig through time") + || cn.equals("edric, spymaster of trest") + || cn.equals("elesh norn, grand cenobite") + || cn.equals("entomb") + || cn.equals("force of will") + || cn.equals("food chain") + || cn.equals("gaddock teeg") + || cn.equals("gaea's cradle") + || cn.equals("grand arbiter augustin iv") + || cn.equals("grim monolith") + || cn.equals("hermit druid") + || cn.equals("hokori, dust drinker") + || cn.equals("humility") + || cn.equals("imperial seal") + || cn.equals("iona, shield of emeria") + || cn.equals("jin-gitaxias, core augur") + || cn.equals("karador, ghost chieftain") + || cn.equals("karakas") + || cn.equals("kataki, war's wage") + || cn.equals("knowledge pool") + || cn.equals("linvala, keeper of silence") + || cn.equals("living death") + || cn.equals("llawan, cephalid empress") + || cn.equals("loyal retainers") + || cn.equals("maelstrom wanderer") + || cn.equals("malfegor") + || cn.equals("master of cruelties") + || cn.equals("mana crypt") + || cn.equals("mana drain") + || cn.equals("mana vault") + || cn.equals("michiko konda, truth seeker") + || cn.equals("nath of the gilt-leaf") + || cn.equals("natural order") + || cn.equals("necrotic ooze") + || cn.equals("nicol bolas") + || cn.equals("numot, the devastator") + || cn.equals("oath of druids") + || cn.equals("pattern of rebirth") + || cn.equals("protean hulk") + || cn.equals("purphoros, god of the forge") + || cn.equals("ravages of war") + || cn.equals("reclamation sage") + || cn.equals("sen triplets") + || cn.equals("serra's sanctum") + || cn.equals("sheoldred, whispering one") + || cn.equals("sol ring") + || cn.equals("spore frog") + || cn.equals("stasis") + || cn.equals("strip mine") + || cn.equals("the tabernacle at pendrell vale") + || cn.equals("tinker") + || cn.equals("treasure cruise") + || cn.equals("urabrask the hidden") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("winter orb") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 12); + } + + // Parts of infinite combos + if (cn.equals("animate artifact") || cn.equals("animar, soul of element") + || cn.equals("archaeomancer") + || cn.equals("ashnod's altar") || cn.equals("azami, lady of scrolls") + || cn.equals("aura flux") + || cn.equals("basalt monolith") || cn.equals("brago, king eternal") + || cn.equals("candelabra of tawnos") || cn.equals("cephalid aristocrat") + || cn.equals("cephalid illusionist") || cn.equals("changeling berserker") + || cn.equals("consecrated sphinx") + || cn.equals("cyclonic rift") + || cn.equals("the chain veil") + || cn.equals("cinderhaze wretch") || cn.equals("cryptic gateway") + || cn.equals("deadeye navigator") || cn.equals("derevi, empyrial tactician") + || cn.equals("doubling season") || cn.equals("dross scorpion") + || cn.equals("earthcraft") || cn.equals("erratic portal") + || cn.equals("enter the infinite") || cn.equals("omniscience") + || cn.equals("exquisite blood") || cn.equals("future sight") + || cn.equals("genesis chamber") + || cn.equals("ghave, guru of spores") + || cn.equals("grave pact") + || cn.equals("grave titan") || cn.equals("great whale") + || cn.equals("grim monolith") || cn.equals("gush") + || cn.equals("hellkite charger") || cn.equals("intruder alarm") + || cn.equals("helm of obedience") + || cn.equals("hermit druid") + || cn.equals("humility") + || cn.equals("iona, shield of emeria") + || cn.equals("karn, silver golem") || cn.equals("kiki-jiki, mirror breaker") + || cn.equals("krark-clan ironworks") || cn.equals("krenko, mob boss") + || cn.equals("krosan restorer") || cn.equals("laboratory maniac") + || cn.equals("leonin relic-warder") || cn.equals("leyline of the void") + || cn.equals("memnarch") + || cn.equals("meren of clan nel toth") || cn.equals("mikaeus, the unhallowed") + || cn.equals("mindcrank") || cn.equals("mindslaver") + || cn.equals("minion reflector") || cn.equals("mycosynth lattice") + || cn.equals("myr turbine") || cn.equals("narset, enlightened master") + || cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary") + || cn.equals("notion thief") + || cn.equals("opalescence") || cn.equals("ornithopter") + || cn.equals("paradox engine") + || cn.equals("purphoros, god of the forge") + || cn.equals("peregrine drake") || cn.equals("palinchron") + || cn.equals("planar portal") || cn.equals("power artifact") + || cn.equals("rings of brighthearth") || cn.equals("rite of replication") + || cn.equals("sanguine bond") || cn.equals("sensei's divining top") + || cn.equals("splinter twin") || cn.equals("stony silence") + || cn.equals("sunder") + || cn.equals("storm cauldron") || cn.equals("teferi's puzzle box") + || cn.equals("tangle wire") + || cn.equals("teferi, mage of zhalfir") + || cn.equals("tezzeret the seeker") || cn.equals("time stretch") + || cn.equals("time warp") || cn.equals("training grounds") + || cn.equals("triskelavus") || cn.equals("triskelion") + || cn.equals("turnabout") || cn.equals("umbral mantle") + || cn.equals("uyo, silent prophet") || cn.equals("voltaic key") + || cn.equals("workhorse") || cn.equals("worldgorger dragon") + || cn.equals("worthy cause") || cn.equals("yawgmoth's will") + || cn.equals("zealous conscripts")) { + thisMaxPower = Math.max(thisMaxPower, 15); + numberInfinitePieces++; + } + + // Saltiest cards (edhrec) + if (cn.equals("acid rain") + || cn.equals("agent of treachery") + || cn.equals("apocalypse") + || cn.equals("armageddon") + || cn.equals("atraxa, praetors' voice") + || cn.equals("aura shards") + || cn.equals("avacyn, angel of hope") + || cn.equals("back to basics") + || cn.equals("bend or break") + || cn.equals("blightsteel colossus") + || cn.equals("blood moon") + || cn.equals("boil") + || cn.equals("boiling seas") + || cn.equals("bribery") + || cn.equals("burning sands") + || cn.equals("card view") + || cn.equals("cataclysm") + || cn.equals("catastrophe") + || cn.equals("chulane, teller of tales") + || cn.equals("confusion in the ranks") + || cn.equals("consecrated sphinx") + || cn.equals("contamination") + || cn.equals("craterhoof behemoth") + || cn.equals("cyclonic rift") + || cn.equals("death cloud") + || cn.equals("decree of annihilation") + || cn.equals("decree of silence") + || cn.equals("demonic consultation") + || cn.equals("derevi, empyrial tactician") + || cn.equals("devastation") + || cn.equals("divine intervention") + || cn.equals("dockside extortionist") + || cn.equals("doomsday") + || cn.equals("doubling season") + || cn.equals("drannith magistrate") + || cn.equals("elesh norn, grand cenobite") + || cn.equals("embargo") + || cn.equals("emrakul, the promised end") + || cn.equals("epicenter") + || cn.equals("expropriate") + || cn.equals("fall of the thran") + || cn.equals("fierce guardianship") + || cn.equals("food chain") + || cn.equals("force of negation") + || cn.equals("force of will") + || cn.equals("gaddock teeg") + || cn.equals("gaea's cradle") + || cn.equals("gilded drake") + || cn.equals("glenn, the voice of calm") + || cn.equals("global ruin") + || cn.equals("golos, tireless pilgrim") + || cn.equals("grand arbiter augustin iv") + || cn.equals("grip of chaos") + || cn.equals("hokori, dust drinker") + || cn.equals("humility") + || cn.equals("impending disaster") + || cn.equals("invoke prejudice") + || cn.equals("iona, shield of emeria") + || cn.equals("jin-gitaxias, core augur") + || cn.equals("jokulhaups") + || cn.equals("keldon firebombers") + || cn.equals("kinnan, bonder prodigy") + || cn.equals("kozilek, butcher of truth") + || cn.equals("land equilibrium") + || cn.equals("linvala, keeper of silence") + || cn.equals("magister sphinx") + || cn.equals("mana breach") + || cn.equals("mana crypt") + || cn.equals("mana drain") + || cn.equals("mana vortex") + || cn.equals("mindslaver") + || cn.equals("narset, enlightened master") + || cn.equals("narset, parter of veils") + || cn.equals("negan, the cold-blooded") + || cn.equals("nether void") + || cn.equals("nexus of fate") + || cn.equals("notion thief") + || cn.equals("obliterate") + || cn.equals("oko, thief of crowns") + || cn.equals("oloro, ageless ascetic") + || cn.equals("omniscience") + || cn.equals("opposition agent") + || cn.equals("oppression") + || cn.equals("overwhelming splendor") + || cn.equals("palinchron") + || cn.equals("paradox engine") + || cn.equals("possessed portal") + || cn.equals("price of glory") + || cn.equals("protean hulk") + || cn.equals("ravages of war") + || cn.equals("rhystic study") + || cn.equals("rick, steadfast leader") + || cn.equals("rising waters") + || cn.equals("ruination") + || cn.equals("scrambleverse") + || cn.equals("seedborn muse") + || cn.equals("sen triplets") + || cn.equals("sire of insanity") + || cn.equals("skithiryx, the blight dragon") + || cn.equals("smokestack") + || cn.equals("smothering tithe") + || cn.equals("sorin markov") + || cn.equals("stasis") + || cn.equals("static orb") + || cn.equals("storage matrix") + || cn.equals("sunder") + || cn.equals("survival of the fittest") + || cn.equals("table view") + || cn.equals("tainted aether") + || cn.equals("tectonic break") + || cn.equals("teferi's protection") + || cn.equals("teferi, master of time") + || cn.equals("teferi, time raveler") + || cn.equals("temporal manipulation") + || cn.equals("tergrid, god of fright") + || cn.equals("text view") + || cn.equals("thassa's oracle") + || cn.equals("the tabernacle at pendrell vale") + || cn.equals("thieves' auction") + || cn.equals("thoughts of ruin") + || cn.equals("thrasios, triton hero") + || cn.equals("time stretch") + || cn.equals("time warp") + || cn.equals("tooth and nail") + || cn.equals("torment of hailfire") + || cn.equals("torpor orb") + || cn.equals("triumph of the hordes") + || cn.equals("ugin, the spirit dragon") + || cn.equals("ulamog, the ceaseless hunger") + || cn.equals("ulamog, the infinite gyre") + || cn.equals("urza, lord high artificer") + || cn.equals("void winnower") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("wake of destruction") + || cn.equals("warp world") + || cn.equals("winter orb") + || cn.equals("xanathar, guild kingpin") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 15); + } + edhPowerLevel += thisMaxPower; + } + + ObjectColor color = null; + for (Card commander : deck.getSideboard()) { + int thisMaxPower = 0; + String cn = commander.getName().toLowerCase(Locale.ENGLISH); + if (color == null) { + color = commander.getColor(null); + } else { + color = color.union(commander.getColor(null)); + } + + FilterMana commanderColor = commander.getColorIdentity(); + if (commanderColor.isWhite()) { + color.setWhite(true); + } + if (commanderColor.isBlue()) { + color.setBlue(true); + } + if (commanderColor.isBlack()) { + color.setBlack(true); + } + if (commanderColor.isRed()) { + color.setRed(true); + } + if (commanderColor.isGreen()) { + color.setGreen(true); + } + + // Least fun commanders + if (cn.equals("animar, soul of element") + || cn.equals("anafenza, the foremost") + || cn.equals("arcum dagsson") + || cn.equals("azami, lady of scrolls") + || cn.equals("azusa, lost but seeking") + || cn.equals("brago, king eternal") + || cn.equals("braids, cabal minion") + || cn.equals("captain sisay") + || cn.equals("child of alara") + || cn.equals("derevi, empyrial tactician") + || cn.equals("edric, spymaster of trest") + || cn.equals("elesh norn, grand cenobite") + || cn.equals("gaddock teeg") + || cn.equals("grand arbiter augustin iv") + || cn.equals("hokori, dust drinker") + || cn.equals("iona, shield of emeria") + || cn.equals("jin-gitaxias, core augur") + || cn.equals("kaalia of the vast") + || cn.equals("karador, ghost chieftain") + || cn.equals("leovold, emissary of trest") + || cn.equals("linvala, keeper of silence") + || cn.equals("llawan, cephalid empress") + || cn.equals("maelstrom wanderer") + || cn.equals("malfegor") + || cn.equals("memnarch") + || cn.equals("meren of clan nel toth") + || cn.equals("michiko konda, truth seeker") + || cn.equals("mikaeus the unhallowed") + || cn.equals("narset, enlightened master") + || cn.equals("nath of the gilt-leaf") + || cn.equals("nekusar, the mindrazer") + || cn.equals("norin the wary") + || cn.equals("numot, the devastator") + || cn.equals("prossh, skyraider of kher") + || cn.equals("purphoros, god of the forge") + || cn.equals("sen triplets") + || cn.equals("sheoldred, whispering one") + || cn.equals("teferi, mage of zhalfir") + || cn.equals("urabrask the hidden") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 25); + } + + // Saltiest commanders + if (cn.equals("atraxa, praetors' voice") + || cn.equals("avacyn, angel of hope") + || cn.equals("chulane, teller of tales") + || cn.equals("derevi, empyrial tactician") + || cn.equals("elesh norn, grand cenobite") + || cn.equals("emrakul, the promised end") + || cn.equals("gaddock teeg") + || cn.equals("glenn, the voice of calm") + || cn.equals("golos, tireless pilgrim") + || cn.equals("grand arbiter augustin iv") + || cn.equals("hokori, dust drinker") + || cn.equals("iona, shield of emeria") + || cn.equals("jin-gitaxias, core augur") + || cn.equals("kinnan, bonder prodigy") + || cn.equals("kozilek, butcher of truth") + || cn.equals("linvala, keeper of silence") + || cn.equals("narset, enlightened master") + || cn.equals("negan, the cold-blooded") + || cn.equals("oko, thief of crowns") + || cn.equals("oloro, ageless ascetic") + || cn.equals("rick, steadfast leader") + || cn.equals("sen triplets") + || cn.equals("skithiryx, the blight dragon") + || cn.equals("teferi, master of time") + || cn.equals("teferi, time raveler") + || cn.equals("thrasios, triton hero") + || cn.equals("ulamog, the ceaseless hunger") + || cn.equals("ulamog, the infinite gyre") + || cn.equals("urza, lord high artificer") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("xanathar, guild kingpin") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 20); + } + edhPowerLevel += thisMaxPower; + } + + edhPowerLevel += numberInfinitePieces * 18; + edhPowerLevel = Math.round(edhPowerLevel / 10); + if (edhPowerLevel >= 100) { + edhPowerLevel = 99; + } + if (color != null) { + edhPowerLevel += (color.isWhite() ? 10000000 : 0); + edhPowerLevel += (color.isBlue() ? 1000000 : 0); + edhPowerLevel += (color.isBlack() ? 100000 : 0); + edhPowerLevel += (color.isRed() ? 10000 : 0); + edhPowerLevel += (color.isGreen() ? 1000 : 0); + } + return edhPowerLevel; + } +} diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Brawl.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Brawl.java index 0b73210f0b..e4c3fb249f 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Brawl.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Brawl.java @@ -156,7 +156,8 @@ public class Brawl extends Constructed { if (ability instanceof CompanionAbility) { CompanionAbility companionAbility = (CompanionAbility) ability; if (!companionAbility.isLegal(cards, getDeckMinSize())) { - addError(DeckValidatorErrorType.PRIMARY, companion.getName(), "Brawl Companion Invalid", true); + addError(DeckValidatorErrorType.PRIMARY, companion.getName(), + String.format("Brawl companion illegal: %s", companionAbility.getLegalRule()), true); valid = false; } break; diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java index cdf129c452..18827f9c4a 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java @@ -1,34 +1,15 @@ package mage.deck; -import mage.MageObject; -import mage.ObjectColor; -import mage.abilities.Ability; -import mage.abilities.common.CanBeYourCommanderAbility; -import mage.abilities.keyword.CompanionAbility; -import mage.abilities.keyword.FriendsForeverAbility; -import mage.abilities.keyword.PartnerAbility; -import mage.abilities.keyword.PartnerWithAbility; -import mage.cards.Card; import mage.cards.ExpansionSet; import mage.cards.Sets; -import mage.cards.decks.Constructed; -import mage.cards.decks.Deck; import mage.cards.decks.DeckValidatorErrorType; -import mage.constants.CardType; -import mage.filter.FilterMana; -import mage.util.CardUtil; -import mage.util.ManaUtil; -import java.util.*; +import java.util.Map; /** * @author Plopman */ -public class Commander extends Constructed { - - protected final List bannedCommander = new ArrayList<>(); - protected final List bannedPartner = new ArrayList<>(); - protected boolean partnerAllowed = true; +public class Commander extends AbstractCommander { public Commander() { super("Commander"); @@ -97,922 +78,14 @@ public class Commander extends Constructed { } @Override - public int getDeckMinSize() { - return 98; - } - - @Override - public int getSideboardMinSize() { - return 1; - } - - @Override - public boolean validate(Deck deck) { + protected boolean checkBanned(Map counts) { boolean valid = true; - errorsList.clear(); - FilterMana colorIdentity = new FilterMana(); - Set commanders = new HashSet<>(); - Card companion; - - int sbsize = deck.getSideboard().size(); - Card card1; - Card card2; - Card card3; - Iterator iter; - switch (deck.getSideboard().size()) { - case 1: - companion = null; - commanders.add(deck.getSideboard().iterator().next()); - break; - case 2: - iter = deck.getSideboard().iterator(); - card1 = iter.next(); - card2 = iter.next(); - if (card1.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { - companion = card1; - commanders.add(card2); - } else if (card2.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { - companion = card2; - commanders.add(card1); - } else { - companion = null; - commanders.add(card1); - commanders.add(card2); - } - break; - case 3: - iter = deck.getSideboard().iterator(); - card1 = iter.next(); - card2 = iter.next(); - card3 = iter.next(); - if (card1.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { - companion = card1; - commanders.add(card2); - commanders.add(card3); - } else if (card2.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { - companion = card2; - commanders.add(card1); - commanders.add(card3); - } else if (card3.getAbilities().stream().anyMatch(CompanionAbility.class::isInstance)) { - companion = card3; - commanders.add(card1); - commanders.add(card2); - } else { - companion = null; - addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); - valid = false; - } - break; - default: - companion = null; - addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); - valid = false; - } - - if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) { - addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); - valid = false; - } else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) { - addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); - valid = false; - } - - Map counts = new HashMap<>(); - countCards(counts, deck.getCards()); - countCards(counts, deck.getSideboard()); - valid = checkCounts(1, counts) && valid; - for (String bannedCard : banned) { if (counts.containsKey(bannedCard)) { addError(DeckValidatorErrorType.BANNED, bannedCard, "Banned", true); valid = false; } } - - Set commanderNames = new HashSet<>(); - for (Card commander : commanders) { - commanderNames.add(commander.getName()); - } - if (commanders.size() == 2 - && commanders - .stream() - .map(MageObject::getAbilities) - .filter(abilities -> abilities.contains(PartnerAbility.getInstance())) - .count() != 2 - && commanders - .stream() - .map(MageObject::getAbilities) - .filter(abilities -> abilities.contains(FriendsForeverAbility.getInstance())) - .count() != 2 - && !CardUtil - .castStream(commanders.stream().map(MageObject::getAbilities), PartnerWithAbility.class) - .map(PartnerWithAbility::getPartnerName) - .allMatch(commanderNames::contains)) { - for (Card commander : commanders) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander with invalid Partner (" + commander.getName() + ')', true); - valid = false; - } - } - for (Card commander : commanders) { - if (bannedCommander.contains(commander.getName())) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander banned (" + commander.getName() + ')', true); - valid = false; - } - if ((!commander.hasCardTypeForDeckbuilding(CardType.CREATURE) || !commander.isLegendary()) - && !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander invalid (" + commander.getName() + ')', true); - valid = false; - } - if (commanders.size() == 2 && bannedPartner.contains(commander.getName())) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander Partner banned (" + commander.getName() + ')', true); - valid = false; - } - ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity()); - } - - // no needs in cards check on wrong commanders - if (!valid) { - return false; - } - - for (Card card : deck.getCards()) { - if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { - addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); - valid = false; - } - } - for (Card card : deck.getSideboard()) { - if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { - addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); - valid = false; - } - } - for (Card card : deck.getCards()) { - if (!isSetAllowed(card.getExpansionSetCode())) { - if (!legalSets(card)) { - addError(DeckValidatorErrorType.WRONG_SET, card.getName(), "Not allowed Set: " + card.getExpansionSetCode(), true); - valid = false; - } - } - } - for (Card card : deck.getSideboard()) { - if (!isSetAllowed(card.getExpansionSetCode())) { - if (!legalSets(card)) { - addError(DeckValidatorErrorType.WRONG_SET, card.getName(), "Not allowed Set: " + card.getExpansionSetCode(), true); - valid = false; - } - } - } - // Check for companion legality - if (companion != null) { - Set cards = new HashSet<>(deck.getCards()); - cards.addAll(commanders); - for (Ability ability : companion.getAbilities()) { - if (ability instanceof CompanionAbility) { - CompanionAbility companionAbility = (CompanionAbility) ability; - if (!companionAbility.isLegal(cards, getDeckMinSize())) { - addError(DeckValidatorErrorType.PRIMARY, companion.getName(), "Commander Companion (deck invalid for companion)", true); - valid = false; - } - break; - } - } - } return valid; } - - @Override - public int getEdhPowerLevel(Deck deck) { - if (deck == null) { - return 0; - } - - int edhPowerLevel = 0; - int commanderColors = 0; - int numberInfinitePieces = 0; - - for (Card card : deck.getCards()) { - - int thisMaxPower = 0; - - // Examine rules to work out most egregious functions in edh - boolean anyNumberOfTarget = false; - boolean annihilator = false; - boolean buyback = false; - boolean cascade = false; - boolean cantBe = false; - boolean cantUntap = false; - boolean copy = false; - boolean costLessEach = false; - boolean createToken = false; - boolean dredge = false; - boolean exile = false; - boolean exileAll = false; - boolean counter = false; - boolean destroy = false; - boolean destroyAll = false; - boolean each = false; - boolean exalted = false; - boolean doesntUntap = false; - boolean drawCards = false; - boolean evoke = false; - boolean extraTurns = false; - boolean flash = false; - boolean flashback = false; - boolean flicker = false; - boolean gainControl = false; - boolean hexproof = false; - boolean infect = false; - boolean lifeTotalBecomes = false; - boolean mayCastForFree = false; - boolean menace = false; - boolean miracle = false; - boolean overload = false; - boolean persist = false; - boolean preventDamage = false; - boolean proliferate = false; - boolean protection = false; - boolean putUnderYourControl = false; - boolean retrace = false; - boolean returnFromYourGY = false; - boolean sacrifice = false; - boolean shroud = false; - boolean skip = false; - boolean sliver = false; - boolean storm = false; - boolean trample = false; - boolean tutor = false; - boolean tutorBasic = false; - boolean twiceAs = false; - boolean unblockable = false; - boolean undying = false; - boolean untapTarget = false; - boolean wheneverEnters = false; - boolean whenCounterThatSpell = false; - boolean xCost = false; - boolean youControlTarget = false; - boolean yourOpponentsControl = false; - boolean whenYouCast = false; - - for (String str : card.getRules()) { - String s = str.toLowerCase(Locale.ENGLISH); - annihilator |= s.contains("annihilator"); - anyNumberOfTarget |= s.contains("any number"); - buyback |= s.contains("buyback"); - cantUntap |= s.contains("can't untap") || s.contains("don't untap"); - cantBe |= s.contains("can't be"); - cascade |= s.contains("cascade"); - copy |= s.contains("copy"); - costLessEach |= s.contains("cost") || s.contains("less") || s.contains("each"); - counter |= s.contains("counter") && s.contains("target"); - createToken |= s.contains("create") && s.contains("token"); - destroy |= s.contains("destroy"); - destroyAll |= s.contains("destroy all"); - doesntUntap |= s.contains("doesn't untap"); - doesntUntap |= s.contains("don't untap"); - drawCards |= s.contains("draw cards"); - dredge |= s.contains("dredge"); - each |= s.contains("each"); - evoke |= s.contains("evoke"); - exalted |= s.contains("exalted"); - exile |= s.contains("exile"); - exileAll |= s.contains("exile") && s.contains(" all "); - extraTurns |= s.contains("extra turn"); - flicker |= s.contains("exile") && s.contains("return") && s.contains("to the battlefield under"); - flash |= s.contains("flash"); - flashback |= s.contains("flashback"); - gainControl |= s.contains("gain control"); - hexproof |= s.contains("hexproof"); - infect |= s.contains("infect"); - lifeTotalBecomes |= s.contains("life total becomes"); - mayCastForFree |= s.contains("may cast") && s.contains("without paying"); - menace |= s.contains("menace"); - miracle |= s.contains("miracle"); - overload |= s.contains("overload"); - persist |= s.contains("persist"); - preventDamage |= s.contains("prevent") && s.contains("all") && s.contains("damage"); - proliferate |= s.contains("proliferate"); - protection |= s.contains("protection"); - putUnderYourControl |= s.contains("put") && s.contains("under your control"); - retrace |= s.contains("retrace"); - returnFromYourGY |= s.contains("return") && s.contains("from your graveyard"); - sacrifice |= s.contains("sacrifice"); - shroud |= s.contains("shroud"); - skip |= s.contains("skip"); - sliver |= s.contains("sliver"); - storm |= s.contains("storm"); - trample |= s.contains("trample"); - tutor |= s.contains("search your library") && !s.contains("basic land"); - tutorBasic |= s.contains("search your library") && s.contains("basic land"); - twiceAs |= s.contains("twice that many") || s.contains("twice as much"); - unblockable |= s.contains("can't be blocked"); - undying |= s.contains("undying"); - untapTarget |= s.contains("untap target"); - whenCounterThatSpell |= s.contains("when") && s.contains("counter that spell"); - wheneverEnters |= s.contains("when") && s.contains("another") && s.contains("enters"); - youControlTarget |= s.contains("you control target"); - yourOpponentsControl |= s.contains("your opponents control"); - whenYouCast |= s.contains("when you cast") || s.contains("whenever you cast"); - } - - for (String s : card.getManaCostSymbols()) { - if (s.contains("X")) { - xCost = true; - } - } - for (Ability a : card.getAbilities()) { - for (String s : a.getManaCostSymbols()) { - if (s.contains("X")) { - xCost = true; - } - } - } - - if (extraTurns) { - thisMaxPower = Math.max(thisMaxPower, 7); - } - if (buyback) { - thisMaxPower = Math.max(thisMaxPower, 6); - } - if (tutor) { - thisMaxPower = Math.max(thisMaxPower, 6); - } - if (annihilator) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - if (cantUntap) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - if (costLessEach) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - if (infect) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - if (overload) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - if (twiceAs) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - if (cascade) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (doesntUntap) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (each) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (exileAll) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (flash) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (flashback) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (flicker) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (gainControl) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (lifeTotalBecomes) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (mayCastForFree) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (preventDamage) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (proliferate) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (protection) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (putUnderYourControl) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (returnFromYourGY) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (sacrifice) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (skip) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (storm) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (unblockable) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (whenCounterThatSpell) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (wheneverEnters) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (xCost) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (youControlTarget) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (yourOpponentsControl) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (whenYouCast) { - thisMaxPower = Math.max(thisMaxPower, 4); - } - if (anyNumberOfTarget) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (createToken) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (destroyAll) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (dredge) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (hexproof) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (shroud) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (undying) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (persist) { - thisMaxPower = Math.max(thisMaxPower, 3); - } - if (cantBe) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (evoke) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (exile) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (menace) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (miracle) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (sliver) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (untapTarget) { - thisMaxPower = Math.max(thisMaxPower, 2); - } - if (copy) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (counter) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (destroy) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (drawCards) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (exalted) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (retrace) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (trample) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - if (tutorBasic) { - thisMaxPower = Math.max(thisMaxPower, 1); - } - - if (card.isPlaneswalker()) { - thisMaxPower = Math.max(thisMaxPower, 6); - } - - String cn = card.getName().toLowerCase(Locale.ENGLISH); - if (cn.equals("ancient tomb") - || cn.equals("anafenza, the foremost") - || cn.equals("arcum dagsson") - || cn.equals("armageddon") - || cn.equals("aura shards") - || cn.equals("azami, lady of scrolls") - || cn.equals("azusa, lost but seeking") - || cn.equals("back to basics") - || cn.equals("bane of progress") - || cn.equals("basalt monolith") - || cn.equals("blightsteel collossus") - || cn.equals("blood moon") - || cn.equals("braids, cabal minion") - || cn.equals("cabal coffers") - || cn.equals("captain sisay") - || cn.equals("celestial dawn") - || cn.equals("child of alara") - || cn.equals("coalition relic") - || cn.equals("craterhoof behemoth") - || cn.equals("deepglow skate") - || cn.equals("derevi, empyrial tactician") - || cn.equals("dig through time") - || cn.equals("edric, spymaster of trest") - || cn.equals("elesh norn, grand cenobite") - || cn.equals("entomb") - || cn.equals("force of will") - || cn.equals("food chain") - || cn.equals("gaddock teeg") - || cn.equals("gaea's cradle") - || cn.equals("grand arbiter augustin iv") - || cn.equals("grim monolith") - || cn.equals("hermit druid") - || cn.equals("hokori, dust drinker") - || cn.equals("humility") - || cn.equals("imperial seal") - || cn.equals("iona, shield of emeria") - || cn.equals("jin-gitaxias, core augur") - || cn.equals("karador, ghost chieftain") - || cn.equals("karakas") - || cn.equals("kataki, war's wage") - || cn.equals("knowledge pool") - || cn.equals("linvala, keeper of silence") - || cn.equals("living death") - || cn.equals("llawan, cephalid empress") - || cn.equals("loyal retainers") - || cn.equals("maelstrom wanderer") - || cn.equals("malfegor") - || cn.equals("master of cruelties") - || cn.equals("mana crypt") - || cn.equals("mana drain") - || cn.equals("mana vault") - || cn.equals("michiko konda, truth seeker") - || cn.equals("nath of the gilt-leaf") - || cn.equals("natural order") - || cn.equals("necrotic ooze") - || cn.equals("nicol bolas") - || cn.equals("numot, the devastator") - || cn.equals("oath of druids") - || cn.equals("pattern of rebirth") - || cn.equals("protean hulk") - || cn.equals("purphoros, god of the forge") - || cn.equals("ravages of war") - || cn.equals("reclamation sage") - || cn.equals("sen triplets") - || cn.equals("serra's sanctum") - || cn.equals("sheoldred, whispering one") - || cn.equals("sol ring") - || cn.equals("spore frog") - || cn.equals("stasis") - || cn.equals("strip mine") - || cn.equals("the tabernacle at pendrell vale") - || cn.equals("tinker") - || cn.equals("treasure cruise") - || cn.equals("urabrask the hidden") - || cn.equals("vorinclex, voice of hunger") - || cn.equals("winter orb") - || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 12); - } - - // Parts of infinite combos - if (cn.equals("animate artifact") || cn.equals("animar, soul of element") - || cn.equals("archaeomancer") - || cn.equals("ashnod's altar") || cn.equals("azami, lady of scrolls") - || cn.equals("aura flux") - || cn.equals("basalt monolith") || cn.equals("brago, king eternal") - || cn.equals("candelabra of tawnos") || cn.equals("cephalid aristocrat") - || cn.equals("cephalid illusionist") || cn.equals("changeling berserker") - || cn.equals("consecrated sphinx") - || cn.equals("cyclonic rift") - || cn.equals("the chain veil") - || cn.equals("cinderhaze wretch") || cn.equals("cryptic gateway") - || cn.equals("deadeye navigator") || cn.equals("derevi, empyrial tactician") - || cn.equals("doubling season") || cn.equals("dross scorpion") - || cn.equals("earthcraft") || cn.equals("erratic portal") - || cn.equals("enter the infinite") || cn.equals("omniscience") - || cn.equals("exquisite blood") || cn.equals("future sight") - || cn.equals("genesis chamber") - || cn.equals("ghave, guru of spores") - || cn.equals("grave pact") - || cn.equals("grave titan") || cn.equals("great whale") - || cn.equals("grim monolith") || cn.equals("gush") - || cn.equals("hellkite charger") || cn.equals("intruder alarm") - || cn.equals("helm of obedience") - || cn.equals("hermit druid") - || cn.equals("humility") - || cn.equals("iona, shield of emeria") - || cn.equals("karn, silver golem") || cn.equals("kiki-jiki, mirror breaker") - || cn.equals("krark-clan ironworks") || cn.equals("krenko, mob boss") - || cn.equals("krosan restorer") || cn.equals("laboratory maniac") - || cn.equals("leonin relic-warder") || cn.equals("leyline of the void") - || cn.equals("memnarch") - || cn.equals("meren of clan nel toth") || cn.equals("mikaeus, the unhallowed") - || cn.equals("mindcrank") || cn.equals("mindslaver") - || cn.equals("minion reflector") || cn.equals("mycosynth lattice") - || cn.equals("myr turbine") || cn.equals("narset, enlightened master") - || cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary") - || cn.equals("notion thief") - || cn.equals("opalescence") || cn.equals("ornithopter") - || cn.equals("paradox engine") - || cn.equals("purphoros, god of the forge") - || cn.equals("peregrine drake") || cn.equals("palinchron") - || cn.equals("planar portal") || cn.equals("power artifact") - || cn.equals("rings of brighthearth") || cn.equals("rite of replication") - || cn.equals("sanguine bond") || cn.equals("sensei's divining top") - || cn.equals("splinter twin") || cn.equals("stony silence") - || cn.equals("sunder") - || cn.equals("storm cauldron") || cn.equals("teferi's puzzle box") - || cn.equals("tangle wire") - || cn.equals("teferi, mage of zhalfir") - || cn.equals("tezzeret the seeker") || cn.equals("time stretch") - || cn.equals("time warp") || cn.equals("training grounds") - || cn.equals("triskelavus") || cn.equals("triskelion") - || cn.equals("turnabout") || cn.equals("umbral mantle") - || cn.equals("uyo, silent prophet") || cn.equals("voltaic key") - || cn.equals("workhorse") || cn.equals("worldgorger dragon") - || cn.equals("worthy cause") || cn.equals("yawgmoth's will") - || cn.equals("zealous conscripts")) { - thisMaxPower = Math.max(thisMaxPower, 15); - numberInfinitePieces++; - } - - // Saltiest cards (edhrec) - if (cn.equals("acid rain") - || cn.equals("agent of treachery") - || cn.equals("apocalypse") - || cn.equals("armageddon") - || cn.equals("atraxa, praetors' voice") - || cn.equals("aura shards") - || cn.equals("avacyn, angel of hope") - || cn.equals("back to basics") - || cn.equals("bend or break") - || cn.equals("blightsteel colossus") - || cn.equals("blood moon") - || cn.equals("boil") - || cn.equals("boiling seas") - || cn.equals("bribery") - || cn.equals("burning sands") - || cn.equals("card view") - || cn.equals("cataclysm") - || cn.equals("catastrophe") - || cn.equals("chulane, teller of tales") - || cn.equals("confusion in the ranks") - || cn.equals("consecrated sphinx") - || cn.equals("contamination") - || cn.equals("craterhoof behemoth") - || cn.equals("cyclonic rift") - || cn.equals("death cloud") - || cn.equals("decree of annihilation") - || cn.equals("decree of silence") - || cn.equals("demonic consultation") - || cn.equals("derevi, empyrial tactician") - || cn.equals("devastation") - || cn.equals("divine intervention") - || cn.equals("dockside extortionist") - || cn.equals("doomsday") - || cn.equals("doubling season") - || cn.equals("drannith magistrate") - || cn.equals("elesh norn, grand cenobite") - || cn.equals("embargo") - || cn.equals("emrakul, the promised end") - || cn.equals("epicenter") - || cn.equals("expropriate") - || cn.equals("fall of the thran") - || cn.equals("fierce guardianship") - || cn.equals("food chain") - || cn.equals("force of negation") - || cn.equals("force of will") - || cn.equals("gaddock teeg") - || cn.equals("gaea's cradle") - || cn.equals("gilded drake") - || cn.equals("glenn, the voice of calm") - || cn.equals("global ruin") - || cn.equals("golos, tireless pilgrim") - || cn.equals("grand arbiter augustin iv") - || cn.equals("grip of chaos") - || cn.equals("hokori, dust drinker") - || cn.equals("humility") - || cn.equals("impending disaster") - || cn.equals("invoke prejudice") - || cn.equals("iona, shield of emeria") - || cn.equals("jin-gitaxias, core augur") - || cn.equals("jokulhaups") - || cn.equals("keldon firebombers") - || cn.equals("kinnan, bonder prodigy") - || cn.equals("kozilek, butcher of truth") - || cn.equals("land equilibrium") - || cn.equals("linvala, keeper of silence") - || cn.equals("magister sphinx") - || cn.equals("mana breach") - || cn.equals("mana crypt") - || cn.equals("mana drain") - || cn.equals("mana vortex") - || cn.equals("mindslaver") - || cn.equals("narset, enlightened master") - || cn.equals("narset, parter of veils") - || cn.equals("negan, the cold-blooded") - || cn.equals("nether void") - || cn.equals("nexus of fate") - || cn.equals("notion thief") - || cn.equals("obliterate") - || cn.equals("oko, thief of crowns") - || cn.equals("oloro, ageless ascetic") - || cn.equals("omniscience") - || cn.equals("opposition agent") - || cn.equals("oppression") - || cn.equals("overwhelming splendor") - || cn.equals("palinchron") - || cn.equals("paradox engine") - || cn.equals("possessed portal") - || cn.equals("price of glory") - || cn.equals("protean hulk") - || cn.equals("ravages of war") - || cn.equals("rhystic study") - || cn.equals("rick, steadfast leader") - || cn.equals("rising waters") - || cn.equals("ruination") - || cn.equals("scrambleverse") - || cn.equals("seedborn muse") - || cn.equals("sen triplets") - || cn.equals("sire of insanity") - || cn.equals("skithiryx, the blight dragon") - || cn.equals("smokestack") - || cn.equals("smothering tithe") - || cn.equals("sorin markov") - || cn.equals("stasis") - || cn.equals("static orb") - || cn.equals("storage matrix") - || cn.equals("sunder") - || cn.equals("survival of the fittest") - || cn.equals("table view") - || cn.equals("tainted aether") - || cn.equals("tectonic break") - || cn.equals("teferi's protection") - || cn.equals("teferi, master of time") - || cn.equals("teferi, time raveler") - || cn.equals("temporal manipulation") - || cn.equals("tergrid, god of fright") - || cn.equals("text view") - || cn.equals("thassa's oracle") - || cn.equals("the tabernacle at pendrell vale") - || cn.equals("thieves' auction") - || cn.equals("thoughts of ruin") - || cn.equals("thrasios, triton hero") - || cn.equals("time stretch") - || cn.equals("time warp") - || cn.equals("tooth and nail") - || cn.equals("torment of hailfire") - || cn.equals("torpor orb") - || cn.equals("triumph of the hordes") - || cn.equals("ugin, the spirit dragon") - || cn.equals("ulamog, the ceaseless hunger") - || cn.equals("ulamog, the infinite gyre") - || cn.equals("urza, lord high artificer") - || cn.equals("void winnower") - || cn.equals("vorinclex, voice of hunger") - || cn.equals("wake of destruction") - || cn.equals("warp world") - || cn.equals("winter orb") - || cn.equals("xanathar, guild kingpin") - || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 15); - } - edhPowerLevel += thisMaxPower; - } - - ObjectColor color = null; - for (Card commander : deck.getSideboard()) { - int thisMaxPower = 0; - String cn = commander.getName().toLowerCase(Locale.ENGLISH); - if (color == null) { - color = commander.getColor(null); - } else { - color = color.union(commander.getColor(null)); - } - - FilterMana commanderColor = commander.getColorIdentity(); - if (commanderColor.isWhite()) { - color.setWhite(true); - } - if (commanderColor.isBlue()) { - color.setBlue(true); - } - if (commanderColor.isBlack()) { - color.setBlack(true); - } - if (commanderColor.isRed()) { - color.setRed(true); - } - if (commanderColor.isGreen()) { - color.setGreen(true); - } - - // Least fun commanders - if (cn.equals("animar, soul of element") - || cn.equals("anafenza, the foremost") - || cn.equals("arcum dagsson") - || cn.equals("azami, lady of scrolls") - || cn.equals("azusa, lost but seeking") - || cn.equals("brago, king eternal") - || cn.equals("braids, cabal minion") - || cn.equals("captain sisay") - || cn.equals("child of alara") - || cn.equals("derevi, empyrial tactician") - || cn.equals("edric, spymaster of trest") - || cn.equals("elesh norn, grand cenobite") - || cn.equals("gaddock teeg") - || cn.equals("grand arbiter augustin iv") - || cn.equals("hokori, dust drinker") - || cn.equals("iona, shield of emeria") - || cn.equals("jin-gitaxias, core augur") - || cn.equals("kaalia of the vast") - || cn.equals("karador, ghost chieftain") - || cn.equals("leovold, emissary of trest") - || cn.equals("linvala, keeper of silence") - || cn.equals("llawan, cephalid empress") - || cn.equals("maelstrom wanderer") - || cn.equals("malfegor") - || cn.equals("memnarch") - || cn.equals("meren of clan nel toth") - || cn.equals("michiko konda, truth seeker") - || cn.equals("mikaeus the unhallowed") - || cn.equals("narset, enlightened master") - || cn.equals("nath of the gilt-leaf") - || cn.equals("nekusar, the mindrazer") - || cn.equals("norin the wary") - || cn.equals("numot, the devastator") - || cn.equals("prossh, skyraider of kher") - || cn.equals("purphoros, god of the forge") - || cn.equals("sen triplets") - || cn.equals("sheoldred, whispering one") - || cn.equals("teferi, mage of zhalfir") - || cn.equals("urabrask the hidden") - || cn.equals("vorinclex, voice of hunger") - || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 25); - } - - // Saltiest commanders - if (cn.equals("atraxa, praetors' voice") - || cn.equals("avacyn, angel of hope") - || cn.equals("chulane, teller of tales") - || cn.equals("derevi, empyrial tactician") - || cn.equals("elesh norn, grand cenobite") - || cn.equals("emrakul, the promised end") - || cn.equals("gaddock teeg") - || cn.equals("glenn, the voice of calm") - || cn.equals("golos, tireless pilgrim") - || cn.equals("grand arbiter augustin iv") - || cn.equals("hokori, dust drinker") - || cn.equals("iona, shield of emeria") - || cn.equals("jin-gitaxias, core augur") - || cn.equals("kinnan, bonder prodigy") - || cn.equals("kozilek, butcher of truth") - || cn.equals("linvala, keeper of silence") - || cn.equals("narset, enlightened master") - || cn.equals("negan, the cold-blooded") - || cn.equals("oko, thief of crowns") - || cn.equals("oloro, ageless ascetic") - || cn.equals("rick, steadfast leader") - || cn.equals("sen triplets") - || cn.equals("skithiryx, the blight dragon") - || cn.equals("teferi, master of time") - || cn.equals("teferi, time raveler") - || cn.equals("thrasios, triton hero") - || cn.equals("ulamog, the ceaseless hunger") - || cn.equals("ulamog, the infinite gyre") - || cn.equals("urza, lord high artificer") - || cn.equals("vorinclex, voice of hunger") - || cn.equals("xanathar, guild kingpin") - || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 20); - } - edhPowerLevel += thisMaxPower; - } - - edhPowerLevel += numberInfinitePieces * 18; - edhPowerLevel = Math.round(edhPowerLevel / 10); - if (edhPowerLevel >= 100) { - edhPowerLevel = 99; - } - if (color != null) { - edhPowerLevel += (color.isWhite() ? 10000000 : 0); - edhPowerLevel += (color.isBlue() ? 1000000 : 0); - edhPowerLevel += (color.isBlack() ? 100000 : 0); - edhPowerLevel += (color.isRed() ? 10000 : 0); - edhPowerLevel += (color.isGreen() ? 1000 : 0); - } - return edhPowerLevel; - } } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java index 314df58b40..ba17a30494 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java @@ -1,28 +1,17 @@ package mage.deck; -import mage.abilities.Ability; -import mage.abilities.keyword.CompanionAbility; -import mage.abilities.keyword.PartnerAbility; -import mage.abilities.keyword.PartnerWithAbility; import mage.cards.Card; import mage.cards.ExpansionSet; import mage.cards.Sets; -import mage.cards.decks.Constructed; -import mage.cards.decks.Deck; import mage.cards.decks.DeckValidatorErrorType; import mage.constants.CardType; -import mage.filter.FilterMana; -import mage.util.ManaUtil; -import java.util.*; +import java.util.Map; /** * @author spjspj */ -public class FreeformCommander extends Constructed { - - protected List bannedCommander = new ArrayList<>(); - private static final Map pdAllowed = new HashMap<>(); +public class FreeformCommander extends AbstractCommander { public FreeformCommander() { super("Freeform Commander"); @@ -43,136 +32,16 @@ public class FreeformCommander extends Constructed { } @Override - public int getDeckMinSize() { - return 98; + protected boolean checkBanned(Map counts) { + return true; } @Override - public int getSideboardMinSize() { - return 1; - } - - @Override - public boolean validate(Deck deck) { - boolean valid = true; - errorsList.clear(); - FilterMana colorIdentity = new FilterMana(); - Set commanders = new HashSet<>(); - Card companion = null; - - if (deck.getSideboard().size() == 1) { - commanders.add(deck.getSideboard().iterator().next()); - } else if (deck.getSideboard().size() == 2) { - Iterator iter = deck.getSideboard().iterator(); - Card card1 = iter.next(); - Card card2 = iter.next(); - if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card1; - commanders.add(card2); - } else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card2; - commanders.add(card1); - } else { - commanders.add(card1); - commanders.add(card2); - } - } else if (deck.getSideboard().size() == 3) { - Iterator iter = deck.getSideboard().iterator(); - Card card1 = iter.next(); - Card card2 = iter.next(); - Card card3 = iter.next(); - if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card1; - commanders.add(card2); - commanders.add(card3); - } else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card2; - commanders.add(card1); - commanders.add(card3); - } else if (card3.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card3; - commanders.add(card1); - commanders.add(card2); - } else { - addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); - valid = false; - } - } else { - addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); - valid = false; - } - - if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) { - addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); - valid = false; - } else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) { - addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); - valid = false; - } - - Map counts = new HashMap<>(); - countCards(counts, deck.getCards()); - countCards(counts, deck.getSideboard()); - valid = checkCounts(1, counts) && valid; - - Set commanderNames = new HashSet<>(); - for (Card commander : commanders) { - commanderNames.add(commander.getName()); - } - for (Card commander : commanders) { - if (!commander.hasCardTypeForDeckbuilding(CardType.CREATURE) && !commander.isLegendary()) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "For Freeform Commander, the commander must be a creature or be legendary. Yours was: " + commander.getName(), true); - valid = false; - } - if (commanders.size() == 2) { - if (!commander.getAbilities().contains(PartnerAbility.getInstance())) { - boolean partnersWith = commander.getAbilities() - .stream() - .filter(PartnerWithAbility.class::isInstance) - .map(PartnerWithAbility.class::cast) - .map(PartnerWithAbility::getPartnerName) - .anyMatch(commanderNames::contains); - if (!partnersWith) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander without Partner (" + commander.getName() + ')', true); - valid = false; - } - } - } - ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity()); - } - - // no needs in cards check on wrong commanders - if (!valid) { + protected boolean checkCommander(Card commander) { + if (!commander.hasCardTypeForDeckbuilding(CardType.CREATURE) && !commander.isLegendary()) { + addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "For Freeform Commander, the commander must be a creature or be legendary. Yours was: " + commander.getName(), true); return false; } - - for (Card card : deck.getCards()) { - if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { - addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); - valid = false; - } - } - for (Card card : deck.getSideboard()) { - if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { - addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); - valid = false; - } - } - // Check for companion legality - if (companion != null) { - Set cards = new HashSet<>(deck.getCards()); - cards.addAll(commanders); - for (Ability ability : companion.getAbilities()) { - if (ability instanceof CompanionAbility) { - CompanionAbility companionAbility = (CompanionAbility) ability; - if (!companionAbility.isLegal(cards, getDeckMinSize())) { - addError(DeckValidatorErrorType.PRIMARY, companion.getName(), "Commander Companion (deck invalid for companion)", true); - valid = false; - } - break; - } - } - } - return valid; + return true; } } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/PennyDreadfulCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/PennyDreadfulCommander.java index 0e5289d4c7..4e81e03b71 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/PennyDreadfulCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/PennyDreadfulCommander.java @@ -1,29 +1,18 @@ package mage.deck; -import mage.abilities.Ability; -import mage.abilities.common.CanBeYourCommanderAbility; -import mage.abilities.keyword.CompanionAbility; -import mage.abilities.keyword.PartnerAbility; -import mage.abilities.keyword.PartnerWithAbility; -import mage.cards.Card; import mage.cards.ExpansionSet; import mage.cards.Sets; -import mage.cards.decks.Constructed; -import mage.cards.decks.Deck; import mage.cards.decks.DeckValidatorErrorType; import mage.cards.decks.PennyDreadfulLegalityUtil; -import mage.constants.CardType; -import mage.filter.FilterMana; -import mage.util.ManaUtil; -import java.util.*; +import java.util.HashMap; +import java.util.Map; /** * @author spjspj */ -public class PennyDreadfulCommander extends Constructed { +public class PennyDreadfulCommander extends AbstractCommander { - protected List bannedCommander = new ArrayList<>(); private static final Map pdAllowed = new HashMap<>(); public PennyDreadfulCommander() { @@ -36,168 +25,17 @@ public class PennyDreadfulCommander extends Constructed { } @Override - public int getDeckMinSize() { - return 98; - } - - @Override - public int getSideboardMinSize() { - return 1; - } - - @Override - public boolean validate(Deck deck) { - boolean valid = true; - errorsList.clear(); - FilterMana colorIdentity = new FilterMana(); - Set commanders = new HashSet<>(); - Card companion = null; - - if (deck.getSideboard().size() == 1) { - commanders.add(deck.getSideboard().iterator().next()); - } else if (deck.getSideboard().size() == 2) { - Iterator iter = deck.getSideboard().iterator(); - Card card1 = iter.next(); - Card card2 = iter.next(); - if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card1; - commanders.add(card2); - } else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card2; - commanders.add(card1); - } else { - commanders.add(card1); - commanders.add(card2); - } - } else if (deck.getSideboard().size() == 3) { - Iterator iter = deck.getSideboard().iterator(); - Card card1 = iter.next(); - Card card2 = iter.next(); - Card card3 = iter.next(); - if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card1; - commanders.add(card2); - commanders.add(card3); - } else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card2; - commanders.add(card1); - commanders.add(card3); - } else if (card3.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - companion = card3; - commanders.add(card1); - commanders.add(card2); - } else { - addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); - valid = false; - } - } else { - addError(DeckValidatorErrorType.PRIMARY, "Commander", "Sideboard must contain only the commander(s) and up to 1 companion"); - valid = false; - } - - if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) { - addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); - valid = false; - } else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) { - addError(DeckValidatorErrorType.DECK_SIZE, "Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); - valid = false; - } - - Map counts = new HashMap<>(); - countCards(counts, deck.getCards()); - countCards(counts, deck.getSideboard()); - valid = checkCounts(1, counts) && valid; - + protected boolean checkBanned(Map counts) { if (pdAllowed.isEmpty()) { pdAllowed.putAll(PennyDreadfulLegalityUtil.getLegalCardList()); } - + boolean valid = true; for (String wantedCard : counts.keySet()) { if (!(pdAllowed.containsKey(wantedCard))) { addError(DeckValidatorErrorType.BANNED, wantedCard, "Banned", true); valid = false; } } - - Set commanderNames = new HashSet<>(); - for (Card commander : commanders) { - commanderNames.add(commander.getName()); - } - for (Card commander : commanders) { - if (bannedCommander.contains(commander.getName())) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander banned (" + commander.getName() + ')', true); - valid = false; - } - if ((!commander.hasCardTypeForDeckbuilding(CardType.CREATURE) || !commander.isLegendary()) - && !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander invalid (" + commander.getName() + ')', true); - valid = false; - } - if (commanders.size() == 2) { - if (!commander.getAbilities().contains(PartnerAbility.getInstance())) { - boolean partnersWith = commander.getAbilities() - .stream() - .filter(PartnerWithAbility.class::isInstance) - .map(PartnerWithAbility.class::cast) - .map(PartnerWithAbility::getPartnerName) - .anyMatch(commanderNames::contains); - if (!partnersWith) { - addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander without Partner (" + commander.getName() + ')', true); - valid = false; - } - } - } - ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity()); - } - - // no needs in cards check on wrong commanders - if (!valid) { - return false; - } - - for (Card card : deck.getCards()) { - if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { - addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); - valid = false; - } - } - for (Card card : deck.getSideboard()) { - if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { - addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); - valid = false; - } - } - for (Card card : deck.getCards()) { - if (!isSetAllowed(card.getExpansionSetCode())) { - if (!legalSets(card)) { - addError(DeckValidatorErrorType.WRONG_SET, card.getName(), "Not allowed Set: " + card.getExpansionSetCode(), true); - valid = false; - } - } - } - for (Card card : deck.getSideboard()) { - if (!isSetAllowed(card.getExpansionSetCode())) { - if (!legalSets(card)) { - addError(DeckValidatorErrorType.WRONG_SET, card.getName(), "Not allowed Set: " + card.getExpansionSetCode(), true); - valid = false; - } - } - } - // Check for companion legality - if (companion != null) { - Set cards = new HashSet<>(deck.getCards()); - cards.addAll(commanders); - for (Ability ability : companion.getAbilities()) { - if (ability instanceof CompanionAbility) { - CompanionAbility companionAbility = (CompanionAbility) ability; - if (!companionAbility.isLegal(cards, getDeckMinSize())) { - addError(DeckValidatorErrorType.PRIMARY, companion.getName(), "Commander Companion (deck invalid for companion)", true); - valid = false; - } - break; - } - } - } return valid; } } diff --git a/Mage.Sets/src/mage/cards/t/ThePrismaticPiper.java b/Mage.Sets/src/mage/cards/t/ThePrismaticPiper.java new file mode 100644 index 0000000000..6401a2bb24 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThePrismaticPiper.java @@ -0,0 +1,42 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.CommanderChooseColorAbility; +import mage.abilities.keyword.PartnerAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThePrismaticPiper extends CardImpl { + + public ThePrismaticPiper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SHAPESHIFTER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // If The Prismatic Piper is your commander, choose a color before the game begins. The Prismatic Piper is the chosen color. + this.addAbility(new CommanderChooseColorAbility()); + + // Partner + this.addAbility(PartnerAbility.getInstance()); + } + + private ThePrismaticPiper(final ThePrismaticPiper card) { + super(card); + } + + @Override + public ThePrismaticPiper copy() { + return new ThePrismaticPiper(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CommanderLegends.java b/Mage.Sets/src/mage/sets/CommanderLegends.java index 012a370df7..fa2ce832a6 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegends.java +++ b/Mage.Sets/src/mage/sets/CommanderLegends.java @@ -657,6 +657,8 @@ public final class CommanderLegends extends ExpansionSet { cards.add(new SetCardInfo("Tevesh Szat, Doom of Fools", 512, Rarity.MYTHIC, mage.cards.t.TeveshSzatDoomOfFools.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thalisse, Reverent Medium", 291, Rarity.UNCOMMON, mage.cards.t.ThalisseReverentMedium.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thalisse, Reverent Medium", 611, Rarity.UNCOMMON, mage.cards.t.ThalisseReverentMedium.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Prismatic Piper", 1, Rarity.COMMON, mage.cards.t.ThePrismaticPiper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Prismatic Piper", 546, Rarity.COMMON, mage.cards.t.ThePrismaticPiper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thirst for Knowledge", 103, Rarity.UNCOMMON, mage.cards.t.ThirstForKnowledge.class)); cards.add(new SetCardInfo("Thorn of the Black Rose", 154, Rarity.COMMON, mage.cards.t.ThornOfTheBlackRose.class)); cards.add(new SetCardInfo("Thornwood Falls", 498, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper1.dck b/Mage.Tests/piperDecks/ThePrismaticPiper1.dck new file mode 100644 index 0000000000..4e6b8c1bb9 --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper1.dck @@ -0,0 +1,3 @@ +NAME:The Prismatic Piper Test +99 [CMR:506] Island +SB: 1 [CMR:1] The Prismatic Piper diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper2.dck b/Mage.Tests/piperDecks/ThePrismaticPiper2.dck new file mode 100644 index 0000000000..4dfc1188f4 --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper2.dck @@ -0,0 +1,5 @@ +NAME:The Prismatic Piper Test +98 [CMR:506] Island +1 [CMR:508] Mountain +SB: 1 [CMR:1] The Prismatic Piper +SB: 1 [CMR:71] Ghost of Ramirez DePietro diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper3.dck b/Mage.Tests/piperDecks/ThePrismaticPiper3.dck new file mode 100644 index 0000000000..3f468b783e --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper3.dck @@ -0,0 +1,5 @@ +NAME:The Prismatic Piper Test +98 [CMR:506] Island +1 [CMR:508] Mountain +SB: 1 [CMR:1] The Prismatic Piper +SB: 1 [C16:34] Kraum, Ludevic's Opus diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper4.dck b/Mage.Tests/piperDecks/ThePrismaticPiper4.dck new file mode 100644 index 0000000000..bd819cc4b1 --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper4.dck @@ -0,0 +1,4 @@ +NAME:The Prismatic Piper Test +98 [CMR:508] Mountain +1 [CMR:506] Island +SB: 1 [CMR:1] The Prismatic Piper diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper5.dck b/Mage.Tests/piperDecks/ThePrismaticPiper5.dck new file mode 100644 index 0000000000..4732bc05f0 --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper5.dck @@ -0,0 +1,6 @@ +NAME:The Prismatic Piper Test +97 [CMR:508] Mountain +1 [CMR:506] Island +1 [CMR:504] Plains +SB: 1 [CMR:1] The Prismatic Piper +SB: 1 [C16:34] Kraum, Ludevic's Opus diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper6.dck b/Mage.Tests/piperDecks/ThePrismaticPiper6.dck new file mode 100644 index 0000000000..3cbb6f6c77 --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper6.dck @@ -0,0 +1,7 @@ +NAME:The Prismatic Piper Test +96 [CMR:508] Mountain +1 [CMR:506] Island +1 [CMR:504] Plains +1 [CMR:510] Forest +SB: 1 [CMR:1] The Prismatic Piper +SB: 1 [C16:34] Kraum, Ludevic's Opus diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper7.dck b/Mage.Tests/piperDecks/ThePrismaticPiper7.dck new file mode 100644 index 0000000000..931f12eb6b --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper7.dck @@ -0,0 +1,4 @@ +NAME:The Prismatic Piper Test +97 [CMR:508] Mountain +1 [CMR:510] Forest +SB: 2 [CMR:1] The Prismatic Piper diff --git a/Mage.Tests/piperDecks/ThePrismaticPiper8.dck b/Mage.Tests/piperDecks/ThePrismaticPiper8.dck new file mode 100644 index 0000000000..e17dcfac38 --- /dev/null +++ b/Mage.Tests/piperDecks/ThePrismaticPiper8.dck @@ -0,0 +1,7 @@ +NAME:The Prismatic Piper Test +97 [CMR:508] Mountain +1 [CMR:506] Island +1 [CMR:510] Forest +SB: 1 [CMR:1] The Prismatic Piper +SB: 1 [C16:34] Kraum, Ludevic's Opus +SB: 1 [IKO:225] Keruga, the Macrosage diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperBaseTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperBaseTest.java new file mode 100644 index 0000000000..fb1293f918 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperBaseTest.java @@ -0,0 +1,16 @@ +package org.mage.test.commander.piper; + +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +/** + * @author TheElk801 + */ +public abstract class ThePrismaticPiperBaseTest extends CardTestCommanderDuelBase { + + protected static final String piper = "The Prismatic Piper"; + + protected ThePrismaticPiperBaseTest(int number) { + super(); + this.deckNameA = "piperDecks/ThePrismaticPiper" + number + ".dck"; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest1.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest1.java new file mode 100644 index 0000000000..a8b4a8e8e6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest1.java @@ -0,0 +1,33 @@ +package org.mage.test.commander.piper; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest1 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 99 Island + // SB: 1 The Prismatic Piper + + public ThePrismaticPiperTest1() { + super(1); + } + + @Test + public void testColor() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piper); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertColor(playerA, piper, "U", true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest2.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest2.java new file mode 100644 index 0000000000..cdf672f916 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest2.java @@ -0,0 +1,53 @@ +package org.mage.test.commander.piper; + +import mage.abilities.mana.ManaOptions; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.utils.ManaOptionsTestUtils; + +import static org.mage.test.utils.ManaOptionsTestUtils.assertDuplicatedManaOptions; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest2 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 98 Island + // 1 Mountain + // SB: 1 The Prismatic Piper + // SB: 1 Ghost of Ramirez DePietro + + public ThePrismaticPiperTest2() { + super(2); + } + + @Test + public void testColor() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piper); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertColor(playerA, piper, "R", true); + } + + @Test + public void testManaOptions() { + addCard(Zone.BATTLEFIELD, playerA, "Command Tower"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); + ManaOptionsTestUtils.assertManaOptions("{U}", manaOptions); + ManaOptionsTestUtils.assertManaOptions("{R}", manaOptions); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest3.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest3.java new file mode 100644 index 0000000000..57e79abc71 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest3.java @@ -0,0 +1,36 @@ +package org.mage.test.commander.piper; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest3 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 98 Island + // 1 Mountain + // SB: 1 The Prismatic Piper + // SB: 1 Kraum, Ludevic's Opus + + public ThePrismaticPiperTest3() { + super(3); + } + + @Test + public void testColor() { + setChoice(playerA, "White"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piper); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertColor(playerA, piper, "W", true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest4.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest4.java new file mode 100644 index 0000000000..a8860f596f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest4.java @@ -0,0 +1,25 @@ +package org.mage.test.commander.piper; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest4 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 98 Mountain + // 1 Island + // SB: 1 The Prismatic Piper + + public ThePrismaticPiperTest4() { + super(4); + } + + @Test(expected = UnsupportedOperationException.class) + public void testColor() { + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest5.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest5.java new file mode 100644 index 0000000000..2a8c9172a0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest5.java @@ -0,0 +1,36 @@ +package org.mage.test.commander.piper; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest5 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 97 Mountain + // 1 Island + // 1 Plains + // SB: 1 The Prismatic Piper + // SB: 1 Kraum, Ludevic's Opus + + public ThePrismaticPiperTest5() { + super(5); + } + + @Test + public void testColor() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piper); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertColor(playerA, piper, "W", true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest6.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest6.java new file mode 100644 index 0000000000..cde2e730e8 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest6.java @@ -0,0 +1,26 @@ +package org.mage.test.commander.piper; + +import org.junit.Test; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest6 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 96 Mountain + // 1 Island + // 1 Plains + // 1 Forest + // SB: 1 The Prismatic Piper + // SB: 1 Kraum, Ludevic's Opus + + public ThePrismaticPiperTest6() { + super(6); + } + + @Test(expected = UnsupportedOperationException.class) + public void testColor() { + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest7.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest7.java new file mode 100644 index 0000000000..f845744853 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest7.java @@ -0,0 +1,62 @@ +package org.mage.test.commander.piper; + +import mage.ObjectColor; +import mage.abilities.mana.ManaOptions; +import mage.cards.Card; +import mage.constants.CommanderCardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.utils.ManaOptionsTestUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.mage.test.utils.ManaOptionsTestUtils.assertDuplicatedManaOptions; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest7 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 97 Mountain + // 1 Forest + // SB: 2 The Prismatic Piper + + public ThePrismaticPiperTest7() { + super(7); + } + + @Test + public void testColor() { + execute(); + + List commanders = new ArrayList<>(currentGame.getCommanderCardsFromCommandZone(playerA, CommanderCardType.ANY)); + Card piper1; + Card piper2; + if (commanders.get(0).getColor(currentGame).isRed()) { + piper1 = commanders.get(0); + piper2 = commanders.get(1); + } else { + piper1 = commanders.get(1); + piper2 = commanders.get(0); + } + Assert.assertEquals("One Piper must be red", piper1.getColor(currentGame), ObjectColor.RED); + Assert.assertEquals("One Piper must be green", piper2.getColor(currentGame), ObjectColor.GREEN); + } + + @Test + public void testManaOptions() { + addCard(Zone.BATTLEFIELD, playerA, "Command Tower"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); + ManaOptionsTestUtils.assertManaOptions("{R}", manaOptions); + ManaOptionsTestUtils.assertManaOptions("{G}", manaOptions); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest8.java b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest8.java new file mode 100644 index 0000000000..5b788024ac --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/piper/ThePrismaticPiperTest8.java @@ -0,0 +1,60 @@ +package org.mage.test.commander.piper; + +import mage.abilities.mana.ManaOptions; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.utils.ManaOptionsTestUtils; + +import static org.mage.test.utils.ManaOptionsTestUtils.assertDuplicatedManaOptions; + +/** + * @author TheElk801 + */ +public class ThePrismaticPiperTest8 extends ThePrismaticPiperBaseTest { + + // Decklist: + // 97 Mountain + // 1 Island + // 1 Forest + // SB: 1 The Prismatic Piper + // SB: 1 Kraum, Ludevic's Opus + // SB: 1 Keruga, the Macrosage + + public ThePrismaticPiperTest8() { + super(8); + } + + @Test + public void testColor() { + setChoice(playerA, true); // Companion + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piper); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Companion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertColor(playerA, piper, "G", true); + assertHandCount(playerA, "Keruga, the Macrosage", 1); + } + + @Test + public void testManaOptions() { + setChoice(playerA, true); // Companion + addCard(Zone.BATTLEFIELD, playerA, "Command Tower"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + Assert.assertEquals("mana variations don't fit", 3, manaOptions.size()); + ManaOptionsTestUtils.assertManaOptions("{U}", manaOptions); + ManaOptionsTestUtils.assertManaOptions("{R}", manaOptions); + ManaOptionsTestUtils.assertManaOptions("{G}", manaOptions); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java new file mode 100644 index 0000000000..6301b9eefc --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java @@ -0,0 +1,74 @@ +package org.mage.test.serverside.deck; + +import mage.deck.Commander; +import org.junit.Test; +import org.mage.test.serverside.base.MageTestBase; + +/** + * @author TheElk801 + */ +public class CommanderDeckValidationTest extends MageTestBase { + + private static final String piper = "The Prismatic Piper"; + + @Test + public void testGristCommander() { + // Grist, the Hunger Tide can be your commander as its first ability applies during deck construction. + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Forest", 49); + deckTester.addMaindeck("Swamp", 50); + + deckTester.addSideboard("Grist, the Hunger Tide", 1); + + deckTester.validate("Grist should be legal as a commander"); + } + + @Test + public void testPrismaticPiperOneCopy() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Forest", 99); + + deckTester.addSideboard(piper, 1); + + deckTester.validate(); + } + + @Test + public void testPrismaticPiper2() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Forest", 49); + deckTester.addMaindeck("Island", 49); + + deckTester.addSideboard(piper, 1); + deckTester.addSideboard("Anara, Wolvid Familiar", 1); + + deckTester.validate(); + } + + @Test + public void testPrismaticPiper3() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Forest", 48); + deckTester.addMaindeck("Island", 48); + deckTester.addMaindeck("Mountain", 2); + + deckTester.addSideboard(piper, 1); + deckTester.addSideboard("Thrasios, Triton Hero", 1); + + deckTester.validate(); + } + + @Test(expected = AssertionError.class) + public void testPrismaticPiper4() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Forest", 48); + deckTester.addMaindeck("Island", 48); + deckTester.addMaindeck("Mountain", 1); + deckTester.addMaindeck("Plains", 1); + + deckTester.addSideboard(piper, 1); + deckTester.addSideboard("Thrasios, Triton Hero", 1); + + deckTester.validate(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java similarity index 98% rename from Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionTest.java rename to Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java index 44378bc50f..aa398d88b1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java @@ -7,7 +7,7 @@ import org.mage.test.serverside.base.MageTestBase; /** * @author TheElk801 */ -public class CompanionTest extends MageTestBase { +public class CompanionDeckValidationTest extends MageTestBase { @Test public void testGyrudaTrue() { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java index c7aea177cd..b461b6f9c1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java @@ -68,18 +68,6 @@ public class DeckValidatorTest extends MageTestBase { deckTester.validate("only 4 of a card are allowed", false); } - @Test - public void testGristCommander() { - // Grist, the Hunger Tide can be your commander as its first ability applies during deck construction. - DeckTester deckTester = new DeckTester(new Commander()); - deckTester.addMaindeck("Forest", 49); - deckTester.addMaindeck("Swamp", 50); - - deckTester.addSideboard("Grist, the Hunger Tide", 1); - - deckTester.validate("Grist should be legal as a commander"); - } - private void assertCounterspellValid(ArrayList deckList) { final boolean needValid = true; // card valid after Modern Horizons 2 boolean valid = testDeckValid(new Modern(), deckList); diff --git a/Mage/src/main/java/mage/abilities/common/CommanderChooseColorAbility.java b/Mage/src/main/java/mage/abilities/common/CommanderChooseColorAbility.java new file mode 100644 index 0000000000..1063dfece4 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/CommanderChooseColorAbility.java @@ -0,0 +1,33 @@ +package mage.abilities.common; + +import mage.abilities.StaticAbility; +import mage.cards.Card; +import mage.constants.Zone; + +/** + * @author TheElk801 + */ +public class CommanderChooseColorAbility extends StaticAbility { + + public CommanderChooseColorAbility() { + super(Zone.ALL, null); + } + + private CommanderChooseColorAbility(final CommanderChooseColorAbility ability) { + super(ability); + } + + @Override + public CommanderChooseColorAbility copy() { + return new CommanderChooseColorAbility(this); + } + + @Override + public String getRule() { + return "If {this} is your commander, choose a color before the game begins. {this} is the chosen color."; + } + + public static boolean checkCard(Card card) { + return card.getAbilities().stream().anyMatch(CommanderChooseColorAbility.class::isInstance); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java b/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java index ca3a36fe85..735824caeb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CompanionAbility.java @@ -41,8 +41,12 @@ public class CompanionAbility extends SpecialAction { return "Companion — " + companionCondition.getRule(); } - public boolean isLegal(Set cards, int startingHandSize) { + final public boolean isLegal(Set cards, int startingHandSize) { return companionCondition.isLegal(cards, startingHandSize); } + + final public String getLegalRule() { + return companionCondition.getRule(); + } } diff --git a/Mage/src/main/java/mage/cards/decks/Constructed.java b/Mage/src/main/java/mage/cards/decks/Constructed.java index 27390a1dda..cb12cfc797 100644 --- a/Mage/src/main/java/mage/cards/decks/Constructed.java +++ b/Mage/src/main/java/mage/cards/decks/Constructed.java @@ -14,7 +14,7 @@ import java.util.Map.Entry; */ public class Constructed extends DeckValidator { - private static final Logger logger = Logger.getLogger(DeckValidator.class); + private static final Logger logger = Logger.getLogger(Constructed.class); private static final List anyNumberCardsAllowed = new ArrayList<>(Arrays.asList( "Relentless Rats", "Shadowborn Apostle", "Rat Colony", diff --git a/Mage/src/main/java/mage/filter/FilterMana.java b/Mage/src/main/java/mage/filter/FilterMana.java index f714321c31..a8584e7fcd 100644 --- a/Mage/src/main/java/mage/filter/FilterMana.java +++ b/Mage/src/main/java/mage/filter/FilterMana.java @@ -1,6 +1,10 @@ package mage.filter; +import mage.ObjectColor; + import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * @author nantuko @@ -108,6 +112,62 @@ public class FilterMana implements Serializable { return colorCount; } + public void addAll(FilterMana filterMana) { + if (filterMana.white) { + this.white = true; + } + if (filterMana.blue) { + this.blue = true; + } + if (filterMana.black) { + this.black = true; + } + if (filterMana.red) { + this.red = true; + } + if (filterMana.green) { + this.green = true; + } + } + + public void removeAll(FilterMana filterMana) { + if (filterMana.white) { + this.white = false; + } + if (filterMana.blue) { + this.blue = false; + } + if (filterMana.black) { + this.black = false; + } + if (filterMana.red) { + this.red = false; + } + if (filterMana.green) { + this.green = false; + } + } + + public List getColors() { + List colors = new ArrayList<>(); + if (this.white) { + colors.add(ObjectColor.WHITE); + } + if (this.blue) { + colors.add(ObjectColor.BLUE); + } + if (this.black) { + colors.add(ObjectColor.BLACK); + } + if (this.red) { + colors.add(ObjectColor.RED); + } + if (this.green) { + colors.add(ObjectColor.GREEN); + } + return colors; + } + public FilterMana copy() { return new FilterMana(this); } diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index 4457b13972..a17bc98e9f 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -1,21 +1,25 @@ package mage.game; +import mage.ObjectColor; import mage.abilities.Ability; +import mage.abilities.common.CommanderChooseColorAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.continuous.CommanderReplacementEffect; import mage.abilities.effects.common.cost.CommanderCostModification; import mage.abilities.keyword.CompanionAbility; import mage.cards.Card; +import mage.choices.ChoiceColor; import mage.constants.*; +import mage.filter.FilterMana; import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; import mage.watchers.common.CommanderInfoWatcher; import mage.watchers.common.CommanderPlaysCountWatcher; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import java.util.stream.Stream; public abstract class GameCommanderImpl extends GameImpl { @@ -40,6 +44,49 @@ public abstract class GameCommanderImpl extends GameImpl { this.checkCommanderDamage = game.checkCommanderDamage; } + private void handlePipers(Player player, Set commanders) { + int piperCount = commanders + .stream() + .filter(CommanderChooseColorAbility::checkCard) + .mapToInt(x -> 1) + .sum(); + if (piperCount < 1) { + return; + } + FilterMana leftoverColors = new FilterMana(); + Stream.concat( + player.getLibrary().getCards(this).stream(), + player.getSideboard().getCards(this).stream() + ).map(Card::getColorIdentity).forEach(leftoverColors::addAll); + FilterMana nonPiperIdentity = new FilterMana(); + commanders + .stream() + .filter(card -> !CommanderChooseColorAbility.checkCard(card)) + .map(Card::getColorIdentity) + .forEach(nonPiperIdentity::addAll); + leftoverColors.removeAll(nonPiperIdentity); + if (piperCount < leftoverColors.getColorCount()) { + throw new UnsupportedOperationException("This deck should not be legal, something went wrong"); + } + Iterator iterator = leftoverColors.getColors().listIterator(); + for (Card commander : commanders) { + if (!CommanderChooseColorAbility.checkCard(commander)) { + continue; + } + ObjectColor color; + if (!iterator.hasNext()) { + ChoiceColor choiceColor = new ChoiceColor( + true, "Choose a color for " + commander.getName() + ); + player.choose(Outcome.Neutral, choiceColor, this); + color = choiceColor.getColor(); + } else { + color = iterator.next(); + } + commander.getColor().addColor(color); + } + } + @Override protected void init(UUID choosingPlayerId) { // Karn Liberated calls it to restart game, all data and commanders must be re-initialized @@ -50,25 +97,31 @@ public abstract class GameCommanderImpl extends GameImpl { // move commanders to command zone for (UUID playerId : state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); - if (player != null) { - // add new commanders - for (UUID cardId : player.getSideboard()) { - Card card = this.getCard(cardId); - if (card != null) { - // Check for companions. If it is the only card in the sideboard, it is the commander, not a companion. - if (player.getSideboard().size() > 1 && card.getAbilities(this).stream().anyMatch(ability -> ability instanceof CompanionAbility)) { - continue; - } - addCommander(card, player); - } + if (player == null) { + continue; + } + // add new commanders + Set commanders = new HashSet<>(); + for (UUID cardId : player.getSideboard()) { + Card card = this.getCard(cardId); + if (card == null) { + continue; } + // Check for companions. If it is the only card in the sideboard, it is the commander, not a companion. + if (player.getSideboard().size() > 1 && card.getAbilities(this).stream().anyMatch(CompanionAbility.class::isInstance)) { + continue; + } + commanders.add(card); + addCommander(card, player); + } - // init commanders - for (UUID commanderId : this.getCommandersIds(player, CommanderCardType.ANY, false)) { - Card commander = this.getCard(commanderId); - if (commander != null) { - initCommander(commander, player); - } + handlePipers(player, commanders); + + // init commanders + for (UUID commanderId : this.getCommandersIds(player, CommanderCardType.ANY, false)) { + Card commander = this.getCard(commanderId); + if (commander != null) { + initCommander(commander, player); } } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 1a1c4c0d75..0108e2ecee 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1129,12 +1129,13 @@ public abstract class GameImpl implements Game { Map playerCompanionMap = new HashMap<>(); for (Player player : state.getPlayers().values()) { // Make a list of legal companions present in the sideboard + Set cards = new HashSet<>(player.getLibrary().getCards(this)); Set potentialCompanions = new HashSet<>(); for (Card card : player.getSideboard().getUniqueCards(this)) { for (Ability ability : card.getAbilities(this)) { if (ability instanceof CompanionAbility) { CompanionAbility companionAbility = (CompanionAbility) ability; - if (companionAbility.isLegal(new HashSet<>(player.getLibrary().getCards(this)), startingHandSize)) { + if (companionAbility.isLegal(cards, startingHandSize)) { potentialCompanions.add(card); break; }