From 4777466b503b5d1eec389bdf80bc855ebd8e3191 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Tue, 17 May 2022 20:05:18 -0400 Subject: [PATCH] [CLB] Implemented Faceless One --- .../src/mage/deck/AbstractCommander.java | 99 +++++++++++-------- .../src/mage/deck/FreeformCommander.java | 3 +- Mage.Sets/src/mage/cards/f/FacelessOne.java | 42 ++++++++ .../CommanderLegendsBattleForBaldursGate.java | 1 + .../common/ChooseABackgroundAbility.java | 33 +++++++ .../src/main/java/mage/constants/SubType.java | 1 + Utils/mtg-cards-data.txt | 1 + 7 files changed, 136 insertions(+), 44 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/f/FacelessOne.java create mode 100644 Mage/src/main/java/mage/abilities/common/ChooseABackgroundAbility.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 index fce6e12402..b8a3090f6f 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java @@ -1,9 +1,9 @@ package mage.deck; -import mage.MageObject; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.CanBeYourCommanderAbility; +import mage.abilities.common.ChooseABackgroundAbility; import mage.abilities.common.CommanderChooseColorAbility; import mage.abilities.keyword.CompanionAbility; import mage.abilities.keyword.FriendsForeverAbility; @@ -14,6 +14,7 @@ import mage.cards.decks.Constructed; import mage.cards.decks.Deck; import mage.cards.decks.DeckValidatorErrorType; import mage.constants.CardType; +import mage.constants.SubType; import mage.filter.FilterMana; import mage.util.CardUtil; import mage.util.ManaUtil; @@ -46,13 +47,52 @@ public abstract class AbstractCommander extends Constructed { 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); + protected boolean checkCommander(Card commander, Set commanders) { + return commander.hasCardTypeForDeckbuilding(CardType.CREATURE) && commander.isLegendary() + || commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()) + || commander.hasSubTypeForDeckbuilding(SubType.BACKGROUND) && commanders.size() == 2; + } + + protected boolean checkPartners(Set commanders) { + switch (commanders.size()) { + case 1: + return true; + case 2: + break; + default: + return false; + } + Iterator iter = commanders.iterator(); + Card commander1 = iter.next(); + Card commander2 = iter.next(); + if (commander1.getAbilities().containsClass(PartnerAbility.class) + && commander2.getAbilities().containsClass(PartnerAbility.class) + || commander1.getAbilities().containsClass(FriendsForeverAbility.class) + && commander2.getAbilities().containsClass(FriendsForeverAbility.class)) { + return true; + } + if (commander1.getAbilities().containsClass(PartnerWithAbility.class) + && commander2.getAbilities().containsClass(PartnerWithAbility.class)) { + String name1 = CardUtil.castStream(commander2.getAbilities().stream(), PartnerWithAbility.class).map(PartnerWithAbility::getPartnerName).findFirst().orElse(null); + String name2 = CardUtil.castStream(commander1.getAbilities().stream(), PartnerWithAbility.class).map(PartnerWithAbility::getPartnerName).findFirst().orElse(null); + if (commander1.getName().equals(name1) && commander2.getName().equals(name2)) { + return true; + } + addError(DeckValidatorErrorType.PRIMARY, commander1.getName(), "Commander with invalid Partner (" + commander1.getName() + ')', true); + addError(DeckValidatorErrorType.PRIMARY, commander2.getName(), "Commander with invalid Partner (" + commander2.getName() + ')', true); return false; } - return true; + if (commander1.getAbilities().containsClass(ChooseABackgroundAbility.class) == commander2.hasSubTypeForDeckbuilding(SubType.BACKGROUND) + || commander2.getAbilities().containsClass(ChooseABackgroundAbility.class) == commander1.hasSubTypeForDeckbuilding(SubType.BACKGROUND)) { + return true; + } + if (commander1.hasSubTypeForDeckbuilding(SubType.BACKGROUND)) { + addError(DeckValidatorErrorType.PRIMARY, commander1.getName(), "Background without valid Commander (" + commander1.getName() + ')', true); + } + if (commander2.hasSubTypeForDeckbuilding(SubType.BACKGROUND)) { + addError(DeckValidatorErrorType.PRIMARY, commander1.getName(), "Background without valid Commander (" + commander2.getName() + ')', true); + } + return false; } private boolean checkColorIdentity(Deck deck, FilterMana colorIdentity, Set commanders) { @@ -175,40 +215,15 @@ public abstract class AbstractCommander extends Constructed { countCards(counts, deck.getCards()); countCards(counts, deck.getSideboard()); valid = checkCounts(1, counts) && valid; - valid = checkBanned(counts) && valid; + valid = checkPartners(commanders) && 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())) { + if (!checkCommander(commander, commanders)) { addError(DeckValidatorErrorType.PRIMARY, commander.getName(), "Commander invalid (" + commander.getName() + ')', true); valid = false; } @@ -246,16 +261,14 @@ public abstract class AbstractCommander extends Constructed { 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; - } + CompanionAbility companionAbility = CardUtil.castStream( + companion.getAbilities().stream(), + CompanionAbility.class + ).findFirst().orElse(null); + if (companionAbility == null || !companionAbility.isLegal(cards, getDeckMinSize())) { + addError(DeckValidatorErrorType.PRIMARY, companion.getName(), + String.format("Commander companion illegal: %s", companionAbility.getLegalRule()), true); + valid = false; } } return valid; 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 dbaba363a5..dda0b0d72c 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 @@ -7,6 +7,7 @@ import mage.cards.decks.DeckValidatorErrorType; import mage.constants.CardType; import java.util.Map; +import java.util.Set; /** * @author spjspj @@ -33,7 +34,7 @@ public class FreeformCommander extends AbstractCommander { } @Override - protected boolean checkCommander(Card commander) { + protected boolean checkCommander(Card commander, Set 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); return false; diff --git a/Mage.Sets/src/mage/cards/f/FacelessOne.java b/Mage.Sets/src/mage/cards/f/FacelessOne.java new file mode 100644 index 0000000000..9030c0d3df --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FacelessOne.java @@ -0,0 +1,42 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.ChooseABackgroundAbility; +import mage.abilities.common.CommanderChooseColorAbility; +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 FacelessOne extends CardImpl { + + public FacelessOne(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{5}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.BACKGROUND); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // If Faceless One is your commander, choose a color before the game begins. Faceless One is the chosen color. + this.addAbility(new CommanderChooseColorAbility()); + + // Choose a Background + this.addAbility(ChooseABackgroundAbility.getInstance()); + } + + private FacelessOne(final FacelessOne card) { + super(card); + } + + @Override + public FacelessOne copy() { + return new FacelessOne(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index 4fea850afc..f5249c9210 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -24,6 +24,7 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Ancient Brass Dragon", 111, Rarity.MYTHIC, mage.cards.a.AncientBrassDragon.class)); cards.add(new SetCardInfo("Bountiful Promenade", 348, Rarity.RARE, mage.cards.b.BountifulPromenade.class)); cards.add(new SetCardInfo("Elder Brain", 125, Rarity.RARE, mage.cards.e.ElderBrain.class)); + cards.add(new SetCardInfo("Faceless One", 1, Rarity.COMMON, mage.cards.f.FacelessOne.class)); cards.add(new SetCardInfo("Fireball", 175, Rarity.UNCOMMON, mage.cards.f.Fireball.class)); cards.add(new SetCardInfo("Lightning Bolt", 187, Rarity.COMMON, mage.cards.l.LightningBolt.class)); cards.add(new SetCardInfo("Luxury Suite", 355, Rarity.RARE, mage.cards.l.LuxurySuite.class)); diff --git a/Mage/src/main/java/mage/abilities/common/ChooseABackgroundAbility.java b/Mage/src/main/java/mage/abilities/common/ChooseABackgroundAbility.java new file mode 100644 index 0000000000..bf21922510 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/ChooseABackgroundAbility.java @@ -0,0 +1,33 @@ +package mage.abilities.common; + +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; +import mage.abilities.effects.common.InfoEffect; +import mage.constants.Zone; + +import java.io.ObjectStreamException; + +/** + * @author TheElk801 + */ +public class ChooseABackgroundAbility extends StaticAbility implements MageSingleton { + + private static final ChooseABackgroundAbility instance = new ChooseABackgroundAbility(); + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static ChooseABackgroundAbility getInstance() { + return instance; + } + + private ChooseABackgroundAbility() { + super(Zone.ALL, new InfoEffect("choose a background")); + } + + @Override + public ChooseABackgroundAbility copy() { + return instance; + } +} diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index c7d6996764..2d4b0cc486 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -31,6 +31,7 @@ public enum SubType { TOWER("Tower", SubTypeSet.NonBasicLandType), // 205.3h Enchantments have their own unique set of subtypes; these subtypes are called enchantment types. AURA("Aura", SubTypeSet.EnchantmentType), + BACKGROUND("Background", SubTypeSet.EnchantmentType), CARTOUCHE("Cartouche", SubTypeSet.EnchantmentType), CLASS("Class", SubTypeSet.EnchantmentType), CURSE("Curse", SubTypeSet.EnchantmentType), diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 5bc1f6066c..a9911956fc 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -44183,6 +44183,7 @@ Island|Streets of New Capenna|264|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Streets of New Capenna|266|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Streets of New Capenna|268|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Streets of New Capenna|270|C||Basic Land - Forest|||({T}: Add {G}.)| +Faceless One|Commander Legends: Battle for Baldur's Gate|1|C|{5}|Legendary Enchantment Creature - Background|3|3|If Faceless One is your commander, choose a color before the game begins. Faceless One is the chosen color.$Choose a Background| Ancient Brass Dragon|Commander Legends: Battle for Baldur's Gate|111|M|{5}{B}{B}|Creature - Elder Dragon|7|6|Flying$Whenever Ancient Brass Dragon deals combat damage to a player, roll a d20. When you do, put any number of target creature cards with mana value X or less from graveyards onto the battlefield under your control, where X is the result.| Elder Brain|Commander Legends: Battle for Baldur's Gate|125|R|{5}{B}{B}|Creature - Horror|6|6|Menace$Whenever Elder Brain attacks a player, exile all cards from that player's hand, then they draw that many cards. You may play lands and cast spells from among the exiled cards for as long as they remain exiled. If you cast a spell this way, you may spend mana as though it were mana of any color to cast it.| Fireball|Commander Legends: Battle for Baldur's Gate|175|U|{X}{R}|Sorcery|||This spell costs {1} more to cast for each target beyond the first.$Fireball deals X damage divided evenly, rounded down, among any number of targets.|