From f3ba897536adf4173cde00f50be7e7a88610f797 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 24 May 2025 19:49:21 +0400 Subject: [PATCH] gui, deck: reworked edh power level (close #5361, close #11732, related to #13341): * added power level info in deck validation panel; * added detail calculation info (hint with cards and their power levels); * fixed that deck's edh power level ignore individual card's levels and used only commanders; * removed outdated deck restrictions by commander colors; * now players can really limit allowed decks by edh power level; --- .../EdhPowerLevelLegalityLabel.java | 76 ++++++++++ .../mage/client/components/LegalityLabel.java | 73 ++++------ .../client/deckeditor/DeckEditorPanel.form | 4 +- .../client/deckeditor/DeckEditorPanel.java | 12 +- .../client/deckeditor/DeckLegalityPanel.java | 8 +- .../CardViewEDHPowerLevelComparator.java | 1 + .../src/mage/deck/AbstractCommander.java | 133 +++++++++++++++--- .../java/mage/server/TableController.java | 2 +- .../java/mage/cards/decks/DeckValidator.java | 2 +- 9 files changed, 235 insertions(+), 76 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java diff --git a/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java b/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java new file mode 100644 index 00000000000..1133eaac986 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java @@ -0,0 +1,76 @@ +package mage.client.components; + +import mage.cards.decks.Deck; +import mage.client.util.GUISizeHelper; +import mage.client.util.gui.GuiDisplayUtil; +import mage.deck.Commander; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Inject power level info inside validation panel + * + * @author JayDi85 + */ +public class EdhPowerLevelLegalityLabel extends LegalityLabel { + + private final Commander commanderDeckType = new Commander(); + private final List foundPowerCards = new ArrayList<>(); + + public EdhPowerLevelLegalityLabel() { + super("EDH Power Level: ?", null); + setPreferredSize(DIM_PREFERRED_X3); + } + + @Override + public List selectCards() { + // choose cards with power level + return this.foundPowerCards; + } + + @Override + public void validateDeck(Deck deck) { + // find and save power level and card hints + + List foundInfo = new ArrayList<>(); + int level = this.commanderDeckType.getEdhPowerLevel(deck, foundPowerCards, foundInfo); + this.setText(String.format("EDH Power Level: %d", level)); + + // sort by score "+5 from xxx" + Pattern pattern = Pattern.compile("\\+(\\d+)"); + foundInfo.sort((o1, o2) -> { + Matcher matcher = pattern.matcher(o1); + int score1 = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; + matcher = pattern.matcher(o2); + int score2 = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; + if (score1 != score2) { + return Integer.compare(score2, score1); + } + return o1.compareTo(o2); + }); + + showStateInfo(formatCardsInfoTooltip(level, foundInfo)); + } + + private String formatCardsInfoTooltip(int level, List foundInfo) { + // use 60% font for better and compatible list + int infoFontSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f); + int maxLimit = 25; + String extraInfo = this.foundPowerCards.size() <= maxLimit ? "" : String.format("
  • and %d more cards
  • ", maxLimit - this.foundPowerCards.size()); + return foundInfo.stream() + .limit(maxLimit) + .reduce("" + + "

    EDH Power Level: " + level + "

    " + + "
    " + + "Found " + this.foundPowerCards.size() + " cards with power levels (click to select it)" + + "
    " + + "
      ", + (str, info) -> str + String.format("
    • %s
    • ", info), String::concat) + + extraInfo + + "
    " + + ""; + } +} diff --git a/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java b/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java index aed4e61bf66..95d2a13c7bf 100644 --- a/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java +++ b/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java @@ -3,17 +3,18 @@ package mage.client.components; import mage.cards.decks.Deck; import mage.cards.decks.DeckValidator; import mage.cards.decks.DeckValidatorError; -import mage.cards.decks.importer.DeckImporter; import org.unbescape.html.HtmlEscape; import org.unbescape.html.HtmlEscapeLevel; import org.unbescape.html.HtmlEscapeType; import javax.swing.*; import java.awt.*; -import java.io.File; +import java.util.Collections; +import java.util.Objects; +import java.util.stream.Collectors; /** - * @author Elandril + * @author Elandril, JayDi85 */ public class LegalityLabel extends JLabel { @@ -25,8 +26,10 @@ public class LegalityLabel extends JLabel { protected static final Dimension DIM_MINIMUM = new Dimension(75, 25); protected static final Dimension DIM_MAXIMUM = new Dimension(150, 75); protected static final Dimension DIM_PREFERRED = new Dimension(75, 25); + protected static final Dimension DIM_PREFERRED_X2 = new Dimension(DIM_PREFERRED.width * 2 + 5, 25); + protected static final Dimension DIM_PREFERRED_X3 = new Dimension(DIM_PREFERRED.width * 3 + 5 + 5, 25); - protected static final int TOOLTIP_TABLE_WIDTH = 300; // size of the label's tooltip + protected static final int TOOLTIP_TABLE_WIDTH = 400; // size of the label's tooltip protected static final int TOOLTIP_MAX_ERRORS = 20; // max errors to show in tooltip protected Deck currentDeck; @@ -92,19 +95,6 @@ public class LegalityLabel extends JLabel { return button; } - public String getErrorMessage() { - return errorMessage; - } - - public DeckValidator getValidator() { - return validator; - } - - public void setValidator(DeckValidator validator) { - this.validator = validator; - revalidateDeck(); - } - protected String escapeHtml(String string) { return HtmlEscape.escapeHtml(string, HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, HtmlEscapeLevel.LEVEL_0_ONLY_MARKUP_SIGNIFICANT_EXCEPT_APOS); } @@ -146,25 +136,33 @@ public class LegalityLabel extends JLabel { setBackground(color); } - public void showState(Color color, String tooltip) { + public void showState(Color color, String tooltip, boolean useErrors) { setBackground(color); - setToolTipText(appendErrorMessage(tooltip)); + if (useErrors) { + setToolTipText(appendErrorMessage(tooltip)); + } else { + setToolTipText(tooltip); + } + } + + public void showStateInfo(String tooltip) { + showState(COLOR_LEGAL, tooltip, false); } public void showStateUnknown(String tooltip) { - showState(COLOR_UNKNOWN, tooltip); + showState(COLOR_UNKNOWN, tooltip, true); } public void showStateLegal(String tooltip) { - showState(COLOR_LEGAL, tooltip); + showState(COLOR_LEGAL, tooltip, true); } public void showStatePartlyLegal(String tooltip) { - showState(COLOR_PARTLY_LEGAL, tooltip); + showState(COLOR_PARTLY_LEGAL, tooltip, true); } public void showStateNotLegal(String tooltip) { - showState(COLOR_NOT_LEGAL, tooltip); + showState(COLOR_NOT_LEGAL, tooltip, true); } public void validateDeck(Deck deck) { @@ -191,28 +189,13 @@ public class LegalityLabel extends JLabel { } } - public void validateDeck(File deckFile) { - deckFile = deckFile.getAbsoluteFile(); - if (!deckFile.exists()) { - errorMessage = String.format("Deck file '%s' does not exist.", deckFile.getAbsolutePath()); - showStateUnknown("No Deck loaded!"); - return; + public java.util.List selectCards() { + if (this.validator == null) { + return Collections.emptyList(); } - try { - StringBuilder errorMessages = new StringBuilder(); - Deck deck = Deck.load(DeckImporter.importDeckFromFile(deckFile.getAbsolutePath(), errorMessages, false), true, true); - errorMessage = errorMessages.toString(); - validateDeck(deck); - } catch (Exception ex) { - errorMessage = String.format("Error importing deck from file '%s'!", deckFile.getAbsolutePath()); - } - } - - public void revalidateDeck() { - validateDeck(currentDeck); - } - - public void validateDeck(String deckFile) { - validateDeck(new File(deckFile)); + return this.validator.getErrorsList().stream() + .map(DeckValidatorError::getCardName) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form index df74cb92578..0df29a4f479 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form @@ -533,10 +533,10 @@ - + - + diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index 7671604596a..326b38bbea9 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -121,12 +121,8 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (!SwingUtilities.isLeftMouseButton(e)) { return; } - List cardNames = new ArrayList<>(); LegalityLabel label = (LegalityLabel) e.getComponent(); - label.getValidator().getErrorsList().stream() - .map(DeckValidatorError::getCardName) - .filter(Objects::nonNull) - .forEach(cardNames::add); + List cardNames = new ArrayList<>(label.selectCards()); deckArea.getDeckList().deselectAll(); deckArea.getDeckList().selectByName(cardNames); deckArea.getSideboardList().deselectAll(); @@ -1290,8 +1286,8 @@ public class DeckEditorPanel extends javax.swing.JPanel { panelInfo.setOpaque(false); - deckLegalityDisplay.setMaximumSize(new java.awt.Dimension(245, 155)); - deckLegalityDisplay.setMinimumSize(new java.awt.Dimension(85, 155)); + deckLegalityDisplay.setMaximumSize(new java.awt.Dimension(245, 255)); + deckLegalityDisplay.setMinimumSize(new java.awt.Dimension(85, 255)); deckLegalityDisplay.setOpaque(false); deckLegalityDisplay.setVisible(false); @@ -1313,7 +1309,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { panelInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(panelInfoLayout.createSequentialGroup() .addContainerGap() - .addComponent(deckLegalityDisplay, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(deckLegalityDisplay, javax.swing.GroupLayout.PREFERRED_SIZE, 255, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(bigCard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java index 0e5e2ff3d91..998c174c7f6 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java @@ -5,6 +5,7 @@ import mage.cards.decks.Deck; import mage.cards.decks.DeckValidator; import mage.cards.mock.MockCard; import mage.cards.mock.MockSplitCard; +import mage.client.components.EdhPowerLevelLegalityLabel; import mage.client.components.LegalityLabel; import mage.deck.*; import org.apache.log4j.Logger; @@ -15,7 +16,7 @@ import java.util.stream.Stream; /** - * @author Elandril + * @author Elandril, JayDi85 */ public class DeckLegalityPanel extends javax.swing.JPanel { @@ -101,6 +102,10 @@ public class DeckLegalityPanel extends javax.swing.JPanel { new Frontier(), new HistoricalType2(), new PennyDreadfulCommander(), new EuropeanHighlander(), new CanadianHighlander() // not used: new Eternal(), new Momir(), new TinyLeaders() ).forEach(this::addLegalityLabel); + + // extra buttons like score + this.add(new EdhPowerLevelLegalityLabel()); + addHidePanelButton(); revalidate(); @@ -147,5 +152,4 @@ public class DeckLegalityPanel extends javax.swing.JPanel { .map(LegalityLabel.class::cast) .forEach(label -> label.validateDeck(deckToValidate)); } - } diff --git a/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java b/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java index 4e456b2155c..9018369f884 100644 --- a/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java +++ b/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java @@ -19,6 +19,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator { return "EDH: " + getPowerLevel(sample); } + // TODO: it's outdated code, must migrate to shared code from AbstractCommander private int getPowerLevel(CardView card) { int thisMaxPower = 0; 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 09ce5debf43..b2ab1adee77 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 @@ -19,7 +19,7 @@ import java.util.*; import java.util.stream.Stream; /** - * @author TheElk801 + * @author TheElk801, JayDi85 */ public abstract class AbstractCommander extends Constructed { @@ -258,14 +258,17 @@ public abstract class AbstractCommander extends Constructed { } @Override - public int getEdhPowerLevel(Deck deck) { - if (deck == null) { - return 0; - } - + public int getEdhPowerLevel(Deck deck, List foundPowerCards, List foundInfo) { + // calculate power level and find all related cards with it to show in hints (see deck validation in deck editor) int edhPowerLevel = 0; int commanderColors = 0; int numberInfinitePieces = 0; + foundPowerCards.clear(); + foundInfo.clear(); + + if (deck == null) { + return 0; + } for (Card card : deck.getCards()) { @@ -330,6 +333,7 @@ public abstract class AbstractCommander extends Constructed { boolean yourOpponentsControl = false; boolean whenYouCast = false; + List cardStates = new ArrayList<>(); for (String str : card.getRules()) { String s = str.toLowerCase(Locale.ENGLISH); annihilator |= s.contains("annihilator"); @@ -406,178 +410,236 @@ public abstract class AbstractCommander extends Constructed { if (extraTurns) { thisMaxPower = Math.max(thisMaxPower, 7); + cardStates.add(String.format("extraTurns %d", 7)); } if (buyback) { thisMaxPower = Math.max(thisMaxPower, 6); + cardStates.add(String.format("buyback %d", 6)); } if (tutor) { thisMaxPower = Math.max(thisMaxPower, 6); + cardStates.add(String.format("tutor %d", 6)); } if (annihilator) { thisMaxPower = Math.max(thisMaxPower, 5); + cardStates.add(String.format("annihilator %d", 5)); } if (cantUntap) { thisMaxPower = Math.max(thisMaxPower, 5); + cardStates.add(String.format("cantUntap %d", 5)); } if (costLessEach) { thisMaxPower = Math.max(thisMaxPower, 5); + cardStates.add(String.format("costLessEach %d", 5)); } if (infect) { thisMaxPower = Math.max(thisMaxPower, 5); + cardStates.add(String.format("infect %d", 5)); } if (overload) { thisMaxPower = Math.max(thisMaxPower, 5); + cardStates.add(String.format("overload %d", 5)); } if (twiceAs) { thisMaxPower = Math.max(thisMaxPower, 5); + cardStates.add(String.format("twiceAs %d", 5)); } if (cascade) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("cascade %d", 4)); } if (doesntUntap) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("doesntUntap %d", 4)); } if (each) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("each %d", 4)); } if (exileAll) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("exileAll %d", 4)); } if (flash) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("flash %d", 4)); } if (flashback) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("flashback %d", 4)); } if (flicker) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("flicker %d", 4)); } if (gainControl) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("gainControl %d", 4)); } if (lifeTotalBecomes) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("lifeTotalBecomes %d", 4)); } if (mayCastForFree) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("mayCastForFree %d", 4)); } if (preventDamage) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("preventDamage %d", 4)); } if (proliferate) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("proliferate %d", 4)); } if (protection) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("protection %d", 4)); } if (putUnderYourControl) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("putUnderYourControl %d", 4)); } if (returnFromYourGY) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("returnFromYourGY %d", 4)); } if (sacrifice) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("sacrifice %d", 2)); } if (skip) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("skip %d", 4)); } if (storm) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("storm %d", 4)); } if (unblockable) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("unblockable %d", 4)); } if (whenCounterThatSpell) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("whenCounterThatSpell %d", 4)); } if (wheneverEnters) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("wheneverEnters %d", 4)); } if (xCost) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("xCost %d", 4)); } if (youControlTarget) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("youControlTarget %d", 4)); } if (yourOpponentsControl) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("yourOpponentsControl %d", 4)); } if (whenYouCast) { thisMaxPower = Math.max(thisMaxPower, 4); + cardStates.add(String.format("whenYouCast %d", 4)); } if (anyNumberOfTarget) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("anyNumberOfTarget %d", 4)); } if (createToken) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("createToken %d", 3)); } if (destroyAll) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("destroyAll %d", 3)); } if (dredge) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("dredge %d", 3)); } if (hexproof) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("hexproof %d", 3)); } if (shroud) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("shroud %d", 3)); } if (undying) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("undying %d", 3)); } if (persist) { thisMaxPower = Math.max(thisMaxPower, 3); + cardStates.add(String.format("persist %d", 3)); } if (cantBe) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("cantBe %d", 2)); } if (evoke) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("evoke %d", 2)); } if (exile) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("exile %d", 2)); } if (menace) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("menace %d", 2)); } if (miracle) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("miracle %d", 2)); } if (sliver) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("sliver %d", 2)); } if (untapTarget) { thisMaxPower = Math.max(thisMaxPower, 2); + cardStates.add(String.format("untapTarget %d", 2)); } if (copy) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("copy %d", 1)); } if (counter) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("counter %d", 1)); } if (destroy) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("destroy %d", 1)); } if (drawCards) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("drawCards %d", 1)); } if (exalted) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("exalted %d", 1)); } if (retrace) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("retrace %d", 1)); } if (trample) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("trample %d", 1)); } if (tutorBasic) { thisMaxPower = Math.max(thisMaxPower, 1); + cardStates.add(String.format("tutorBasic %d", 1)); } if (card.isPlaneswalker()) { thisMaxPower = Math.max(thisMaxPower, 6); + cardStates.add(String.format("isPlaneswalker %d", 6)); } String cn = card.getName().toLowerCase(Locale.ENGLISH); @@ -845,6 +907,7 @@ public abstract class AbstractCommander extends Constructed { || cn.equals("zealous conscripts") || cn.equals("zur the enchanter")) { thisMaxPower = Math.max(thisMaxPower, 12); + cardStates.add(String.format("saltiest card %d", 12)); } // Parts of infinite combos @@ -906,6 +969,7 @@ public abstract class AbstractCommander extends Constructed { || cn.equals("worthy cause") || cn.equals("yawgmoth's will") || cn.equals("zealous conscripts")) { thisMaxPower = Math.max(thisMaxPower, 15); + cardStates.add(String.format("infinite combo %d", 15)); numberInfinitePieces++; } @@ -951,11 +1015,26 @@ public abstract class AbstractCommander extends Constructed { || cn.equals("winota, joiner of forces") || cn.equals("yuriko, the tiger's shadow")) { thisMaxPower = Math.max(thisMaxPower, 20); + cardStates.add(String.format("game changer %d", 20)); } - } + + // keep card's level + if (!cardStates.isEmpty()) { + foundInfo.add(String.format("+%d from %s (%s)", + thisMaxPower, + card.getName(), + String.join(", ", cardStates) + )); + foundPowerCards.add(card.getName()); + } + + edhPowerLevel += thisMaxPower; + + } // cards list ObjectColor color = null; for (Card commander : deck.getSideboard()) { + List commanderStates = new ArrayList<>(); int thisMaxPower = 0; String cn = commander.getName().toLowerCase(Locale.ENGLISH); if (color == null) { @@ -1024,6 +1103,7 @@ public abstract class AbstractCommander extends Constructed { || cn.equals("vorinclex, voice of hunger") || cn.equals("zur the enchanter")) { thisMaxPower = Math.max(thisMaxPower, 25); + commanderStates.add(String.format("not fun commander (+%d)", 25)); } // Saltiest commanders @@ -1060,21 +1140,40 @@ public abstract class AbstractCommander extends Constructed { || cn.equals("xanathar, guild kingpin") || cn.equals("zur the enchanter")) { thisMaxPower = Math.max(thisMaxPower, 20); + commanderStates.add(String.format("saltiest commander (+%d)", 20)); } + + // keep commander's level + if (!commanderStates.isEmpty()) { + foundInfo.add(String.format("+%d from %s (%s)", + thisMaxPower, + commander.getName(), + String.join(", ", commanderStates) + )); + foundPowerCards.add(commander.getName()); + } + edhPowerLevel += thisMaxPower; } - edhPowerLevel += numberInfinitePieces * 18; - edhPowerLevel = Math.round(edhPowerLevel / 10); - if (edhPowerLevel >= 100) { - edhPowerLevel = 99; + if (numberInfinitePieces > 0) { + edhPowerLevel += numberInfinitePieces * 18; + foundInfo.add(String.format("+%d from %d infinite pieces", numberInfinitePieces * 18, numberInfinitePieces)); } - 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); + + // block colored decks by table's edh power level were disabled + // it's better to show real edh power level and allow for direct values control + if (false) { + 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/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index f9625d148cd..d11b3c5950b 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -325,7 +325,7 @@ public class TableController { // user - restrict by deck power level and cards colors (see edh power level for details) int edhPowerLevel = table.getMatch().getOptions().getEdhPowerLevel(); if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase(Locale.ENGLISH).equals("commander")) { - int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck); + int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck, new ArrayList<>(), new ArrayList<>()); if (deckEdhPowerLevel % 100 > edhPowerLevel) { String message = new StringBuilder("Your deck appears to be too powerful for this table.\n\nReduce the number of extra turn cards, infect, counters, fogs, reconsider your commander. ") .append("\nThe table requirement has a maximum power level of ").append(edhPowerLevel).append(" whilst your deck has a calculated power level of ") diff --git a/Mage/src/main/java/mage/cards/decks/DeckValidator.java b/Mage/src/main/java/mage/cards/decks/DeckValidator.java index 6a9772c4ae5..a80ff3d7444 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckValidator.java +++ b/Mage/src/main/java/mage/cards/decks/DeckValidator.java @@ -174,7 +174,7 @@ public abstract class DeckValidator implements Serializable { } } - public int getEdhPowerLevel(Deck deck) { + public int getEdhPowerLevel(Deck deck, List foundPowerCards, List foundInfo) { return 0; }