From fa03c6404f525924094395d12f0d5df918960a08 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Tue, 23 May 2023 19:25:00 -0400 Subject: [PATCH] fix and rework commander validation (fixes #10345) --- .../src/mage/deck/AbstractCommander.java | 46 ++++++------------- .../deck/CommanderDeckValidationTest.java | 11 +++++ .../abilities/keyword/PartnerWithAbility.java | 45 ++++++++---------- .../ChooseABackgroundValidator.java | 28 +++++++++++ .../util/validation/CommanderValidator.java | 21 +++++++++ .../validation/FriendsForeverValidator.java | 16 +++++++ .../util/validation/PartnerValidator.java | 16 +++++++ .../util/validation/PartnerWithValidator.java | 19 ++++++++ 8 files changed, 145 insertions(+), 57 deletions(-) create mode 100644 Mage/src/main/java/mage/util/validation/ChooseABackgroundValidator.java create mode 100644 Mage/src/main/java/mage/util/validation/CommanderValidator.java create mode 100644 Mage/src/main/java/mage/util/validation/FriendsForeverValidator.java create mode 100644 Mage/src/main/java/mage/util/validation/PartnerValidator.java create mode 100644 Mage/src/main/java/mage/util/validation/PartnerWithValidator.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 4f80709c188..523aac06c74 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 @@ -3,21 +3,17 @@ package mage.deck; 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; -import mage.abilities.keyword.PartnerAbility; -import mage.abilities.keyword.PartnerWithAbility; import mage.cards.Card; 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; +import mage.util.validation.*; import java.util.*; import java.util.stream.Stream; @@ -27,6 +23,12 @@ import java.util.stream.Stream; */ public abstract class AbstractCommander extends Constructed { + private static List validators = Arrays.asList( + PartnerValidator.instance, + FriendsForeverValidator.instance, + PartnerWithValidator.instance, + ChooseABackgroundValidator.instance + ); protected final List bannedCommander = new ArrayList<>(); protected final List bannedPartner = new ArrayList<>(); protected boolean partnerAllowed = true; @@ -50,7 +52,7 @@ public abstract class AbstractCommander extends Constructed { 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; + || (validators.stream().anyMatch(validator -> validator.specialCheck(commander)) && commanders.size() == 2); } protected boolean checkPartners(Set commanders) { @@ -58,40 +60,20 @@ public abstract class AbstractCommander extends Constructed { case 1: return true; case 2: - break; + if (partnerAllowed) { + 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)) { + if (validators.stream().anyMatch(validator -> validator.checkBothPartners(commander1, commander2))) { 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; - } - 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); - } + addError(DeckValidatorErrorType.PRIMARY, commander1.getName(), "Invalid commander pair (" + commander1.getName() + ')', true); + addError(DeckValidatorErrorType.PRIMARY, commander2.getName(), "Invalid commander pair (" + commander2.getName() + ')', true); return false; } 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 index 872c04f7fd2..aad94d5578a 100644 --- 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 @@ -23,6 +23,17 @@ public class CommanderDeckValidationTest extends MageTestBase { deckTester.validate("Grist should be legal as a commander"); } + @Test(expected = AssertionError.class) + public void testTwoInvalidCommanders() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Wastes", 99); + + deckTester.addSideboard("Tiana, Ship's Caretaker", 1); + deckTester.addSideboard("Mazzy, Truesword Paladin", 1); + + deckTester.validate("These commanders don't have partner"); + } + @Test public void testPrismaticPiperOneCopy() { DeckTester deckTester = new DeckTester(new Commander()); diff --git a/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java b/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java index a9f9a82ec01..0b12edfc851 100644 --- a/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java @@ -1,11 +1,10 @@ - package mage.abilities.keyword; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; +import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.Outcome; import mage.constants.Zone; @@ -17,7 +16,6 @@ import mage.target.TargetPlayer; import mage.target.common.TargetCardInLibrary; /** - * * @author TheElk801 */ public class PartnerWithAbility extends EntersBattlefieldTriggeredAbility { @@ -73,6 +71,10 @@ public class PartnerWithAbility extends EntersBattlefieldTriggeredAbility { return partnerName; } + public boolean checkPartner(Card card) { + return partnerName.equals(card.getName()); + } + public static String shortenName(String st) { StringBuilder sb = new StringBuilder(); for (char s : st.toCharArray()) { @@ -108,29 +110,22 @@ class PartnersWithSearchEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Player player = game.getPlayer(source.getFirstTarget()); - if (player != null) { - FilterCard filter = new FilterCard("card named " + partnerName); - filter.add(new NamePredicate(partnerName)); - TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.chooseUse(Outcome.Benefit, "Search your library for a card named " + partnerName + " and put it into your hand?", source, game)) { - player.searchLibrary(target, source, game); - for (UUID cardId : target.getTargets()) { - Card card = player.getLibrary().getCard(cardId, game); - if (card != null) { - player.revealCards(source, new CardsImpl(card), game); - player.moveCards(card, Zone.HAND, source, game); - } - } - player.shuffleLibrary(source, game); - } - } - // prevent undo - controller.resetStoredBookmark(game); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + if (!player.chooseUse(Outcome.Benefit, "Search your library for a card named " + partnerName + " and put it into your hand?", source, game)) { return true; } - return false; + FilterCard filter = new FilterCard("card named " + partnerName); + filter.add(new NamePredicate(partnerName)); + TargetCardInLibrary target = new TargetCardInLibrary(filter); + player.searchLibrary(target, source, game); + Cards cards = new CardsImpl(target.getTargets()); + cards.retainZone(Zone.LIBRARY, game); + player.revealCards(source, cards, game); + player.moveCards(cards, Zone.HAND, source, game); + player.shuffleLibrary(source, game); + return true; } } diff --git a/Mage/src/main/java/mage/util/validation/ChooseABackgroundValidator.java b/Mage/src/main/java/mage/util/validation/ChooseABackgroundValidator.java new file mode 100644 index 00000000000..6350dd7f146 --- /dev/null +++ b/Mage/src/main/java/mage/util/validation/ChooseABackgroundValidator.java @@ -0,0 +1,28 @@ +package mage.util.validation; + +import mage.abilities.common.ChooseABackgroundAbility; +import mage.cards.Card; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public enum ChooseABackgroundValidator implements CommanderValidator { + instance; + + @Override + public boolean checkPartner(Card commander1, Card commander2) { + return commander1.getAbilities().containsClass(ChooseABackgroundAbility.class) + && commander2.hasSubTypeForDeckbuilding(SubType.BACKGROUND); + } + + @Override + public boolean checkBothPartners(Card commander1, Card commander2) { + return checkPartner(commander1, commander2) || checkPartner(commander2, commander1); + } + + @Override + public boolean specialCheck(Card commander) { + return commander.hasSubTypeForDeckbuilding(SubType.BACKGROUND); + } +} diff --git a/Mage/src/main/java/mage/util/validation/CommanderValidator.java b/Mage/src/main/java/mage/util/validation/CommanderValidator.java new file mode 100644 index 00000000000..f5b5e36f768 --- /dev/null +++ b/Mage/src/main/java/mage/util/validation/CommanderValidator.java @@ -0,0 +1,21 @@ +package mage.util.validation; + +import mage.cards.Card; + +/** + * interface for validating two commanders + * + * @author TheElk801 + */ +public interface CommanderValidator { + + boolean checkPartner(Card commander1, Card commander2); + + default boolean checkBothPartners(Card commander1, Card commander2) { + return checkPartner(commander1, commander2) && checkPartner(commander2, commander1); + } + + default boolean specialCheck(Card commander) { + return false; + } +} diff --git a/Mage/src/main/java/mage/util/validation/FriendsForeverValidator.java b/Mage/src/main/java/mage/util/validation/FriendsForeverValidator.java new file mode 100644 index 00000000000..48c8afed8cc --- /dev/null +++ b/Mage/src/main/java/mage/util/validation/FriendsForeverValidator.java @@ -0,0 +1,16 @@ +package mage.util.validation; + +import mage.abilities.keyword.FriendsForeverAbility; +import mage.cards.Card; + +/** + * @author TheElk801 + */ +public enum FriendsForeverValidator implements CommanderValidator { + instance; + + @Override + public boolean checkPartner(Card commander1, Card commander2) { + return commander1.getAbilities().containsClass(FriendsForeverAbility.class); + } +} diff --git a/Mage/src/main/java/mage/util/validation/PartnerValidator.java b/Mage/src/main/java/mage/util/validation/PartnerValidator.java new file mode 100644 index 00000000000..61db178a14a --- /dev/null +++ b/Mage/src/main/java/mage/util/validation/PartnerValidator.java @@ -0,0 +1,16 @@ +package mage.util.validation; + +import mage.abilities.keyword.PartnerAbility; +import mage.cards.Card; + +/** + * @author TheElk801 + */ +public enum PartnerValidator implements CommanderValidator { + instance; + + @Override + public boolean checkPartner(Card commander1, Card commander2) { + return commander1.getAbilities().containsClass(PartnerAbility.class); + } +} diff --git a/Mage/src/main/java/mage/util/validation/PartnerWithValidator.java b/Mage/src/main/java/mage/util/validation/PartnerWithValidator.java new file mode 100644 index 00000000000..0747dae932b --- /dev/null +++ b/Mage/src/main/java/mage/util/validation/PartnerWithValidator.java @@ -0,0 +1,19 @@ +package mage.util.validation; + +import mage.abilities.keyword.PartnerWithAbility; +import mage.cards.Card; +import mage.util.CardUtil; + +/** + * @author TheElk801 + */ +public enum PartnerWithValidator implements CommanderValidator { + instance; + + @Override + public boolean checkPartner(Card commander1, Card commander2) { + return CardUtil + .castStream(commander2.getAbilities().stream(), PartnerWithAbility.class) + .anyMatch(ability -> ability.checkPartner(commander1)); + } +}