diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index 23ffa77fbe5..d44b93c0141 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -20,6 +20,8 @@ package forge.deck; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + import forge.StaticData; import forge.card.CardRules; import forge.card.CardRulesPredicates; @@ -47,7 +49,7 @@ public enum DeckFormat { QuestDeck ( Range.between(40, Integer.MAX_VALUE), Range.between(0, 15), 4), Limited ( Range.between(40, Integer.MAX_VALUE), null, Integer.MAX_VALUE), Commander ( Range.is(99), Range.between(0, 10), 1, new Predicate() { - private final Set bannedCards = new HashSet(Arrays.asList( + private final Set bannedCards = ImmutableSet.of( "Adriana's Valor", "Advantageous Proclamation", "Amulet of Quoz", "Ancestral Recall", "Assemble the Rank and Vile", "Backup Plan", "Balance", "Biorhythm", "Black Lotus", "Brago's Favor", "Braids, Cabal Minion", "Bronze Tablet", "Channel", "Chaos Orb", "Coalition Victory", "Contract from Below", "Darkpact", "Demonic Attorney", "Double Stroke", @@ -59,7 +61,7 @@ public enum DeckFormat { "Rebirth", "Recurring Nightmare", "Rofellos, Llanowar Emissary", "Secret Summoning", "Secrets of Paradise", "Sentinel Dispatch", "Shahrazad", "Sovereign's Realm", "Summoner's Bond", "Sundering Titan", "Sway of the Stars", "Sylvan Primordial", "Tempest Efreet", "Time Vault", "Time Walk", "Timmerian Fiends", "Tinker", "Tolarian Academy", - "Trade Secrets", "Unexpected Potential", "Upheaval", "Weight Advantage", "Worldfire", "Worldknit", "Yawgmoth's Bargain")); + "Trade Secrets", "Unexpected Potential", "Upheaval", "Weight Advantage", "Worldfire", "Worldknit", "Yawgmoth's Bargain"); @Override public boolean apply(CardRules rules) { if (bannedCards.contains(rules.getName())) { @@ -70,8 +72,8 @@ public enum DeckFormat { }), Pauper ( Range.is(60), Range.between(0, 10), 1), Brawl ( Range.is(59), Range.between(0, 15), 1, null, new Predicate() { - private final Set bannedCards = new HashSet(Arrays.asList( - "Baral, Chief of Compliance","Smuggler's Copter","Sorcerous Spyglass")); + private final Set bannedCards = ImmutableSet.of( + "Baral, Chief of Compliance","Smuggler's Copter","Sorcerous Spyglass"); @Override public boolean apply(PaperCard card) { //why do we need to hard code the bannings here - they are defined in the GameFormat predicate used below @@ -81,7 +83,7 @@ public enum DeckFormat { return StaticData.instance() == null ? false : StaticData.instance().getBrawlPredicate().apply(card); } }) { - private final ImmutableSet bannedCommanders = ImmutableSet.of("Baral, Chief of Compliance"); + private final Set bannedCommanders = ImmutableSet.of("Baral, Chief of Compliance"); @Override public boolean isLegalCommander(CardRules rules) { @@ -89,11 +91,11 @@ public enum DeckFormat { } }, TinyLeaders ( Range.is(49), Range.between(0, 10), 1, new Predicate() { - private final Set bannedCards = new HashSet(Arrays.asList( + private final Set bannedCards = ImmutableSet.of( "Ancestral Recall", "Balance", "Black Lotus", "Black Vise", "Channel", "Chaos Orb", "Contract From Below", "Counterbalance", "Darkpact", "Demonic Attorney", "Demonic Tutor", "Earthcraft", "Edric, Spymaster of Trest", "Falling Star", "Fastbond", "Flash", "Goblin Recruiter", "Grindstone", "Hermit Druid", "Imperial Seal", "Jeweled Bird", "Karakas", "Library of Alexandria", "Mana Crypt", "Mana Drain", "Mana Vault", "Metalworker", "Mind Twist", "Mishra's Workshop", "Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister", - "Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will")); + "Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will"); @Override public boolean apply(CardRules rules) { @@ -112,7 +114,7 @@ public enum DeckFormat { return true; } }) { - private final ImmutableSet bannedCommanders = ImmutableSet.of("Derevi, Empyrial Tactician", "Erayo, Soratami Ascendant", "Rofellos, Llanowar Emissary"); + private final Set bannedCommanders = ImmutableSet.of("Derevi, Empyrial Tactician", "Erayo, Soratami Ascendant", "Rofellos, Llanowar Emissary"); @Override public boolean isLegalCommander(CardRules rules) { @@ -141,13 +143,6 @@ public enum DeckFormat { private final static String ADVPROCLAMATION = "Advantageous Proclamation"; private final static String SOVREALM = "Sovereign's Realm"; - private static final List limitExceptions = Arrays.asList( - new String[]{"Relentless Rats", "Shadowborn Apostle", "Rat Colony"}); - - public static List getLimitExceptions(){ - return limitExceptions; - } - private DeckFormat(Range mainRange0, Range sideRange0, int maxCardCopies0, Predicate cardPoolFilter0, Predicate paperCardPoolFilter0) { mainRange = mainRange0; sideRange = sideRange0; @@ -342,7 +337,6 @@ public enum DeckFormat { //basic lands, Shadowborn Apostle, Relentless Rats and Rat Colony final CardPool allCards = deck.getAllCardsInASinglePool(hasCommander()); - final ImmutableSet limitExceptions = ImmutableSet.of("Relentless Rats", "Shadowborn Apostle", "Rat Colony"); // should group all cards by name, so that different editions of same card are really counted as the same card for (final Entry cp : Aggregates.groupSumBy(allCards, PaperCard.FN_GET_NAME)) { @@ -351,8 +345,7 @@ public enum DeckFormat { return TextUtil.concatWithSpace("contains the nonexisting card", cp.getKey()); } - final boolean canHaveMultiple = simpleCard.getRules().getType().isBasicLand() || limitExceptions.contains(cp.getKey()); - if (!canHaveMultiple && cp.getValue() > maxCopies) { + if (!canHaveAnyNumberOf(simpleCard) && cp.getValue() > maxCopies) { return TextUtil.concatWithSpace("must not contain more than", String.valueOf(maxCopies), "copies of the card", cp.getKey()); } } @@ -370,6 +363,12 @@ public enum DeckFormat { return null; } + public static boolean canHaveAnyNumberOf(final IPaperCard icard) { + return icard.getRules().getType().isBasicLand() + || Iterables.contains(icard.getRules().getMainPart().getKeywords(), + "A deck can have any number of cards named CARDNAME."); + } + public static String getPlaneSectionConformanceProblem(final CardPool planes) { //Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton. if (planes == null || planes.countAll() < 10) { diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java index 8c35c67ec10..bce6256d230 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java @@ -201,8 +201,7 @@ public abstract class ACEditorBase { if (deck == null || card == null) { max = Integer.MAX_VALUE; } - else if (limit == CardLimit.None || card.getRules().getType().isBasic() || DeckFormat.getLimitExceptions().contains(card.getName())) { + else if (limit == CardLimit.None || DeckFormat.canHaveAnyNumberOf(card)) { max = Integer.MAX_VALUE; if (parentScreen.isLimitedEditor() && !isAddSource) { //prevent adding more than is in other pool when editing limited decks diff --git a/forge-gui/res/cardsfolder/r/rat_colony.txt b/forge-gui/res/cardsfolder/r/rat_colony.txt index a721c026b14..74348ead913 100644 --- a/forge-gui/res/cardsfolder/r/rat_colony.txt +++ b/forge-gui/res/cardsfolder/r/rat_colony.txt @@ -1,8 +1,8 @@ Name:Rat Colony ManaCost:1 B Types:Creature Rat -Text:A deck can have any number of cards named Rat Colony. PT:2/1 +K:A deck can have any number of cards named CARDNAME. S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | References$ X | Description$ CARDNAME gets +1/+0 for each other Rat you control. SVar:X:Count$Valid Rat.YouCtrl+Other SVar:BuffedBy:Rat diff --git a/forge-gui/res/cardsfolder/r/relentless_rats.txt b/forge-gui/res/cardsfolder/r/relentless_rats.txt index 0ac935d7fb6..282a1c808a2 100644 --- a/forge-gui/res/cardsfolder/r/relentless_rats.txt +++ b/forge-gui/res/cardsfolder/r/relentless_rats.txt @@ -1,8 +1,8 @@ Name:Relentless Rats ManaCost:1 B B Types:Creature Rat -Text:A deck can have any number of cards named Relentless Rats. PT:2/2 +K:A deck can have any number of cards named CARDNAME. S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | References$ X | Description$ CARDNAME gets +1/+1 for each other creature on the battlefield named Relentless Rats. SVar:X:Count$Valid Creature.namedRelentless Rats+Other SVar:BuffedBy:Creature.namedRelentless Rats diff --git a/forge-gui/res/cardsfolder/s/shadowborn_apostle.txt b/forge-gui/res/cardsfolder/s/shadowborn_apostle.txt index 9a3a4a19947..c8598df5774 100644 --- a/forge-gui/res/cardsfolder/s/shadowborn_apostle.txt +++ b/forge-gui/res/cardsfolder/s/shadowborn_apostle.txt @@ -1,11 +1,11 @@ Name:Shadowborn Apostle ManaCost:B Types:Creature Human Cleric -Text:A deck can have any number of cards named Shadowborn Apostle. PT:1/1 +K:A deck can have any number of cards named CARDNAME. A:AB$ ChangeZone | Cost$ B Sac<6/Creature.namedShadowborn Apostle/creatures named Shadowborn Apostle> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Demon | ChangeNum$ 1 | SpellDescription$ Search your library for a Demon creature, put it onto the battlefield, then shuffle your library. DeckNeeds:Name$Shadowborn Apostle DeckNeeds:Type$Demon DeckHints:Name$Shadowborn Demon SVar:Picture:http://www.wizards.com/global/images/magic/general/shadowborn_apostle.jpg -Oracle:A deck can have any number of cards named Shadowborn Apostle.\n{B}, Sacrifice six creatures named Shadowborn Apostle: Search your library for a Demon creature card, put it onto the battlefield, then shuffle your library. \ No newline at end of file +Oracle:A deck can have any number of cards named Shadowborn Apostle.\n{B}, Sacrifice six creatures named Shadowborn Apostle: Search your library for a Demon creature card, put it onto the battlefield, then shuffle your library. diff --git a/forge-gui/res/cardsfolder/upcoming/persistent_petitioners.txt b/forge-gui/res/cardsfolder/upcoming/persistent_petitioners.txt new file mode 100644 index 00000000000..52b58776834 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/persistent_petitioners.txt @@ -0,0 +1,10 @@ +Name:Persistent Petitioners +ManaCost:1 U +Types:Creature Human Advisor +PT:1/3 +K:A deck can have any number of cards named CARDNAME. +A:AB$ Mill | Cost$ 1 T | NumCards$ 1 | ValidTgts$ Player | TgtPrompt$ Choose a player | SpellDescription$ Target player puts the top card of their library into their graveyard. +A:AB$ Mill | Cost$ tapXType<4/Advisor> | NumCards$ 12 | ValidTgts$ Player | TgtPrompt$ Choose a player | SpellDescription$ Target player puts the top twelve cards of their library into their graveyard. +SVar:BuffedBy:Creature.namedPersistent Petitioners +DeckNeeds:Type$Advisor +Oracle:{1}, {T}: Target player puts the top card of their library into their graveyard.\nTap four untapped Advisors you control: Target player puts the top twelve cards of their library into their graveyard.\nA deck can have any number of cards named Persistent Petitioners. diff --git a/forge-gui/src/main/java/forge/quest/QuestSpellShop.java b/forge-gui/src/main/java/forge/quest/QuestSpellShop.java index 96eae768963..5924404f35f 100644 --- a/forge-gui/src/main/java/forge/quest/QuestSpellShop.java +++ b/forge-gui/src/main/java/forge/quest/QuestSpellShop.java @@ -358,7 +358,7 @@ public class QuestSpellShop { } //If this card has an exception to the card limit, e.g.: Relentless Rats, get the quest preference - if (DeckFormat.getLimitExceptions().contains(card.getName())) { + if (DeckFormat.canHaveAnyNumberOf(card)) { numToKeep = FModel.getQuestPreferences().getPrefInt(QPref.PLAYSET_ANY_NUMBER_SIZE); }