fix and rework commander validation (fixes #10345)

This commit is contained in:
theelk801 2023-05-23 19:25:00 -04:00
parent 85aaaec468
commit fa03c6404f
8 changed files with 145 additions and 57 deletions

View file

@ -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<CommanderValidator> validators = Arrays.asList(
PartnerValidator.instance,
FriendsForeverValidator.instance,
PartnerWithValidator.instance,
ChooseABackgroundValidator.instance
);
protected final List<String> bannedCommander = new ArrayList<>();
protected final List<String> bannedPartner = new ArrayList<>();
protected boolean partnerAllowed = true;
@ -50,7 +52,7 @@ public abstract class AbstractCommander extends Constructed {
protected boolean checkCommander(Card commander, Set<Card> 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<Card> commanders) {
@ -58,40 +60,20 @@ public abstract class AbstractCommander extends Constructed {
case 1:
return true;
case 2:
if (partnerAllowed) {
break;
}
default:
return false;
}
Iterator<Card> 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;
}

View file

@ -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());

View file

@ -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) {
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;
}
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);
}
}
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);
}
}
// prevent undo
controller.resetStoredBookmark(game);
return true;
}
return false;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}