Merge branch 'master' into refactor/multiple-names

This commit is contained in:
Evan Kranzler 2025-06-02 17:15:42 -04:00 committed by GitHub
commit 5f59e45302
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11478 changed files with 191207 additions and 64927 deletions

View file

@ -6,7 +6,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.54</version>
<version>1.4.57</version>
</parent>
<artifactId>mage-client</artifactId>

Binary file not shown.

View file

@ -4,4 +4,4 @@ set JAVA_HOME="C:\Program Files\Java\jre7\"
set CLASSPATH=%JAVA_HOME%/bin;%CLASSPATH%
set PATH=%JAVA_HOME%/bin;%PATH%
:NOJAVADIR
java -Xmx1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -jar .\lib\mage-client-${project.version}.jar
java -Xmx2000m -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -jar .\lib\mage-client-${project.version}.jar

View file

@ -2,4 +2,4 @@
cd "`dirname "$0"`"
java -Xmx1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -jar ./lib/mage-client-${project.version}.jar &
java -Xmx2000m -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -jar ./lib/mage-client-${project.version}.jar &

View file

@ -1,3 +1,3 @@
#!/bin/sh
java -Xmx1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -jar ./lib/mage-client-${project.version}.jar &
java -Xmx2000m -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -jar ./lib/mage-client-${project.version}.jar &

View file

@ -4,4 +4,4 @@ set JAVA_HOME="C:\Program Files (x86)\Java\jre7\"
set CLASSPATH=%JAVA_HOME%/bin;%CLASSPATH%
set PATH=%JAVA_HOME%/bin;%PATH%
:NOJAVADIR
java -Xmx1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -jar .\lib\mage-client-${project.version}.jar
java -Xmx2000m -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -jar .\lib\mage-client-${project.version}.jar

View file

@ -26,6 +26,7 @@ import mage.client.plugins.adapters.MageActionCallback;
import mage.client.plugins.impl.Plugins;
import mage.client.preference.MagePreferences;
import mage.client.remote.CallbackClientImpl;
import mage.client.remote.XmageURLConnection;
import mage.client.table.TablesPane;
import mage.client.table.TablesPanel;
import mage.client.tournament.TournamentPane;
@ -54,6 +55,7 @@ import net.java.truevfs.access.TArchiveDetector;
import net.java.truevfs.access.TConfig;
import net.java.truevfs.kernel.spec.FsAccessOption;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.mage.card.arcane.ManaSymbols;
import org.mage.card.arcane.SvgUtils;
import org.mage.plugins.card.images.DownloadPicturesService;
@ -196,19 +198,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
}
public MageFrame() throws MageException {
File cacertsFile = new File(System.getProperty("user.dir") + "/release/cacerts").getAbsoluteFile();
if (!cacertsFile.exists()) { // When running from the jar file the contents of the /release folder will have been expanded into the home folder as part of packaging
cacertsFile = new File(System.getProperty("user.dir") + "/cacerts").getAbsoluteFile();
}
if (cacertsFile.exists()) {
LOGGER.info("Custom (or bundled) Java certificate file (cacerts) file found");
String cacertsPath = cacertsFile.getPath();
System.setProperty("javax.net.ssl.trustStore", cacertsPath);
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
} else {
LOGGER.info("custom Java certificate file not found at: " + cacertsFile.getAbsolutePath());
}
setWindowTitle();
// mac os only: enable full screen support in java 8 (java 11+ try to use it all the time)
@ -403,6 +392,41 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
});
}
/**
* Init certificates store for https work (if java version is outdated)
* Debug with -Djavax.net.debug=SSL,trustmanager
*/
@Deprecated // TODO: replaced by enableAIAcaIssuers, delete that code after few releases (2025-01-01)
private void initSSLCertificates() {
// from dev build (runtime)
boolean cacertsUsed = false;
File cacertsFile = new File(System.getProperty("user.dir") + "/release/cacerts").getAbsoluteFile();
if (cacertsFile.exists()) {
cacertsUsed = true;
LOGGER.info("SSL certificates: used runtime cacerts bundle");
}
// from release build (jar)
// When running from the jar file the contents of the /release folder will have been expanded into the home folder as part of packaging
if (!cacertsUsed) {
cacertsFile = new File(System.getProperty("user.dir") + "/cacerts").getAbsoluteFile();
if (cacertsFile.exists()) {
cacertsUsed = true;
LOGGER.info("SSL certificates: used release cacerts bundle");
}
}
if (cacertsUsed && cacertsFile.exists()) {
String cacertsPath = cacertsFile.getPath();
System.setProperty("javax.net.ssl.trustStoreType", "PKCS12"); // cacerts file format from java 9+ instead "jks" from java 8
System.setProperty("javax.net.ssl.trustStore", cacertsPath);
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
} else {
LOGGER.info("SSL certificates: used default cacerts bundle from " + System.getProperty("java.version"));
}
System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
}
private void bootstrapSetsAndFormats() {
LOGGER.info("Loading sets and formats...");
ConstructedFormats.ensureLists();
@ -925,11 +949,15 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
} catch (SocketException ex) {
}
currentConnection.setUserIdStr(System.getProperty("user.name") + ":" + System.getProperty("os.name") + ":" + MagePreferences.getUserNames() + ":" + allMAC);
currentConnection.setProxyType(proxyType);
currentConnection.setProxyHost(proxyServer);
currentConnection.setProxyPort(proxyPort);
currentConnection.setProxyUsername(proxyUsername);
currentConnection.setProxyPassword(proxyPassword);
if (PreferencesDialog.NETWORK_ENABLE_PROXY_SUPPORT) {
currentConnection.setProxyType(proxyType);
currentConnection.setProxyHost(proxyServer);
currentConnection.setProxyPort(proxyPort);
currentConnection.setProxyUsername(proxyUsername);
currentConnection.setProxyPassword(proxyPassword);
} else {
currentConnection.setProxyType(ProxyType.NONE);
}
setUserPrefsToConnection(currentConnection);
}
@ -1502,7 +1530,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
if (!Charset.defaultCharset().toString().equals("UTF-8")) {
LOGGER.warn("WARNING, bad charset. Some images will not be downloaded. You must:");
LOGGER.warn("* Open launcher -> settings -> java -> client java options");
LOGGER.warn("* Insert additional command at the the end: -Dfile.encoding=UTF-8");
LOGGER.warn("* Insert at the the end: -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8");
}
startTime = System.currentTimeMillis();

View file

@ -114,12 +114,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING);
int col = x / (cardWidth + GRID_PADDING);
int dx = x % (cardWidth + getGridPadding());
int col = x / (cardWidth + getGridPadding());
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
int countLabelHeight = getCountLabelHeight();
if (dx < GRID_PADDING && col < gridWidth) {
if (dx < getGridPadding() && col < gridWidth) {
// Which row to add to?
int curY = countLabelHeight;
int rowIndex = 0;
@ -142,7 +142,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Insert between two columns
insertArrow.setIcon(INSERT_COL_ICON);
insertArrow.setSize(64, 64);
insertArrow.setLocation((cardWidth + GRID_PADDING) * col + GRID_PADDING / 2 - 32, curY);
insertArrow.setLocation((cardWidth + getGridPadding()) * col + getGridPadding() / 2 - 32, curY);
} else {
// Clamp to a new col one after the current last one
col = Math.min(col, gridWidth);
@ -184,7 +184,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Position arrow
insertArrow.setIcon(INSERT_ROW_ICON);
insertArrow.setSize(64, 32);
insertArrow.setLocation((cardWidth + GRID_PADDING) * col + GRID_PADDING + cardWidth / 2 - 32, curY + stackInsertIndex * cardTopHeight - 32);
insertArrow.setLocation((cardWidth + getGridPadding()) * col + getGridPadding() + cardWidth / 2 - 32, curY + stackInsertIndex * cardTopHeight - 32);
}
}
@ -225,12 +225,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
int cardWidth = getCardWidth();
int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING);
int col = x / (cardWidth + GRID_PADDING);
int dx = x % (cardWidth + getGridPadding());
int col = x / (cardWidth + getGridPadding());
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
int countLabelHeight = getCountLabelHeight();
if (dx < GRID_PADDING && col < gridWidth) {
if (dx < getGridPadding() && col < gridWidth) {
// Which row to add to?
int curY = countLabelHeight;
int rowIndex = 0;
@ -334,7 +334,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Add new cards to grid
for (CardView card : cards) {
card.setSelected(true);
addCardView(card, false);
addCardView(card, null);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
}
layoutGrid();
@ -575,7 +575,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Constants
private static final int DEFAULT_COUNT_LABEL_HEIGHT = 40; // can contain 1 or 2 lines
public static final int GRID_PADDING = 10;
public static final int GRID_PADDING = 20;
private static final ImageIcon INSERT_ROW_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_row.png"));
private static final ImageIcon INSERT_COL_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_col.png"));
@ -1044,7 +1044,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
selectBySearchPanelC.fill = GridBagConstraints.VERTICAL;
searchByTextField = new JTextField();
searchByTextField.setToolTipText("Searches for card names, types, rarity, casting cost and rules text. NB: Mana symbols are written like {W},{U},{C} etc");
searchByTextField.setToolTipText("Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)");
searchByTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
@ -1253,10 +1253,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
selectionPanel.setSize(x2 - x1, y2 - y1);
// First and last cols
int col1 = x1 / (cardWidth + GRID_PADDING);
int col2 = x2 / (cardWidth + GRID_PADDING);
int offsetIntoCol2 = x2 % (cardWidth + GRID_PADDING);
if (offsetIntoCol2 < GRID_PADDING) {
int col1 = x1 / (cardWidth + getGridPadding());
int col2 = x2 / (cardWidth + getGridPadding());
int offsetIntoCol2 = x2 % (cardWidth + getGridPadding());
if (offsetIntoCol2 < getGridPadding()) {
--col2;
}
@ -1322,7 +1322,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// re-insert
for (CardView card : allCards) {
sortIntoGrid(card);
sortIntoGrid(card, null);
}
trimGrid();
@ -1717,7 +1717,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (acard.getName().equals(card.getName())) {
CardView pimpedCard = new CardView(acard);
addCardView(pimpedCard, false);
addCardView(pimpedCard, null);
eventSource.fireEvent(pimpedCard, ClientEventType.DECK_ADD_SPECIFIC_CARD);
pimpedCards.put(pimpedCard, 1);
didModify = true;
@ -1729,7 +1729,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (didModify) {
for (CardView c : pimpedCards.keySet()) {
sortIntoGrid(c);
sortIntoGrid(c, null);
}
trimGrid();
@ -1762,7 +1762,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
CardView oldestCardView = new CardView(oldestCardInfo.createMockCard());
this.removeCardView(card);
eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD);
this.addCardView(oldestCardView, false);
this.addCardView(oldestCardView, null);
eventSource.fireEvent(oldestCardView, ClientEventType.DECK_ADD_SPECIFIC_CARD);
newStack.add(oldestCardView);
} else {
@ -1814,10 +1814,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) {
// Is a new card
addCardView(newCard, false);
addCardView(newCard, null);
// Put it into the appropirate place in the grid given the current sort
sortIntoGrid(newCard);
sortIntoGrid(newCard, null);
// Mark
didModify = true;
@ -1837,7 +1837,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) {
// Add the new card
addCardView(newCard, false);
addCardView(newCard, null);
// Add the new card to tracking
Map<String, List<CardView>> forSetCode;
@ -1889,7 +1889,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (List<CardView> orphans : tracked.values()) {
for (CardView orphan : orphans) {
logger.info("Orphan when setting with layout: ");
sortIntoGrid(orphan);
sortIntoGrid(orphan, null);
}
}
}
@ -1984,7 +1984,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
menu.show(e.getComponent(), e.getX(), e.getY());
}
public void addCardView(final CardView card, boolean duplicated) {
public void addCardView(final CardView card, final CardView duplicatedFromCard) {
allCards.add(card);
// Update counts
@ -2019,8 +2019,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
cardContent.add(cardPanel);
cardViews.put(card.getId(), cardPanel);
if (duplicated) {
sortIntoGrid(card);
if (duplicatedFromCard != null) {
sortIntoGrid(card, duplicatedFromCard);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
// clear grid from empty rows
@ -2094,7 +2094,21 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
*
* @param newCard Card to add to the cardGrid array.
*/
private void sortIntoGrid(CardView newCard) {
private void sortIntoGrid(CardView newCard, CardView duplicatedFromCard) {
// fast put duplicated card to the same place as original
if (duplicatedFromCard != null) {
for (List<List<CardView>> gridRow : cardGrid) {
for (List<CardView> gridStack : gridRow) {
for (int i = 0; i < gridStack.size(); i++) {
if (gridStack.get(i).equals(duplicatedFromCard)) {
gridStack.add(i, newCard);
return;
}
}
}
}
}
// row 1 must exists
if (cardGrid.isEmpty()) {
cardGrid.add(0, new ArrayList<>());
@ -2336,7 +2350,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
} else {
String description = cardSort.getComparator().getCategoryName(stack.get(0));
DragCardGrid.updateCountLabel(countLabel, stack.size(), description);
countLabel.setLocation(GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex, currentY - countLabelHeight);
countLabel.setLocation(getGridPadding() + (cardWidth + getGridPadding()) * colIndex, currentY - countLabelHeight);
countLabel.setSize(cardWidth, countLabelHeight);
countLabel.setVisible(true);
}
@ -2348,7 +2362,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i);
MageCard view = cardViews.get(card.getId());
int x = GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex;
int x = getGridPadding() + (cardWidth + getGridPadding()) * colIndex;
int y = currentY + i * cardTopHeight;
view.setCardBounds(x, y, cardWidth, cardHeight);
cardContent.setLayer(view, layerIndex++);
@ -2356,14 +2370,18 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
}
// Update the max stack size for this row and the max width
maxWidth = Math.max(maxWidth, GRID_PADDING + (GRID_PADDING + cardWidth) * gridRow.size());
maxWidth = Math.max(maxWidth, getGridPadding() + (getGridPadding() + cardWidth) * gridRow.size());
maxStackSize.set(rowIndex, rowMaxStackSize);
currentY += (cardTopHeight * (rowMaxStackSize - 1) + cardHeight) + countLabelHeight;
}
// Resize card container
cardContent.setPreferredSize(new Dimension(maxWidth, currentY - countLabelHeight + GRID_PADDING));
//cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + GRID_PADDING);
cardContent.setPreferredSize(new Dimension(maxWidth, currentY - countLabelHeight + getGridPadding()));
//cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + getGridPadding());
}
private int getGridPadding() {
return Math.max(GRID_PADDING, Math.round(cardSizeMod * GRID_PADDING * GUISizeHelper.dialogGuiScale));
}
public static int getCountLabelHeight() {

View file

@ -265,7 +265,7 @@ public class ChatPanelBasic extends javax.swing.JPanel {
}
String cachedProfanityFilterValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAME_USE_PROFANITY_FILTER, "0");
boolean isContainsSwearing = !containsSwearing(messageToTest, cachedProfanityFilterValue);
boolean isContainsSwearing = containsSwearing(messageToTest, cachedProfanityFilterValue);
boolean isUserInfoOrGameOrStatus = messageType == MessageType.USER_INFO || messageType == MessageType.GAME || messageType == MessageType.STATUS;
if (isUserInfoOrGameOrStatus || cachedProfanityFilterValue.equals("0") || (!cachedProfanityFilterValue.equals("0") && !isContainsSwearing)) {
if (username != null && !username.isEmpty()) {

View file

@ -83,7 +83,7 @@ public enum CombatManager {
UUID defenderId = group.getDefenderId();
if (defenderId != null) {
// if attacker was blocked then use another arrow color
Color attackColor = group.getBlockers().isEmpty() ? ARROW_COLOR_ATTACKER : ARROW_COLOR_BLOCKED_ATTACKER;
Color attackColor = group.isBlocked() ? ARROW_COLOR_BLOCKED_ATTACKER : ARROW_COLOR_ATTACKER;
parentPoint = getParentPoint(attackerCard);
PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId);
if (p != null) {

View file

@ -0,0 +1,343 @@
package mage.client.components;
import mage.MageObject;
import mage.cards.Card;
import mage.cards.decks.Deck;
import mage.client.util.GUISizeHelper;
import java.awt.*;
import java.util.List;
import java.util.*;
import java.util.stream.Stream;
/**
* Inject bracket level inside validation panel
* See more details at <a href="https://mtg.wiki/page/Commander_Brackets">wiki</a>
* <p>
* Support:
* - [x] game changers
* - [ ] infinite combos
* - [x] mass land destruction
* - [x] extra turns
* - [x] tutors
*
* @author JayDi85
*/
public class BracketLegalityLabel extends LegalityLabel {
private static final String GROUP_GAME_CHANGES = "Game Changers";
private static final String GROUP_INFINITE_COMBOS = "Infinite Combos (unsupported)";
private static final String GROUP_MASS_LAND_DESTRUCTION = "Mass Land Destruction";
private static final String GROUP_EXTRA_TURN = "Extra Turns";
private static final String GROUP_TUTORS = "Tutors";
private final BracketLevel level;
private final List<String> foundGameChangers = new ArrayList<>();
private final List<String> foundInfiniteCombos = new ArrayList<>();
private final List<String> foundMassLandDestruction = new ArrayList<>();
private final List<String> foundExtraTurn = new ArrayList<>();
private final List<String> foundTutors = new ArrayList<>();
private final List<String> badCards = new ArrayList<>();
private final List<String> fullGameChanges = new ArrayList<>();
public enum BracketLevel {
BRACKET_1("Bracket 1"),
BRACKET_2_3("Bracket 2-3"),
BRACKET_4_5("Bracket 4-5");
private final String name;
BracketLevel(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
public BracketLegalityLabel(BracketLevel level) {
super(level.toString(), null);
this.level = level;
setPreferredSize(DIM_PREFERRED);
}
@Override
public List<String> selectCards() {
return new ArrayList<>(this.badCards);
}
private void validateBracketLevel() {
this.badCards.clear();
switch (this.level) {
case BRACKET_1:
// No cards from the Game Changer list.
// No intentional two-card infinite combos.
// No mass land destruction.
// No extra turn cards.
// Tutors should be sparse.
this.badCards.addAll(this.foundGameChangers);
this.badCards.addAll(this.foundInfiniteCombos);
this.badCards.addAll(this.foundMassLandDestruction);
this.badCards.addAll(this.foundExtraTurn);
if (this.foundTutors.size() > 3) {
this.badCards.addAll(this.foundTutors);
}
break;
case BRACKET_2_3:
// 2
// No cards from the Game Changer list.
// No intentional two-card infinite combos.
// No mass land destruction.
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
// Tutors should be sparse.
// 3
// Up to three (3) cards from the Game Changer list.
// No intentional early game two-card infinite combos.
// No mass land destruction.
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
if (this.foundGameChangers.size() > 3) {
this.badCards.addAll(this.foundGameChangers);
}
this.badCards.addAll(this.foundInfiniteCombos);
this.badCards.addAll(this.foundMassLandDestruction);
if (this.foundExtraTurn.size() > 3) {
this.badCards.addAll(this.foundExtraTurn);
}
// this.badCards.addAll(this.foundTutors); // allow any amount
break;
case BRACKET_4_5:
// allow any cards
break;
default:
throw new IllegalArgumentException("Unsupported level: " + this.level);
}
}
@Override
public void validateDeck(Deck deck) {
collectAll(deck);
validateBracketLevel();
int infoFontSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f);
// show all found cards in any use cases
Color showColor = this.badCards.isEmpty() ? COLOR_LEGAL : COLOR_NOT_LEGAL;
List<String> showInfo = new ArrayList<>();
if (this.badCards.isEmpty()) {
showInfo.add("<p>Deck is <span style='color:green;font-weight:bold;'>GOOD</span> for " + this.level + "</p>");
} else {
showInfo.add("<p>Deck is <span style='color:#BF544A;font-weight:bold;'>BAD</span> for " + this.level + "</p>");
showInfo.add("<p>(click here to select all bad cards)</p>");
}
Map<String, List<String>> groups = new LinkedHashMap<>();
groups.put(GROUP_GAME_CHANGES, this.foundGameChangers);
groups.put(GROUP_INFINITE_COMBOS, this.foundInfiniteCombos);
groups.put(GROUP_MASS_LAND_DESTRUCTION, this.foundMassLandDestruction);
groups.put(GROUP_EXTRA_TURN, this.foundExtraTurn);
groups.put(GROUP_TUTORS, this.foundTutors);
groups.forEach((group, cards) -> {
showInfo.add("<br>");
showInfo.add("<br>");
showInfo.add("<span style='font-weight:bold;'>" + group + ": " + cards.size() + "</span>");
if (!cards.isEmpty()) {
showInfo.add("<ul style=\"font-size: " + infoFontSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">");
cards.forEach(s -> showInfo.add(String.format("<li style=\"margin-bottom: 2px;\">%s</li>", s)));
showInfo.add("</ul>");
}
});
String showText = "<html><body>" + String.join("\n", showInfo) + "</body></html>";
showState(showColor, showText, false);
}
private void collectAll(Deck deck) {
collectGameChangers(deck);
collectInfiniteCombos(deck);
collectMassLandDestruction(deck);
collectExtraTurn(deck);
collectTutors(deck);
}
private void collectGameChangers(Deck deck) {
this.foundGameChangers.clear();
if (fullGameChanges.isEmpty()) {
// https://mtg.wiki/page/Game_Changers
// TODO: share list with AbstractCommander and edh power level
fullGameChanges.addAll(Arrays.asList(
"Ad Nauseam",
"Ancient Tomb",
"Aura Shards",
"Bolas's Citadel",
"Braids, Cabal Minion",
"Demonic Tutor",
"Drannith Magistrate",
"Chrome Mox",
"Coalition Victory",
"Consecrated Sphinx",
"Crop Rotation",
"Cyclonic Rift",
"Deflecting Swat",
"Enlightened Tutor",
"Expropriate",
"Field of the Dead",
"Fierce Guardianship",
"Food Chain",
"Force of Will",
"Gaea's Cradle",
"Gamble",
"Gifts Ungiven",
"Glacial Chasm",
"Grand Arbiter Augustin IV",
"Grim Monolith",
"Humility",
"Imperial Seal",
"Intuition",
"Jeska's Will",
"Jin-Gitaxias, Core Augur",
"Kinnan, Bonder Prodigy",
"Lion's Eye Diamond",
"Mana Vault",
"Mishra's Workshop",
"Mox Diamond",
"Mystical Tutor",
"Narset, Parter of Veils",
"Natural Order",
"Necropotence",
"Notion Thief",
"Rhystic Study",
"Opposition Agent",
"Orcish Bowmasters",
"Panoptic Mirror",
"Seedborn Muse",
"Serra's Sanctum",
"Smothering Tithe",
"Survival of the Fittest",
"Sway of the Stars",
"Teferi's Protection",
"Tergrid, God of Fright",
"Thassa's Oracle",
"The One Ring",
"The Tabernacle at Pendrell Vale",
"Underworld Breach",
"Urza, Lord High Artificer",
"Vampiric Tutor",
"Vorinclex, Voice of Hunger",
"Yuriko, the Tiger's Shadow",
"Winota, Joiner of Forces",
"Worldly Tutor"
));
}
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.map(MageObject::getName)
.filter(fullGameChanges::contains)
.sorted()
.forEach(this.foundGameChangers::add);
}
private void collectInfiniteCombos(Deck deck) {
// TODO: implement
this.foundInfiniteCombos.clear();
}
private void collectMassLandDestruction(Deck deck) {
// https://mtg.wiki/page/Land_destruction
// https://draftsim.com/mtg-mass-land-destruction/
this.foundMassLandDestruction.clear();
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.filter(card -> card.getRules().stream()
.map(s -> s.toLowerCase(Locale.ENGLISH))
.anyMatch(s -> (s.contains("destroy") || s.contains("sacrifice"))
&& (s.contains("all") || s.contains("x target") || s.contains("{x} target"))
&& isTextContainsLandName(s)
)
)
.map(Card::getName)
.sorted()
.forEach(this.foundMassLandDestruction::add);
}
private void collectExtraTurn(Deck deck) {
this.foundExtraTurn.clear();
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.filter(card -> card.getRules().stream()
.map(s -> s.toLowerCase(Locale.ENGLISH))
.anyMatch(s -> s.contains("extra turn"))
)
.map(Card::getName)
.sorted()
.forEach(this.foundExtraTurn::add);
}
private void collectTutors(Deck deck) {
// edh power level uses search for land and non-land card, but bracket need only non-land cards searching
this.foundTutors.clear();
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.filter(card -> card.getRules().stream()
.map(s -> s.toLowerCase(Locale.ENGLISH))
.anyMatch(s -> s.contains("search your library") && !isTextContainsLandCard(s))
)
.map(Card::getName)
.sorted()
.forEach(this.foundTutors::add);
}
private boolean isTextContainsLandCard(String lowerText) {
// TODO: share code with AbstractCommander and edh power level
// TODO: add tests
return lowerText.contains("basic ")
|| lowerText.contains("plains card")
|| lowerText.contains("island card")
|| lowerText.contains("swamp card")
|| lowerText.contains("mountain card")
|| lowerText.contains("forest card");
}
private boolean isTextContainsLandName(String lowerText) {
// TODO: add tests to find all cards from https://mtg.wiki/page/Land_destruction
// TODO: add tests
/*
// mass land destruction
Ajani Vengeant
Armageddon
Avalanche
Bend or Break
Boil
Boiling Seas
Boom // Bust
Burning of Xinye
Catastrophe
Decree of Annihilation
Desolation Angel
Devastation
Fall of the Thran
From the Ashes
Impending Disaster
Jokulhaups
Myojin of Infinite Rage
Numot, the Devastator
Obliterate
Orcish Settlers
Ravages of War
Ruination
Rumbling Crescendo
Scorched Earth
Tsunami
Wake of Destruction
Wildfire
*/
return lowerText.contains("lands")
|| lowerText.contains("plains")
|| lowerText.contains("island")
|| lowerText.contains("swamp")
|| lowerText.contains("mountain")
|| lowerText.contains("forest");
}
}

View file

@ -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<String> foundPowerCards = new ArrayList<>();
public EdhPowerLevelLegalityLabel() {
super("EDH Power Level: ?", null);
setPreferredSize(DIM_PREFERRED_X3);
}
@Override
public List<String> selectCards() {
// choose cards with power level
return this.foundPowerCards;
}
@Override
public void validateDeck(Deck deck) {
// find and save power level and card hints
List<String> 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<String> 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("<li style=\"margin-bottom: 2px;\">and %d more cards</li>", this.foundPowerCards.size() - maxLimit);
return foundInfo.stream()
.limit(maxLimit)
.reduce("<html><body>"
+ "<p>EDH Power Level: <span style='color:#b8860b;font-weight:bold;'>" + level + "</span></p>"
+ "<br>"
+ "<u>Found <span style='font-weight:bold;'>" + this.foundPowerCards.size() + "</span> cards with power levels (click to select it)</u>"
+ "<br>"
+ "<ul style=\"font-size: " + infoFontSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">",
(str, info) -> str + String.format("<li style=\"margin-bottom: 2px;\">%s</li>", info), String::concat)
+ extraInfo
+ "</ul>"
+ "</body></html>";
}
}

View file

@ -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("<html><body><b>No Deck loaded!</b></body></html>");
return;
public java.util.List<String> 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());
}
}

View file

@ -41,7 +41,7 @@ public class MageTextArea extends MageEditorPane {
// prepare text format as header and details texts
final StringBuilder buffer = new StringBuilder(512);
final StringBuilder buffer = new StringBuilder();
// Dialog is a java logical font family, so it should work on all systems
buffer.append("<body style='font-family:Dialog;font-size:");
buffer.append(GUISizeHelper.gameFeedbackPanelMainMessageFontSize);

View file

@ -152,7 +152,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener {
setBackgroundPainter(mwPanelPainter);
title = new ColorPane();
title.setFont(new Font("Times New Roman", 1, sizeMod(15)));
title.setFont(new Font("Times New Roman", Font.BOLD, sizeMod(15)));
title.setEditable(false);
title.setFocusCycleRoot(false);
title.setOpaque(false);
@ -186,11 +186,12 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener {
rows.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
if (SwingUtilities.isLeftMouseButton(evt) && !rows.isSelectionEmpty()) {
objectMouseClicked(evt);
}
}
});
rows.setSelectedIndex(0);
rows.setFont(new Font("Times New Roman", 1, sizeMod(17)));
rows.setBorder(BorderFactory.createEmptyBorder());
@ -233,18 +234,16 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int notches = e.getWheelRotation();
int index = rows.getSelectedIndex();
if (notches < 0) {
if (index > 0) {
rows.setSelectedIndex(index - 1);
rows.repaint();
}
} else if (index < choices.size() - 1) {
rows.setSelectedIndex(index + 1);
rows.repaint();
int direction = e.getWheelRotation() < 0 ? -1 : +1;
int index = rows.getSelectedIndex() + direction;
if (index < 0) {
index = 0;
} else if (index >= choices.size()) {
index = choices.size() - 1;
}
rows.setSelectedIndex(index);
rows.repaint();
}
private void objectMouseClicked(MouseEvent event) {

View file

@ -81,7 +81,7 @@ public class DialogContainer extends JPanel {
case EMBLEMS: {
backgroundColor = new Color(0, 0, 50, 110);
alpha = 0;
ChoiceDialog dlg = new ChoiceDialog(params, "Command Zone (Commander, Emblems and Planes)");
ChoiceDialog dlg = new ChoiceDialog(params, "Command Zone (Commanders, Emblems, and Planes)");
add(dlg);
dlg.setLocation(X_OFFSET + 10, Y_OFFSET + 10);
dlg.updateSize(params.rect.width - 80, params.rect.height - 80);

View file

@ -5,20 +5,21 @@ import mage.cards.decks.Deck;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.ExpansionRepository;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.sets.ConstructedFormats;
import mage.constants.CardType;
import mage.constants.ColoredManaSymbol;
import mage.constants.SuperType;
import mage.util.RandomUtil;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Generates random card pool and builds a deck.
*
* @author nantuko
* @author Simown
* @author nantuko, Simown, JayDi85
*/
public final class DeckGenerator {
@ -124,7 +125,7 @@ public final class DeckGenerator {
genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(),
genDialog.getLandPercentage(), allowedColors, genDialog.isSingleton(), genDialog.isColorless(),
genDialog.isAdvanced(), genDialog.getDeckGeneratorCMC());
genDialog.isCommander(), genDialog.isAdvanced(), genDialog.getDeckGeneratorCMC());
final String[] sets = setsToUse.toArray(new String[setsToUse.size()]);
@ -155,8 +156,8 @@ public final class DeckGenerator {
// Generate basic land cards
Map<String, List<CardInfo>> basicLands = DeckGeneratorPool.generateBasicLands(setsToUse);
DeckGeneratorPool.generateSpells(creatureCriteria, genPool.getCreatureCount());
DeckGeneratorPool.generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount());
DeckGeneratorPool.generateSpells(creatureCriteria, genPool.getCreatureCount(), genPool.getCommandersCount());
DeckGeneratorPool.generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount(), 0);
DeckGeneratorPool.generateLands(genDialog.useNonBasicLand(), nonBasicLandCriteria, basicLands);
// Reconstructs the final deck and adjusts for Math rounding and/or missing cards

View file

@ -1,46 +1,76 @@
package mage.client.deck.generator;
import com.google.common.collect.ImmutableList;
import java.util.List;
/**
* Mana value distribution between cards in diff deck sizes
*/
public enum DeckGeneratorCMC {
Low(ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.60f))
.add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.10f)).build(),
Low(
// 100
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.55f))
.add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.15f)).build(),
// 60
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.60f))
.add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.10f)).build(),
// 40
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.65f))
.add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 5, 0.05f)).build()),
Default(ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.20f))
.add(new CMC(3, 5, 0.50f))
.add(new CMC(6, 7, 0.25f))
.add(new CMC(8, 100, 0.05f)).build(),
Default(
// 100
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.15f))
.add(new CMC(3, 5, 0.50f))
.add(new CMC(6, 7, 0.30f))
.add(new CMC(8, 100, 0.05f)).build(),
// 60
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.20f))
.add(new CMC(3, 5, 0.50f))
.add(new CMC(6, 7, 0.25f))
.add(new CMC(8, 100, 0.05f)).build(),
// 40
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.30f))
.add(new CMC(3, 4, 0.45f))
.add(new CMC(5, 6, 0.20f))
.add(new CMC(7, 100, 0.05f)).build()),
High(ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.05f))
.add(new CMC(3, 5, 0.35f))
.add(new CMC(6, 7, 0.40f))
.add(new CMC(8, 100, 0.15f)).build(),
High(
// 100
ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.05f))
.add(new CMC(3, 5, 0.40f))
.add(new CMC(6, 7, 0.40f))
.add(new CMC(8, 100, 0.15f)).build(),
// 60
ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.05f))
.add(new CMC(3, 5, 0.35f))
.add(new CMC(6, 7, 0.40f))
.add(new CMC(8, 100, 0.15f)).build(),
// 40
ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.10f))
.add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.45f))
.add(new CMC(7, 100, 0.15f)).build());
private final List<CMC> poolCMCs100;
private final List<CMC> poolCMCs60;
private final List<CMC> poolCMCs40;
DeckGeneratorCMC(List<CMC> CMCs60, List<CMC> CMCs40) {
DeckGeneratorCMC(List<CMC> CMCs100, List<CMC> CMCs60, List<CMC> CMCs40) {
this.poolCMCs100 = CMCs100;
this.poolCMCs60 = CMCs60;
this.poolCMCs40 = CMCs40;
}
@ -53,6 +83,10 @@ public enum DeckGeneratorCMC {
return this.poolCMCs60;
}
public List<CMC> get100CardPoolCMC() {
return this.poolCMCs100;
}
static class CMC {
public final int min;
public final int max;

View file

@ -32,7 +32,7 @@ public class DeckGeneratorDialog {
private static String selectedColors;
private static JComboBox cbSets, cbDeckSize, cbCMC;
private static JButton btnGenerate, btnCancel, btnReset;
private static JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless, cAdvanced;
private static JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless, cAdvanced, cCommander;
private static JLabel averageCMCLabel;
private static SimpleDateFormat dateFormat;
private static RatioAdjustingSliderPanel adjustingSliderPanel;
@ -63,7 +63,7 @@ public class DeckGeneratorDialog {
c.insets = new Insets(5, 10, 0, 10);
c.gridx = 1;
c.gridy = 0;
String chosen = MageFrame.getPreferences().get("genDeckColor", "u");
String chosen = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORS, "u");
final ColorsChooser colorsChooser = new ColorsChooser(chosen);
mainPanel.add(colorsChooser, c);
@ -135,7 +135,7 @@ public class DeckGeneratorDialog {
c.ipadx = 30;
c.insets = new Insets(5, 10, 0, 10);
c.weightx = 0.90;
cbDeckSize = new JComboBox<>(new String[]{"40", "60"});
cbDeckSize = new JComboBox<>(new String[]{"40", "60", "100"});
cbDeckSize.setSelectedIndex(0);
cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT);
mainPanel.add(cbDeckSize, c);
@ -148,31 +148,33 @@ public class DeckGeneratorDialog {
JPanel jCheckBoxes = new JPanel(new FlowLayout(FlowLayout.LEFT));
// Singletons
boolean commanderEnabled = Boolean.parseBoolean(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COMMANDER, "false"));
cSingleton = new JCheckBox("Singleton", false);
cSingleton.setToolTipText("Allow only a single copy of each non-land card in your deck.");
String singletonEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SINGLETON, "false");
cSingleton.setSelected(Boolean.valueOf(singletonEnabled));
cSingleton.setSelected(Boolean.parseBoolean(singletonEnabled));
jCheckBoxes.add(cSingleton);
cSingleton.setEnabled(!commanderEnabled);
// Artifacts
cArtifacts = new JCheckBox("Artifacts", false);
cArtifacts.setToolTipText("Use artifacts and artifact creatures in your deck.");
String artifactEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, "false");
cArtifacts.setSelected(Boolean.valueOf(artifactEnabled));
cArtifacts.setSelected(Boolean.parseBoolean(artifactEnabled));
jCheckBoxes.add(cArtifacts);
// Non-basic lands
cNonBasicLands = new JCheckBox("Non-basic Lands", false);
cNonBasicLands.setToolTipText("Use non-basic lands in your deck (if applicable).");
String nonBasicEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS, "false");
cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled));
cNonBasicLands.setSelected(Boolean.parseBoolean(nonBasicEnabled));
jCheckBoxes.add(cNonBasicLands);
// Colorless mana
cColorless = new JCheckBox("Colorless mana", false);
cColorless.setToolTipText("Allow cards with colorless mana cost.");
String colorlessEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, "false");
cColorless.setSelected(Boolean.valueOf(colorlessEnabled));
cColorless.setSelected(Boolean.parseBoolean(colorlessEnabled));
jCheckBoxes.add(cColorless);
c.ipadx = 0;
c.gridx = 0;
@ -181,12 +183,28 @@ public class DeckGeneratorDialog {
c.gridwidth = 3;
mainPanel.add(jCheckBoxes, c);
// Commander
cCommander = new JCheckBox("Commander", false);
cCommander.setToolTipText("Add legendary creature as commander");
cCommander.setSelected(commanderEnabled);
jCheckBoxes.add(cCommander);
c.ipadx = 0;
c.gridx = 0;
c.gridy = 3;
c.weightx = 1;
c.gridwidth = 3;
mainPanel.add(jCheckBoxes, c);
cCommander.addItemListener(itemEvent -> {
// commander require singletone mode
cSingleton.setEnabled(!cCommander.isSelected());
});
// Create the advanced configuration panel
JPanel advancedPanel = createAdvancedPanel();
// Advanced checkbox (enable/disable advanced configuration)
cAdvanced = new JCheckBox("Advanced");
cAdvanced.setToolTipText("Enable advanced configuration options");
cAdvanced = new JCheckBox("Customize distribution");
cAdvanced.setToolTipText("Customize cards distribution due mana values and types");
cAdvanced.addItemListener(itemEvent -> {
boolean enable = cAdvanced.isSelected();
enableAdvancedPanel(enable);
@ -194,7 +212,7 @@ public class DeckGeneratorDialog {
// Advanced Checkbox
String advancedSavedValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, "false");
boolean advancedEnabled = Boolean.valueOf(advancedSavedValue);
boolean advancedEnabled = Boolean.parseBoolean(advancedSavedValue);
enableAdvancedPanel(advancedEnabled);
cAdvanced.setSelected(advancedEnabled);
c.gridy = 4;
@ -212,7 +230,7 @@ public class DeckGeneratorDialog {
colorsChooser.setEnabled(false);
selectedColors = (String) colorsChooser.getSelectedItem();
dlg.setVisible(false);
MageFrame.getPreferences().put("genDeckColor", selectedColors);
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORS, selectedColors);
});
btnCancel = new JButton("Cancel");
btnCancel.addActionListener(e -> {
@ -297,7 +315,7 @@ public class DeckGeneratorDialog {
c.gridwidth = 1;
c.gridy = 2;
btnReset = new JButton("Reset");
btnReset.setToolTipText("Reset advanced dialog to default values");
btnReset.setToolTipText("Reset custom cards distribution to default values");
btnReset.addActionListener(actionEvent -> {
cbCMC.setSelectedItem(DeckGeneratorCMC.Default);
adjustingSliderPanel.resetValues();
@ -374,6 +392,12 @@ public class DeckGeneratorDialog {
return selected;
}
public boolean isCommander() {
boolean selected = cCommander.isSelected();
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COMMANDER, Boolean.toString(selected));
return selected;
}
public boolean isAdvanced() {
boolean selected = cAdvanced.isSelected();
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, Boolean.toString(selected));

View file

@ -1,6 +1,6 @@
package mage.client.deck.generator;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.cards.decks.Deck;
@ -11,16 +11,17 @@ import mage.constants.ColoredManaSymbol;
import mage.constants.Rarity;
import mage.util.RandomUtil;
import mage.util.TournamentUtil;
import org.apache.log4j.Logger;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* @author Simown
* @author Simown, JayDi85
*/
public class DeckGeneratorPool
{
public class DeckGeneratorPool {
private static final Logger logger = Logger.getLogger(DeckGeneratorPool.class);
public static final int DEFAULT_CREATURE_PERCENTAGE = 38;
public static final int DEFAULT_NON_CREATURE_PERCENTAGE = 21;
@ -31,6 +32,7 @@ public class DeckGeneratorPool
private final List<DeckGeneratorCMC.CMC> poolCMCs;
private final int creatureCount;
private final int nonCreatureCount;
private final int commandersCount;
private final int landCount;
private final boolean isSingleton;
private final int deckSize;
@ -48,65 +50,83 @@ public class DeckGeneratorPool
/**
* Creates a card pool with specified criterea used when generating a deck.
*
* @param deckSize the size of the complete deck
* @param creaturePercentage what percentage of creatures to use when generating the deck.
* @param deckSize the size of the complete deck
* @param creaturePercentage what percentage of creatures to use when generating the deck.
* @param nonCreaturePercentage percentage of non-creatures to use when generating the deck.
* @param landPercentage percentage of lands to use when generating the deck.
* @param allowedColors which card colors are allowed in the generated deck.
* @param isSingleton if the deck only has 1 copy of each non-land card.
* @param colorlessAllowed if colourless mana symbols are allowed in costs in the deck.
* @param isAdvanced if the user has provided advanced options to generate the deck.
* @param deckGeneratorCMC the CMC curve to use for this deck
* @param landPercentage percentage of lands to use when generating the deck.
* @param allowedColors which card colors are allowed in the generated deck.
* @param isSingleton if the deck only has 1 copy of each non-land card.
* @param colorlessAllowed if colourless mana symbols are allowed in costs in the deck.
* @param isAdvanced if the user has provided advanced options to generate the deck.
* @param isCommander reserve commander card
* @param deckGeneratorCMC the CMC curve to use for this deck
*/
public DeckGeneratorPool(final int deckSize, final int creaturePercentage, final int nonCreaturePercentage, final int landPercentage,
final List<ColoredManaSymbol> allowedColors, boolean isSingleton, boolean colorlessAllowed, boolean isAdvanced, DeckGeneratorCMC deckGeneratorCMC)
{
final List<ColoredManaSymbol> allowedColors, boolean isSingleton, boolean colorlessAllowed, boolean isCommander,
boolean isAdvanced, DeckGeneratorCMC deckGeneratorCMC) {
this.deckSize = deckSize;
this.allowedColors = allowedColors;
this.isSingleton = isSingleton;
this.colorlessAllowed = colorlessAllowed;
this.commandersCount = isCommander ? 1 : 0;
this.isSingleton = isSingleton || isCommander; // commander must use singleton mode only
this.deck = new Deck();
// Advanced (CMC Slider panel and curve drop-down in the dialog)
if(isAdvanced) {
this.creatureCount = (int)Math.ceil((deckSize / 100.0) * creaturePercentage);
this.nonCreatureCount = (int)Math.ceil((deckSize / 100.0)* nonCreaturePercentage);
this.landCount = (int)Math.ceil((deckSize / 100.0)* landPercentage);
if(this.deckSize == 60) {
this.poolCMCs = deckGeneratorCMC.get60CardPoolCMC();
} else {
this.poolCMCs = deckGeneratorCMC.get40CardPoolCMC();
if (isAdvanced) {
this.creatureCount = (int) Math.ceil((deckSize / 100.0) * creaturePercentage);
this.nonCreatureCount = (int) Math.ceil((deckSize / 100.0) * nonCreaturePercentage);
this.landCount = (int) Math.ceil((deckSize / 100.0) * landPercentage);
switch (this.deckSize) {
case 100:
this.poolCMCs = deckGeneratorCMC.get100CardPoolCMC();
break;
case 60:
this.poolCMCs = deckGeneratorCMC.get60CardPoolCMC();
break;
case 40:
this.poolCMCs = deckGeneratorCMC.get40CardPoolCMC();
break;
default:
throw new IllegalArgumentException("Unsupported deck size: " + this.deckSize);
}
} else {
// Ignore the advanced group, just use defaults
this.creatureCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_CREATURE_PERCENTAGE);
this.nonCreatureCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_NON_CREATURE_PERCENTAGE);
this.landCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_LAND_PERCENTAGE);
if(this.deckSize == 60) {
this.poolCMCs = DeckGeneratorCMC.Default.get60CardPoolCMC();
} else {
this.poolCMCs = DeckGeneratorCMC.Default.get40CardPoolCMC();
this.creatureCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_CREATURE_PERCENTAGE);
this.nonCreatureCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_NON_CREATURE_PERCENTAGE);
this.landCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_LAND_PERCENTAGE);
switch (this.deckSize) {
case 100:
this.poolCMCs = DeckGeneratorCMC.Default.get100CardPoolCMC();
break;
case 60:
this.poolCMCs = DeckGeneratorCMC.Default.get60CardPoolCMC();
break;
case 40:
this.poolCMCs = DeckGeneratorCMC.Default.get40CardPoolCMC();
break;
default:
throw new IllegalArgumentException("Unsupported deck size: " + this.deckSize);
}
}
if(allowedColors.size() == 1) {
if (allowedColors.size() == 1) {
monoColored = true;
}
}
/**
* Adjusts the number of spell cards that should be in a converted mana cost (CMC) range, given the amount of cards total.
*
* @param cardsCount the number of total cards.
* @return a list of CMC ranges, with the amount of cards for each CMC range
*/
public List<DeckGeneratorCMC.CMC> getCMCsForSpellCount(int cardsCount) {
List<DeckGeneratorCMC.CMC> adjustedCMCs = new ArrayList<>(this.poolCMCs);
// For each CMC calculate how many spell cards are needed, given the total amount of cards
for(DeckGeneratorCMC.CMC deckCMC : adjustedCMCs) {
deckCMC.setAmount((int)Math.ceil(deckCMC.percentage * cardsCount));
for (DeckGeneratorCMC.CMC deckCMC : adjustedCMCs) {
deckCMC.setAmount((int) Math.ceil(deckCMC.percentage * cardsCount));
}
return adjustedCMCs;
}
@ -115,15 +135,15 @@ public class DeckGeneratorPool
* Verifies if the spell card supplied is valid for this pool of cards.
* Checks that there isn't too many copies of this card in the deck.
* Checks that the card fits the chosen colors for this pool.
*
* @param card the spell card
* @return if the spell card is valid for this pool.
*/
public boolean isValidSpellCard(Card card)
{
public boolean isValidSpellCard(Card card) {
int cardCount = getCardCount((card.getName()));
// Check it hasn't already got the maximum number of copies in a deck
if(cardCount < (isSingleton ? 1 : 4)) {
if(cardFitsChosenColors(card)) {
if (cardCount < (isSingleton ? 1 : 4)) {
if (cardFitsChosenColors(card)) {
return true;
}
}
@ -132,11 +152,11 @@ public class DeckGeneratorPool
/**
* Verifies if the non-basic land card supplied is valid for this pool of cards.
*
* @param card the non-basic land card
* @return if the land card generates the allowed colors for this pool.
*/
public boolean isValidLandCard(Card card)
{
public boolean isValidLandCard(Card card) {
int cardCount = getCardCount((card.getName()));
// No need to check if the land is valid for the colors chosen
// They are all filtered before searching for lands to include in the deck.
@ -146,60 +166,56 @@ public class DeckGeneratorPool
/**
* Adds a card to the pool and updates the count of this card.
*
* @param card the card to add.
*/
public void addCard(Card card)
{
public void addCard(Card card) {
Object cnt = cardCounts.get((card.getName()));
if(cnt == null)
if (cnt == null)
cardCounts.put(card.getName(), 0);
int existingCount = cardCounts.get((card.getName()));
cardCounts.put(card.getName(), existingCount+1);
cardCounts.put(card.getName(), existingCount + 1);
deckCards.add(card);
}
public void clearCards(boolean isClearReserve) {
cardCounts.clear();
deckCards.clear();
if (isClearReserve) {
reserveSpells.clear();
}
}
/**
* Adds a card to the reserve pool.
* Reserve pool is used when the deck generation fails to build a complete deck, or
* a partially complete deck (e.g. if there are no cards found that match a CMC)
* @param card the card to add
*
* @param card the card to add
* @param cardCMC the converted mana cost of the card
*/
public boolean tryAddReserve(Card card, int cardCMC) {
// Only cards with CMC < 7 and don't already exist in the deck
// can be added to our reserve pool as not to overwhelm the curve
// with high CMC cards and duplicates.
if(cardCMC < 7 && getCardCount(card.getName()) == 0) {
if (cardCMC < 7 && getCardCount(card.getName()) == 0) {
this.reserveSpells.add(card);
return true;
}
return false;
}
/**
* Checks if the mana symbols in the card all match the allowed colors for this pool.
* @param card the spell card to check.
* @return if all the mana symbols fit the chosen colors.
*/
private boolean cardFitsChosenColors(Card card) {
for (String symbol : card.getManaCostSymbols()) {
boolean found = false;
symbol = symbol.replace("{", "").replace("}", "");
if (isColoredManaSymbol(symbol)) {
for (ColoredManaSymbol allowed : allowedColors) {
if (symbol.contains(allowed.toString())) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
if (symbol.equals("C") && !colorlessAllowed) {
Set<String> needColors = allowedColors.stream().map(ColoredManaSymbol::toString).collect(Collectors.toSet());
List<ObjectColor> cardColors = card.getColorIdentity().getColors();
for (ObjectColor cardColor : cardColors) {
if (!needColors.contains(cardColor.toString())) {
return false;
}
}
if (cardColors.isEmpty() && !colorlessAllowed) {
return false;
}
return true;
}
@ -208,6 +224,7 @@ public class DeckGeneratorPool
* Calculates the percentage of colored mana symbols over all spell cards in the deck.
* Used to balance the generation of basic lands so the amount of lands matches the
* cards mana costs.
*
* @return a list of colored mana symbols and the percentage of symbols seen in cards mana costs.
*/
public Map<String, Double> calculateSpellColorPercentages() {
@ -221,14 +238,14 @@ public class DeckGeneratorPool
int totalCount = 0;
List<Card> fixedSpells = getFixedSpells();
for(Card spell: fixedSpells) {
for (Card spell : fixedSpells) {
for (String symbol : spell.getManaCostSymbols()) {
symbol = symbol.replace("{", "").replace("}", "");
if (isColoredManaSymbol(symbol)) {
for (ColoredManaSymbol allowed : allowedColors) {
if (symbol.contains(allowed.toString())) {
int cnt = colorCount.get(allowed.toString());
colorCount.put(allowed.toString(), cnt+1);
colorCount.put(allowed.toString(), cnt + 1);
totalCount++;
}
}
@ -236,7 +253,7 @@ public class DeckGeneratorPool
}
}
final Map<String, Double> percentages = new HashMap<>();
for(Map.Entry<String, Integer> singleCount: colorCount.entrySet()) {
for (Map.Entry<String, Integer> singleCount : colorCount.entrySet()) {
String color = singleCount.getKey();
int count = singleCount.getValue();
// Calculate the percentage this color has out of the total color counts
@ -248,20 +265,20 @@ public class DeckGeneratorPool
/**
* Calculates how many of each mana the non-basic lands produce.
*
* @param deckLands the non-basic lands which will be used in the deck.
* @return a mapping of colored mana symbol to the amount that can be produced.
*/
public Map<String,Integer> countManaProduced(List<Card> deckLands)
{
public Map<String, Integer> countManaProduced(List<Card> deckLands) {
Map<String, Integer> manaCounts = new HashMap<>();
for (final ColoredManaSymbol color : ColoredManaSymbol.values()) {
manaCounts.put(color.toString(), 0);
}
for(Card land: deckLands) {
for(Ability landAbility: land.getAbilities()) {
for (Card land : deckLands) {
for (Ability landAbility : land.getAbilities()) {
for (ColoredManaSymbol symbol : allowedColors) {
String abilityString = landAbility.getRule();
if(landTapsForAllowedColor(abilityString, symbol.toString())) {
if (landTapsForAllowedColor(abilityString, symbol.toString())) {
Integer count = manaCounts.get(symbol.toString());
manaCounts.put(symbol.toString(), count + 1);
}
@ -271,15 +288,17 @@ public class DeckGeneratorPool
return manaCounts;
}
/** Filter all the non-basic lands retrieved from the database.
/**
* Filter all the non-basic lands retrieved from the database.
*
* @param landCardsInfo information about all the cards.
* @return a list of cards that produce the allowed colors for this pool.
*/
public List<Card> filterLands(List<CardInfo> landCardsInfo) {
List<Card> matchingLandList = new ArrayList<>();
for(CardInfo landCardInfo: landCardsInfo) {
for (CardInfo landCardInfo : landCardsInfo) {
Card landCard = landCardInfo.createMockCard();
if(landProducesChosenColors(landCard)) {
if (landProducesChosenColors(landCard)) {
matchingLandList.add(landCard);
}
}
@ -288,11 +307,12 @@ public class DeckGeneratorPool
/**
* Returns the card name that represents the basic land for this color.
*
* @param symbolString the colored mana symbol.
* @return the name of a basic land card.
*/
public static String getBasicLandName(String symbolString) {
switch(symbolString) {
switch (symbolString) {
case "B":
return "Swamp";
case "G":
@ -311,25 +331,50 @@ public class DeckGeneratorPool
/**
* Returns a complete deck.
*
* @return the deck.
*/
public Deck getDeck() {
Set<Card> actualDeck = deck.getCards();
actualDeck.addAll(deckCards);
deck.getCards().clear();
deck.getSideboard().clear();
List<Card> useCards = new ArrayList<>(deckCards);
List<Card> useCommanders = new ArrayList<>();
// take random commanders
if (commandersCount > 0) {
List<Card> possibleCommanders = deckCards.stream()
.filter(this::isValidCommander)
.collect(Collectors.toList());
Card nextCommander = RandomUtil.randomFromCollection(possibleCommanders);
while (nextCommander != null && useCommanders.size() < commandersCount) {
useCards.remove(nextCommander);
useCommanders.add(nextCommander);
}
}
deck.getCards().addAll(useCards);
deck.getSideboard().addAll(useCommanders);
return deck;
}
/**
* Returns the number of creatures needed in this pool.
*
* @return the number of creatures.
*/
public int getCreatureCount() {
return creatureCount;
}
public int getCommandersCount() {
return commandersCount;
}
/**
* Returns the number of non-creatures needed in this pool.
*
* @return the number of non-creatures.
*/
public int getNonCreatureCount() {
@ -338,6 +383,7 @@ public class DeckGeneratorPool
/**
* Returns the number of lands (basic + non-basic) needed in this pool.
*
* @return the number of lands.
*/
public int getLandCount() {
@ -346,6 +392,7 @@ public class DeckGeneratorPool
/**
* Returns if this pool only uses one color.
*
* @return if this pool is monocolored.
*/
public boolean isMonoColoredDeck() {
@ -354,6 +401,7 @@ public class DeckGeneratorPool
/**
* Returns the size of the deck to generate from this pool.
*
* @return the deck size.
*/
public int getDeckSize() {
@ -364,20 +412,20 @@ public class DeckGeneratorPool
* Fixes undersized or oversized decks that have been generated.
* Removes random cards from an oversized deck until it is the correct size.
* Uses the reserve pool to fill up and undersized deck with cards.
*
* @return a fixed list of cards for this deck.
*/
private List<Card> getFixedSpells()
{
private List<Card> getFixedSpells() {
int spellSize = deckCards.size();
int nonLandSize = (deckSize - landCount);
// Less spells than needed
if(spellSize < nonLandSize) {
if (spellSize < nonLandSize) {
int spellsNeeded = nonLandSize-spellSize;
int spellsNeeded = nonLandSize - spellSize;
// If we haven't got enough spells in reserve to fulfil the amount we need, skip adding any.
if(reserveSpells.size() >= spellsNeeded) {
if (reserveSpells.size() >= spellsNeeded) {
List<Card> spellsToAdd = new ArrayList<>(spellsNeeded);
@ -398,16 +446,16 @@ public class DeckGeneratorPool
}
// More spells than needed
else if(spellSize > (deckSize - landCount)) {
int spellsRemoved = (spellSize)-(deckSize-landCount);
for(int i = 0; i < spellsRemoved; ++i) {
else if (spellSize > (deckSize - landCount)) {
int spellsRemoved = (spellSize) - (deckSize - landCount);
for (int i = 0; i < spellsRemoved; ++i) {
deckCards.remove(RandomUtil.nextInt(deckCards.size()));
}
}
// Check we have exactly the right amount of cards for a deck.
if(deckCards.size() != nonLandSize) {
throw new IllegalStateException("Not enough cards found to generate deck.");
if (deckCards.size() != nonLandSize) {
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors");
}
// Return the fixed amount
return deckCards;
@ -416,16 +464,18 @@ public class DeckGeneratorPool
/**
* Returns if this land taps for the given color.
* Basic string matching to check the ability adds one of the chosen mana when tapped.
*
* @param ability MockAbility of the land card
* @param symbol colored mana symbol.
* @param symbol colored mana symbol.
* @return if the ability is tapping to produce the mana the symbol represents.
*/
private boolean landTapsForAllowedColor(String ability, String symbol) {
private boolean landTapsForAllowedColor(String ability, String symbol) {
return ability.matches(".*Add \\{" + symbol + "\\}.");
}
/**
* Returns if this land will produce the chosen colors for this pool.
*
* @param card a non-basic land card.
* @return if this land card taps to produces the colors chosen.
*/
@ -434,32 +484,49 @@ public class DeckGeneratorPool
// and other Abilities so we have to do some basic string matching on land cards for now.
List<Ability> landAbilities = card.getAbilities();
int count = 0;
for(Ability ability : landAbilities) {
for (Ability ability : landAbilities) {
String abilityString = ability.getRule();
// Lands that tap to produce mana of the chosen colors
for(ColoredManaSymbol symbol : allowedColors) {
if(landTapsForAllowedColor(abilityString, symbol.toString())) {
for (ColoredManaSymbol symbol : allowedColors) {
if (landTapsForAllowedColor(abilityString, symbol.toString())) {
count++;
}
}
if(count > 1) {
if (count > 1) {
return true;
}
}
return false;
}
private boolean isValidCommander(Card card) {
// commander must be legendary creature
if (!card.isCreature() || !card.isLegendary()) {
return false;
}
// commander must have all chosen colors
for (ColoredManaSymbol symbol : allowedColors) {
if (!card.getColor().contains(new ObjectColor(symbol.toString()))) {
return false;
}
}
return true;
}
/**
* Returns if the symbol is a colored mana symbol.
*
* @param symbol the symbol to check.
* @return If it is a basic mana symbol or a hybrid mana symbol.
*/
private static boolean isColoredManaSymbol(String symbol) {
// Hybrid mana
if(symbol.contains("/")) {
if (symbol.contains("/")) {
return true;
}
for(ColoredManaSymbol c: ColoredManaSymbol.values()) {
for (ColoredManaSymbol c : ColoredManaSymbol.values()) {
if (symbol.charAt(0) == (c.toString().charAt(0))) {
return true;
}
@ -470,14 +537,15 @@ public class DeckGeneratorPool
/**
* Returns how many of this card is in the pool.
* If there are none in the pool it will initalise the card count.
*
* @param cardName the name of the card to check.
* @return the number of cards in the pool of this name.
*/
private int getCardCount(String cardName) {
Object cC = cardCounts.get((cardName));
if(cC == null)
if (cC == null)
cardCounts.put(cardName, 0);
return cardCounts.get((cardName));
return cardCounts.get((cardName));
}
@ -490,8 +558,8 @@ public class DeckGeneratorPool
* color of cards.
*
* @param useNonBasicLand
* @param criteria the criteria of the lands to search for in the database.
* @param basicLands information about the basic lands from the sets used.
* @param criteria the criteria of the lands to search for in the database.
* @param basicLands information about the basic lands from the sets used.
*/
protected static void generateLands(boolean useNonBasicLand, CardCriteria criteria, Map<String, List<CardInfo>> basicLands) {
DeckGeneratorPool genPool = DeckGenerator.genPool;
@ -541,45 +609,69 @@ public class DeckGeneratorPool
* non-creatures are retrieved separately to ensure the deck contains a
* reasonable mix of both.
*
* @param criteria the criteria to search for in the database.
* @param spellCount the number of spells that match the criteria needed in
* the deck.
* @param criteria the criteria to search for in the database.
* @param needCardsCount the number of spells that match the criteria needed in
* the deck.
* @param needCommandersCount make sure it contains commander creature (must be uses on first generateSpells only)
*/
protected static void generateSpells(CardCriteria criteria, int spellCount) {
protected static void generateSpells(CardCriteria criteria, int needCardsCount, int needCommandersCount) {
DeckGeneratorPool genPool = DeckGenerator.genPool;
if (needCommandersCount > 0 && !genPool.cardCounts.isEmpty()) {
throw new IllegalArgumentException("Wrong code usage: generateSpells with creatures and commanders must be called as first");
}
List<CardInfo> cardPool = CardRepository.instance.findCards(criteria);
int retrievedCount = cardPool.size();
List<DeckGeneratorCMC.CMC> deckCMCs = genPool.getCMCsForSpellCount(spellCount);
List<DeckGeneratorCMC.CMC> deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
int count = 0;
int validCommanders = 0;
int reservesAdded = 0;
boolean added;
if (retrievedCount > 0 && retrievedCount >= spellCount) {
if (cardPool.size() > 0 && cardPool.size() >= needCardsCount) {
int tries = 0;
while (count < spellCount) {
Card card = cardPool.get(RandomUtil.nextInt(retrievedCount)).createMockCard();
if (genPool.isValidSpellCard(card)) {
int cardCMC = card.getManaValue();
for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) {
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
int currentAmount = deckCMC.getAmount();
if (currentAmount > 0) {
deckCMC.setAmount(currentAmount - 1);
genPool.addCard(card.copy());
count++;
}
} else if (reservesAdded < (genPool.getDeckSize() / 2)) {
added = genPool.tryAddReserve(card, cardCMC);
if (added) {
reservesAdded++;
while (true) {
tries++;
// can't finish deck, stop and use reserved cards later
if (tries > DeckGenerator.MAX_TRIES) {
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors");
break;
}
// can finish deck - but make sure it has commander
if (count >= needCardsCount) {
if (validCommanders < needCommandersCount) {
// reset deck search from scratch (except reserved cards)
count = 0;
validCommanders = 0;
deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
genPool.clearCards(false);
continue;
}
break;
}
Card card = cardPool.get(RandomUtil.nextInt(cardPool.size())).createMockCard();
if (!genPool.isValidSpellCard(card)) {
continue;
}
int cardCMC = card.getManaValue();
for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) {
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
int currentAmount = deckCMC.getAmount();
if (currentAmount > 0) {
deckCMC.setAmount(currentAmount - 1);
genPool.addCard(card.copy());
count++;
// make sure it has compatible commanders
if (genPool.isValidCommander(card)) {
validCommanders++;
}
}
} else if (reservesAdded < (genPool.getDeckSize() / 2)) {
if (genPool.tryAddReserve(card, cardCMC)) {
reservesAdded++;
}
}
}
tries++;
if (tries > DeckGenerator.MAX_TRIES) {
// Break here, we'll fill in random missing ones later
break;
}
}
} else {
throw new IllegalStateException("Not enough cards to generate deck.");

View file

@ -780,7 +780,7 @@
</Component>
<Component class="javax.swing.JTextField" name="jTextFieldSearch">
<Properties>
<Property name="toolTipText" type="java.lang.String" value="Searches for card names and in the rule text of the card."/>
<Property name="toolTipText" type="java.lang.String" value="Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)"/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="chkNames">

View file

@ -1085,7 +1085,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene
}
});
jTextFieldSearch.setToolTipText("Searches for card names and in the rule text of the card.");
jTextFieldSearch.setToolTipText("Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)");
chkNames.setSelected(true);
chkNames.setText("Names");

View file

@ -110,7 +110,7 @@ public class DeckArea extends javax.swing.JPanel {
sideboardList.deselectAll();
for (CardView card : cards) {
CardView newCard = new CardView(card);
deckList.addCardView(newCard, true);
deckList.addCardView(newCard, card);
}
}
@ -150,7 +150,7 @@ public class DeckArea extends javax.swing.JPanel {
deckList.deselectAll();
for (CardView card : cards) {
CardView newCard = new CardView(card);
sideboardList.addCardView(newCard, true);
sideboardList.addCardView(newCard, card);
}
}

View file

@ -533,10 +533,10 @@
<Component class="mage.client.deckeditor.DeckLegalityPanel" name="deckLegalityDisplay">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[245, 155]"/>
<Dimension value="[245, 255]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[85, 155]"/>
<Dimension value="[85, 255]"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
</Properties>

View file

@ -121,12 +121,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
if (!SwingUtilities.isLeftMouseButton(e)) {
return;
}
List<String> cardNames = new ArrayList<>();
LegalityLabel label = (LegalityLabel) e.getComponent();
label.getValidator().getErrorsList().stream()
.map(DeckValidatorError::getCardName)
.filter(Objects::nonNull)
.forEach(cardNames::add);
List<String> 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())

View file

@ -5,6 +5,8 @@ import mage.cards.decks.Deck;
import mage.cards.decks.DeckValidator;
import mage.cards.mock.MockCard;
import mage.cards.mock.MockSplitCard;
import mage.client.components.BracketLegalityLabel;
import mage.client.components.EdhPowerLevelLegalityLabel;
import mage.client.components.LegalityLabel;
import mage.deck.*;
import org.apache.log4j.Logger;
@ -15,7 +17,7 @@ import java.util.stream.Stream;
/**
* @author Elandril
* @author Elandril, JayDi85
*/
public class DeckLegalityPanel extends javax.swing.JPanel {
@ -101,6 +103,14 @@ 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());
// only 3 buttons allowed for one line
this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_1));
this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_2_3));
this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_4_5));
addHidePanelButton();
revalidate();
@ -147,5 +157,4 @@ public class DeckLegalityPanel extends javax.swing.JPanel {
.map(LegalityLabel.class::cast)
.forEach(label -> label.validateDeck(deckToValidate));
}
}

View file

@ -180,7 +180,6 @@
</Component>
<Component class="javax.swing.JLabel" name="lblForestIcon">
<Properties>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[22, 20]"/>
</Property>

View file

@ -87,7 +87,7 @@ public class AddLandDialog extends MageDialog {
landSetNames.add(expansionInfo.getName());
}
if (landSetNames.isEmpty()) {
throw new IllegalArgumentException("No set with basic land was found");
throw new IllegalArgumentException("No set with basic land was found (possible memory problems, need client restart)");
}
if (landSetNames.size() > 1) {
landSetNames.add("<Random lands>");
@ -271,7 +271,6 @@ public class AddLandDialog extends MageDialog {
spnForest.setModel(new javax.swing.SpinnerNumberModel(0, 0, null, 1));
lblForestIcon.setToolTipText("");
lblForestIcon.setMaximumSize(new java.awt.Dimension(22, 20));
lblForestIcon.setMinimumSize(new java.awt.Dimension(22, 20));
lblForestIcon.setPreferredSize(new java.awt.Dimension(22, 20));
@ -477,6 +476,7 @@ public class AddLandDialog extends MageDialog {
}//GEN-LAST:event_btnSetFastSearchActionPerformed
private void autoAddLands() {
// suggest lands amount for deck without lands
int deckSize = ((Number) spnDeckSize.getValue()).intValue();
int[] lands = DeckBuildUtils.landCountSuggestion(deckSize, deck.getMaindeckCards());
spnPlains.setValue(lands[0]);

View file

@ -177,7 +177,7 @@ public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconi
super.show();
// auto-position on first usage
if (positioned) {
if (!positioned) {
showAndPositionWindow();
}
}
@ -190,9 +190,23 @@ public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconi
Point centered = SettingsManager.instance.getComponentPosition(width, height);
if (!positioned) {
// starting position
// auto-resize window, but keep it GUI friendly on too many cards (do not overlap a full screen)
int minWidth = CardInfoWindowDialog.this.getWidth();
int maxWidth = SettingsManager.instance.getScreenWidth() / 2;
int needWidth = CardInfoWindowDialog.this.cards.getPreferredSize().width;
needWidth = Math.max(needWidth, minWidth);
needWidth = Math.min(needWidth, maxWidth);
needWidth += GUISizeHelper.scrollBarSize; // more space, so no horizontal scrolls
int needHeight = CardInfoWindowDialog.this.getHeight(); // keep default height
CardInfoWindowDialog.this.setPreferredSize(new Dimension(needWidth, needHeight));
CardInfoWindowDialog.this.pack();
centered = SettingsManager.instance.getComponentPosition(needWidth, needHeight);
// little randomize to see multiple opened windows
int xPos = centered.x / 2 + RandomUtil.nextInt(50);
int yPos = centered.y / 2 + RandomUtil.nextInt(50);
CardInfoWindowDialog.this.setLocation(xPos, yPos);
show();
positioned = true;

View file

@ -5,6 +5,8 @@ import mage.client.util.AppUtil;
import mage.client.util.GUISizeHelper;
import mage.util.CardUtil;
import java.nio.charset.Charset;
/**
* GUI: error dialog with copyable error message
* // TODO: add game logs and data for game errors (client side info from GameView)
@ -28,7 +30,10 @@ public class ErrorDialog extends MageDialog {
// add additional info
String fullError = "Error type: " + fullTitle + "\n"
+ "\n"
+ "Client version: " + MageFrame.getInstance().getVersion().toString() + "\n"
+ "Java version: " + System.getProperty("java.version") + "\n"
+ "Default charset: " + Charset.defaultCharset() + "\n"
+ "\n"
+ errorText;
this.textError.setText(fullError);
@ -68,7 +73,8 @@ public class ErrorDialog extends MageDialog {
AppUtil.openUrlInSystemBrowser(url);
}
/** This method is called from within the constructor to
/**
* This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.

View file

@ -35,7 +35,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsLast">
<Properties>
<Property name="text" type="java.lang.String" value="Load from last time"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettingsLastActionPerformed"/>
@ -46,7 +45,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettings1">
<Properties>
<Property name="text" type="java.lang.String" value="Load from config 1"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettings1ActionPerformed"/>
@ -65,7 +63,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsDefault">
<Properties>
<Property name="text" type="java.lang.String" value="Load default settings"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettingsDefaultActionPerformed"/>
@ -391,7 +388,6 @@
<Component class="javax.swing.JLabel" name="lblSkillLevel">
<Properties>
<Property name="text" type="java.lang.String" value="Skill Level:"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="cbSkillLevel">

View file

@ -150,7 +150,6 @@ public class NewTableDialog extends MageDialog {
popupSaveSettings.add(menuSaveSettings2);
menuLoadSettingsLast.setText("Load from last time");
menuLoadSettingsLast.setToolTipText("");
menuLoadSettingsLast.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsLastActionPerformed(evt);
@ -160,7 +159,6 @@ public class NewTableDialog extends MageDialog {
popupLoadSettings.add(separator1);
menuLoadSettings1.setText("Load from config 1");
menuLoadSettings1.setToolTipText("");
menuLoadSettings1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettings1ActionPerformed(evt);
@ -178,7 +176,6 @@ public class NewTableDialog extends MageDialog {
popupLoadSettings.add(separator2);
menuLoadSettingsDefault.setText("Load default settings");
menuLoadSettingsDefault.setToolTipText("");
menuLoadSettingsDefault.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsDefaultActionPerformed(evt);
@ -228,7 +225,6 @@ public class NewTableDialog extends MageDialog {
});
lblSkillLevel.setText("Skill Level:");
lblSkillLevel.setToolTipText("");
cbSkillLevel.setToolTipText("<HTML>This option can be used to make it easier to find matches<br>\nwith opponents of the appropriate skill level.");
@ -598,7 +594,7 @@ public class NewTableDialog extends MageDialog {
private MatchOptions getMatchOptions() {
// current settings
GameTypeView gameType = (GameTypeView) cbGameType.getSelectedItem();
MatchOptions options = new MatchOptions(this.txtName.getText(), gameType.getName(), false, 2);
MatchOptions options = new MatchOptions(this.txtName.getText(), gameType.getName(), false);
options.getPlayerTypes().add(PlayerType.HUMAN);
for (TablePlayerPanel player : players) {
options.getPlayerTypes().add(player.getPlayerType());

View file

@ -35,7 +35,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsLast">
<Properties>
<Property name="text" type="java.lang.String" value="Load from last time"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettingsLastActionPerformed"/>
@ -46,7 +45,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettings1">
<Properties>
<Property name="text" type="java.lang.String" value="Load from config 1"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettings1ActionPerformed"/>
@ -65,7 +63,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsDefault">
<Properties>
<Property name="text" type="java.lang.String" value="Load default settings"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettingsDefaultActionPerformed"/>
@ -102,19 +99,21 @@
<Component id="pnlPacks" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="lblPacks" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblPlayer1" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="lblNbrPlayers" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnNumPlayers" min="-2" pref="46" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="lblNbrSeats" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnNumSeats" min="-2" pref="46" max="-2" attributes="0"/>
<Component id="lblNumWins" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnNumWins" min="-2" pref="50" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="chkSingleMultiplayerGame" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="lblPacks" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblPlayer1" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" pref="28" max="-2" attributes="0"/>
@ -122,10 +121,7 @@
<EmptySpace max="32767" attributes="0"/>
<Component id="lblNumRounds" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<EmptySpace max="32767" attributes="0"/>
<Component id="lblConstructionTime" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="lblConstructionTime" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
@ -203,11 +199,7 @@
<Component id="lbBufferTime" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbBufferTime" min="-2" pref="101" max="-2" attributes="1"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblNumWins" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnNumWins" min="-2" pref="50" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" pref="87" max="-2" attributes="0"/>
<Component id="chkRollbackTurnsAllowed" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbAllowSpectators" min="-2" max="-2" attributes="0"/>
@ -225,8 +217,6 @@
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
<Component id="lblNumWins" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="spnNumWins" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="chkRollbackTurnsAllowed" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbAllowSpectators" alignment="3" max="32767" attributes="0"/>
</Group>
@ -290,7 +280,7 @@
<Group type="102" attributes="0">
<Component id="pnlPacks" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pnlRandomPacks" pref="9" max="32767" attributes="0"/>
<Component id="pnlRandomPacks" pref="20" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Group type="103" alignment="1" groupAlignment="3" attributes="0">
@ -298,15 +288,17 @@
<Component id="lblNumRounds" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="lblNbrPlayers" alignment="1" max="32767" attributes="0"/>
<Component id="spnNumPlayers" alignment="1" pref="23" max="32767" attributes="1"/>
<Component id="pnlDraftOptions" alignment="1" pref="23" max="32767" attributes="1"/>
<Group type="103" alignment="1" groupAlignment="3" attributes="0">
<Component id="spnNumPlayers" alignment="3" max="32767" attributes="1"/>
<Component id="chkSingleMultiplayerGame" alignment="3" min="-2" max="-2" attributes="0"/>
<Group type="103" alignment="3" groupAlignment="3" attributes="0">
<Component id="lblNumWins" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="spnNumWins" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<Component id="pnlDraftOptions" alignment="1" pref="0" max="32767" attributes="1"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Component id="lblNbrSeats" alignment="1" max="32767" attributes="0"/>
<Component id="spnNumSeats" alignment="1" max="32767" attributes="1"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" pref="27" max="-2" attributes="0"/>
<Component id="lblPlayer1" min="-2" pref="25" max="-2" attributes="0"/>
</Group>
<Group type="103" groupAlignment="3" attributes="0">
@ -507,19 +499,18 @@
<Property name="text" type="java.lang.String" value="Players:"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="lblNbrSeats">
<Properties>
<Property name="text" type="java.lang.String" value="Seats:"/>
</Properties>
</Component>
<Component class="javax.swing.JSpinner" name="spnNumPlayers">
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="spnNumPlayersStateChanged"/>
</Events>
</Component>
<Component class="javax.swing.JSpinner" name="spnNumSeats">
<Component class="javax.swing.JCheckBox" name="chkSingleMultiplayerGame">
<Properties>
<Property name="text" type="java.lang.String" value="play as single game"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Allow to play single game with all tourney&apos;s players -- e.g. play one game after draft with 4 players"/>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="spnNumSeatsStateChanged"/>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="chkSingleMultiplayerGameItemStateChanged"/>
</Events>
</Component>
<Container class="javax.swing.JPanel" name="pnlDraftOptions">
@ -618,7 +609,7 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="pnlOtherPlayers" alignment="0" pref="8" max="32767" attributes="0"/>
<Component id="pnlOtherPlayers" alignment="0" pref="15" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
@ -663,7 +654,6 @@
<EtchetBorder/>
</Border>
</Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
@ -676,9 +666,6 @@
</Properties>
</Component>
<Component class="javax.swing.JSpinner" name="spnQuitRatio">
<Properties>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="lblMinimumRating">
<Properties>

View file

@ -40,6 +40,11 @@ public class NewTournamentDialog extends MageDialog {
private static final Logger logger = Logger.getLogger(NewTournamentDialog.class);
// it's ok to have 4 players at the screen, 6 is fine for big screens too
private static final int MAX_WORKABLE_PLAYERS_PER_GAME = 6;
private static final String CUBE_FROM_DECK_NAME = "Cube From Deck";
// temp settings on loading players list
private final List<PlayerType> prefPlayerTypes = new ArrayList<>();
private final List<Integer> prefPlayerSkills = new ArrayList<>();
@ -77,12 +82,15 @@ public class NewTournamentDialog extends MageDialog {
private int getCurrentNumPlayers() {
int res = (Integer) spnNumPlayers.getValue();
return res > 0 ? res : 2;
return Math.max(2, res);
}
private int getCurrentNumSeats() {
int res = (Integer) spnNumSeats.getValue();
return res > 0 ? res : 2;
if (chkSingleMultiplayerGame.isSelected()) {
return getCurrentNumPlayers();
} else {
return 2;
}
}
public void showDialog(UUID roomId) {
@ -159,9 +167,8 @@ public class NewTournamentDialog extends MageDialog {
lblPacks = new javax.swing.JLabel();
pnlPacks = new javax.swing.JPanel();
lblNbrPlayers = new javax.swing.JLabel();
lblNbrSeats = new javax.swing.JLabel();
spnNumPlayers = new javax.swing.JSpinner();
spnNumSeats = new javax.swing.JSpinner();
chkSingleMultiplayerGame = new javax.swing.JCheckBox();
pnlDraftOptions = new javax.swing.JPanel();
jLabel6 = new javax.swing.JLabel();
cbDraftTiming = new javax.swing.JComboBox();
@ -203,7 +210,6 @@ public class NewTournamentDialog extends MageDialog {
popupSaveSettings.add(menuSaveSettings2);
menuLoadSettingsLast.setText("Load from last time");
menuLoadSettingsLast.setToolTipText("");
menuLoadSettingsLast.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsLastActionPerformed(evt);
@ -213,7 +219,6 @@ public class NewTournamentDialog extends MageDialog {
popupLoadSettings.add(separator1);
menuLoadSettings1.setText("Load from config 1");
menuLoadSettings1.setToolTipText("");
menuLoadSettings1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettings1ActionPerformed(evt);
@ -231,7 +236,6 @@ public class NewTournamentDialog extends MageDialog {
popupLoadSettings.add(separator2);
menuLoadSettingsDefault.setText("Load default settings");
menuLoadSettingsDefault.setToolTipText("");
menuLoadSettingsDefault.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsDefaultActionPerformed(evt);
@ -324,17 +328,17 @@ public class NewTournamentDialog extends MageDialog {
lblNbrPlayers.setText("Players:");
lblNbrSeats.setText("Seats:");
spnNumPlayers.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
spnNumPlayersStateChanged(evt);
}
});
spnNumSeats.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
spnNumSeatsStateChanged(evt);
chkSingleMultiplayerGame.setText("play as single game");
chkSingleMultiplayerGame.setToolTipText("<HTML>Allow to play single game with all tourney's players -- e.g. play one game after draft with 4 players");
chkSingleMultiplayerGame.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
chkSingleMultiplayerGameItemStateChanged(evt);
}
});
@ -393,7 +397,7 @@ public class NewTournamentDialog extends MageDialog {
);
pnlPlayersLayout.setVerticalGroup(
pnlPlayersLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 8, Short.MAX_VALUE)
.addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 15, Short.MAX_VALUE)
);
btnOk.setText("Create");
@ -411,13 +415,10 @@ public class NewTournamentDialog extends MageDialog {
});
pnlRandomPacks.setBorder(javax.swing.BorderFactory.createEtchedBorder());
pnlRandomPacks.setToolTipText("");
pnlRandomPacks.setLayout(new javax.swing.BoxLayout(pnlRandomPacks, javax.swing.BoxLayout.Y_AXIS));
lblQuitRatio.setText("Allowed quit %");
spnQuitRatio.setToolTipText("");
lblMinimumRating.setText("Minimum rating:");
lblMinimumRating.setToolTipText("Players with rating less than this value can't join this table");
@ -466,25 +467,26 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(pnlPacks, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblPacks)
.addComponent(lblPlayer1)
.addGroup(layout.createSequentialGroup()
.addComponent(lblNbrPlayers)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(lblNbrSeats)
.addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnNumSeats, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(lblPacks)
.addComponent(lblPlayer1))
.addComponent(lblNumWins)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(chkSingleMultiplayerGame)))
.addGap(21, 21, 21)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(28, 28, 28)
.addComponent(pnlDraftOptions, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(lblNumRounds))
.addGroup(layout.createSequentialGroup()
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(lblConstructionTime)))
.addComponent(lblConstructionTime))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -551,11 +553,7 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(lbBufferTime)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cbBufferTime, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblNumWins)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGap(87, 87, 87)
.addComponent(chkRollbackTurnsAllowed)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cbAllowSpectators)))))
@ -567,8 +565,6 @@ public class NewTournamentDialog extends MageDialog {
.addGap(2, 2, 2)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblNumWins)
.addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(chkRollbackTurnsAllowed)
.addComponent(cbAllowSpectators, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
@ -618,20 +614,21 @@ public class NewTournamentDialog extends MageDialog {
.addGroup(layout.createSequentialGroup()
.addComponent(pnlPacks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pnlRandomPacks, javax.swing.GroupLayout.DEFAULT_SIZE, 9, Short.MAX_VALUE)
.addComponent(pnlRandomPacks, javax.swing.GroupLayout.DEFAULT_SIZE, 20, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(spnNumRounds, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblNumRounds))
.addComponent(lblNbrPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(spnNumPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 23, Short.MAX_VALUE)
.addComponent(pnlDraftOptions, javax.swing.GroupLayout.PREFERRED_SIZE, 23, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(lblNbrSeats, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(spnNumSeats))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(spnNumPlayers)
.addComponent(chkSingleMultiplayerGame)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblNumWins)
.addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(pnlDraftOptions, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
.addGap(27, 27, 27)
.addComponent(lblPlayer1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -658,7 +655,7 @@ public class NewTournamentDialog extends MageDialog {
}// </editor-fold>//GEN-END:initComponents
private void cbTournamentTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTournamentTypeActionPerformed
prepareTourneyView(false, prepareVersionStr(-1, false), getCurrentNumPlayers(), getCurrentNumSeats());
loadTourneyView(false, prepareVersionStr(-1, false), getCurrentNumPlayers(), chkSingleMultiplayerGame.isSelected());
jumpstartPacksFilename = "";
if (cbTournamentType.getSelectedItem().toString().matches(".*Jumpstart.*Custom.*")) {
@ -700,6 +697,38 @@ public class NewTournamentDialog extends MageDialog {
}
}
// players count limited by GUI size
// draft bots are loses and hide at the start, so count only human and AI
if (tOptions.getMatchOptions().isSingleGameTourney()) {
int workablePlayers = tOptions.getPlayerTypes().stream()
.mapToInt(p -> p.isWorkablePlayer() ? 1 : 0)
.sum();
if (workablePlayers > MAX_WORKABLE_PLAYERS_PER_GAME) {
JOptionPane.showMessageDialog(
MageFrame.getDesktop(),
String.format("Warning, in single game mode you can choose %d human/ai players but selected %d", MAX_WORKABLE_PLAYERS_PER_GAME, workablePlayers),
"Warning",
JOptionPane.WARNING_MESSAGE
);
return;
}
}
// cube from deck uses weird choose logic from combobox select, so players can forget or cancel it
if (tournamentType.isDraft()
&& tOptions.getLimitedOptions().getDraftCubeName() != null
&& tOptions.getLimitedOptions().getDraftCubeName().contains(CUBE_FROM_DECK_NAME)) {
if (tOptions.getLimitedOptions().getCubeFromDeck() == null || tOptions.getLimitedOptions().getCubeFromDeck().getCards().isEmpty()) {
JOptionPane.showMessageDialog(
MageFrame.getDesktop(),
"Found empty cube. You must choose Cube From Deck again and select existing deck file.",
"Warning",
JOptionPane.WARNING_MESSAGE
);
return;
}
}
// save last settings
onSaveSettings(0, tOptions);
@ -747,44 +776,29 @@ public class NewTournamentDialog extends MageDialog {
doClose();
}//GEN-LAST:event_btnCancelActionPerformed
private void updateNumSeats() {
int numSeats = (Integer) this.spnNumSeats.getValue();
private void applyNewPlayersCount() {
// make sure players count is compatible
int numPlayers = getCurrentNumPlayers();
int compatiblePlayers = getCompatiblePlayersCount(numPlayers);
if (numPlayers != compatiblePlayers) {
numPlayers = compatiblePlayers;
spnNumPlayers.setValue(numPlayers);
}
createPlayers(numPlayers - 1);
if (numSeats > 2) {
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
if (numSeats >= tournamentType.getMinPlayers()) {
createPlayers(numSeats - 1);
spnNumPlayers.setValue(numSeats);
} else {
numSeats = tournamentType.getMinPlayers();
createPlayers(numSeats - 1);
spnNumPlayers.setValue(numSeats);
spnNumSeats.setValue(numSeats);
}
// make sure wins is compatible
// is's can be a too long match for 2+ wins in 4+ game
if (chkSingleMultiplayerGame.isSelected()) {
spnNumWins.setValue(1);
}
}
private void spnNumPlayersStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumPlayersStateChanged
int numPlayers = getCurrentNumPlayers();
createPlayers(numPlayers - 1);
int numSeats = (Integer) this.spnNumSeats.getValue();
if (numSeats > 2 && numPlayers != numSeats) {
updateNumSeats();
}
applyNewPlayersCount();
}//GEN-LAST:event_spnNumPlayersStateChanged
private void spnNumSeatsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumSeatsStateChanged
int numSeats = (Integer) this.spnNumSeats.getValue();
this.spnNumPlayers.setEnabled(numSeats <= 2);
updateNumSeats();
}//GEN-LAST:event_spnNumSeatsStateChanged
private void spnNumWinsnumPlayersChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumWinsnumPlayersChanged
int numSeats = getCurrentNumSeats();
if (numSeats > 2) {
spnNumWins.setValue(1);
}
applyNewPlayersCount();
}//GEN-LAST:event_spnNumWinsnumPlayersChanged
private JFileChooser fcSelectDeck = null;
@ -829,7 +843,7 @@ public class NewTournamentDialog extends MageDialog {
private void cbDraftCubeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDraftCubeActionPerformed
cubeFromDeckFilename = "";
if (cbDraftCube.getSelectedItem().toString().equals("Cube From Deck")) {
if (cbDraftCube.getSelectedItem().toString().startsWith(CUBE_FROM_DECK_NAME)) {
cubeFromDeckFilename = playerLoadDeck();
}
}//GEN-LAST:event_cbDraftCubeActionPerformed
@ -886,27 +900,52 @@ public class NewTournamentDialog extends MageDialog {
customOptions.showDialog();
}//GEN-LAST:event_btnCustomOptionsActionPerformed
private void chkSingleMultiplayerGameItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkSingleMultiplayerGameItemStateChanged
// for checkboxes - it's important to use ItemStateChanged instead stateChanged
// (the last one will raise on moving mouse over, not on checkbox state change only)
applyNewPlayersCount();
}//GEN-LAST:event_chkSingleMultiplayerGameItemStateChanged
private void setGameOptions() {
createPlayers(getCurrentNumPlayers() - 1);
}
private void prepareTourneyView(boolean loadPlayerSettings, String versionStr, int numPlayers, int numSeats) {
private int getCompatiblePlayersCount(int count) {
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
if (tournamentType == null) {
return count;
}
int compatibleMin = tournamentType.getMinPlayers();
int compatibleMax = tournamentType.getMaxPlayers();
if (chkSingleMultiplayerGame.isSelected()) {
// user can select any amount of draft bots, real amount checks on submit
//compatibleMax = Math.min(MAX_PLAYERS_PER_GAME, compatibleMax);
}
int compatibleCount = count;
compatibleCount = Math.max(compatibleCount, compatibleMin);
compatibleCount = Math.min(compatibleCount, compatibleMax);
return compatibleCount;
}
private void loadTourneyView(boolean loadPlayerSettings, String versionStr, int numPlayers, boolean isSingleMultiplayerGame) {
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
activatePanelElements(tournamentType);
if (numPlayers < tournamentType.getMinPlayers() || numPlayers > tournamentType.getMaxPlayers()) {
numPlayers = tournamentType.getMinPlayers();
}
numPlayers = getCompatiblePlayersCount(numPlayers);
this.spnNumPlayers.setModel(new SpinnerNumberModel(numPlayers, tournamentType.getMinPlayers(), tournamentType.getMaxPlayers(), 1));
this.spnNumPlayers.setEnabled(tournamentType.getMinPlayers() != tournamentType.getMaxPlayers());
this.spnNumSeats.setModel(new SpinnerNumberModel(2, 2, tournamentType.getMaxPlayers(), 1));
// manual call change events to apply players/seats restrictions and create miss panels
// manual call change events to apply players restrictions and create miss panels before load player related settings
// TODO: refactor to use isLoading and restrictions from a code instead restrictions from a component
this.chkSingleMultiplayerGame.setSelected(isSingleMultiplayerGame);
this.spnNumPlayers.setValue(numPlayers);
spnNumPlayersStateChanged(null);
this.spnNumSeats.setValue(numSeats);
spnNumSeatsStateChanged(null);
// wins must be loaded after chkSingleMultiplayerGame change, cause it can be limited by 1
int numWins = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_OF_WINS + versionStr, "2"));
this.spnNumWins.setValue(numWins);
if (loadPlayerSettings) {
// load player data
@ -1265,8 +1304,7 @@ public class NewTournamentDialog extends MageDialog {
private TournamentOptions getTournamentOptions() {
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
int numSeats = (Integer) this.spnNumSeats.getValue();
TournamentOptions tOptions = new TournamentOptions(this.txtName.getText(), "", numSeats);
TournamentOptions tOptions = new TournamentOptions(this.txtName.getText(), "", chkSingleMultiplayerGame.isSelected());
tOptions.setTournamentType(tournamentType.getName());
tOptions.setPassword(txtPassword.getText());
tOptions.getPlayerTypes().add(PlayerType.HUMAN);
@ -1429,7 +1467,6 @@ public class NewTournamentDialog extends MageDialog {
break;
}
}
this.spnNumWins.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_OF_WINS + versionStr, "2")));
this.spnQuitRatio.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_QUIT_RATIO + versionStr, "100")));
this.spnMinimumRating.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_MINIMUM_RATING + versionStr, "0")));
@ -1437,7 +1474,6 @@ public class NewTournamentDialog extends MageDialog {
activatePanelElements(tournamentType);
int defaultNumberPlayers = 2;
int defaultNumberSeats = 2;
if (tournamentType.isLimited()) {
if (tournamentType.isDraft()) {
defaultNumberPlayers = 4;
@ -1468,8 +1504,8 @@ public class NewTournamentDialog extends MageDialog {
}
int numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_PLAYERS + versionStr, String.valueOf(defaultNumberPlayers)));
int numSeats = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_SEATS + versionStr, String.valueOf(defaultNumberSeats)));
prepareTourneyView(true, versionStr, numPlayers, numSeats);
boolean isSingleMultiplayerGame = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_SINGLE_MULTIPLAYER_GAME + versionStr, "No").equals("Yes");
loadTourneyView(true, versionStr, numPlayers, isSingleMultiplayerGame);
this.customOptions.onLoadSettings(version);
}
@ -1517,7 +1553,7 @@ public class NewTournamentDialog extends MageDialog {
}
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_PLAYERS + versionStr, Integer.toString(tOptions.getPlayerTypes().size()));
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_SEATS + versionStr, Integer.toString((Integer) this.spnNumSeats.getValue()));
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_SINGLE_MULTIPLAYER_GAME + versionStr, (tOptions.getMatchOptions().isSingleGameTourney() ? "Yes" : "No"));
// save player data
// player type
@ -1558,6 +1594,7 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JComboBox cbTournamentType;
private javax.swing.JCheckBox chkRated;
private javax.swing.JCheckBox chkRollbackTurnsAllowed;
private javax.swing.JCheckBox chkSingleMultiplayerGame;
private javax.swing.JLabel jLabel6;
private javax.swing.JLabel lbBufferTime;
private javax.swing.JLabel lbDeckType;
@ -1569,7 +1606,6 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JLabel lblMinimumRating;
private javax.swing.JLabel lblName;
private javax.swing.JLabel lblNbrPlayers;
private javax.swing.JLabel lblNbrSeats;
private javax.swing.JLabel lblNumRounds;
private javax.swing.JLabel lblNumWins;
private javax.swing.JLabel lblPacks;
@ -1598,7 +1634,6 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JSpinner spnMinimumRating;
private javax.swing.JSpinner spnNumPlayers;
private javax.swing.JSpinner spnNumRounds;
private javax.swing.JSpinner spnNumSeats;
private javax.swing.JSpinner spnNumWins;
private javax.swing.JSpinner spnQuitRatio;
private javax.swing.JTextField txtName;

View file

@ -1,5 +1,7 @@
package mage.client.dialog;
import mage.client.cards.BigCard;
import mage.client.components.MageTextArea;
import mage.constants.ColoredManaSymbol;
import mage.util.MultiAmountMessage;
@ -11,20 +13,24 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Game GUI: dialog to distribute values between multiple items
* (used for some cards and add lands in cheat menu, search by MultiAmountMessage)
*
* @author weirddan455
* @author weirddan455, JayDi85
*/
public class PickMultiNumberDialog extends MageDialog {
private boolean cancel;
private PickMultiNumberCallback callback = null;
private UUID gameId = null;
private BigCard bigCard = null;
private List<JLabel> labelList = null;
private List<MageTextArea> infoList = null;
private List<JSpinner> spinnerList = null;
public PickMultiNumberDialog() {
@ -36,6 +42,11 @@ public class PickMultiNumberDialog extends MageDialog {
void onChoiceDone();
}
public void init(UUID gameId, BigCard bigCard) {
this.gameId = gameId;
this.bigCard = bigCard;
}
public void showDialog(List<MultiAmountMessage> messages, int min, int max, Map<String, Serializable> options, PickMultiNumberCallback callback) {
this.cancel = false;
this.callback = callback;
@ -46,9 +57,10 @@ public class PickMultiNumberDialog extends MageDialog {
boolean canCancel = options.get("canCancel") != null && (boolean) options.get("canCancel");
btnCancel.setVisible(canCancel);
if (labelList != null) {
for (JLabel label : labelList) {
jPanel1.remove(label);
// clean
if (infoList != null) {
for (MageTextArea info : infoList) {
jPanel1.remove(info);
}
}
if (spinnerList != null) {
@ -56,14 +68,15 @@ public class PickMultiNumberDialog extends MageDialog {
jPanel1.remove(spinner);
}
}
int size = messages.size();
labelList = new ArrayList<>(size);
infoList = new ArrayList<>(size);
spinnerList = new ArrayList<>(size);
jPanel1.setLayout(new GridBagLayout());
GridBagConstraints labelC = new GridBagConstraints();
GridBagConstraints spinnerC = new GridBagConstraints();
for (int i = 0; i < size; i++) {
JLabel label = new JLabel();
MageTextArea info = new MageTextArea();
info.enableTextLabelMode();
info.setGameData(this.gameId, this.bigCard);
// mana mode
String manaText = null;
@ -86,24 +99,23 @@ public class PickMultiNumberDialog extends MageDialog {
break;
}
if (manaText != null) {
label.setText("<html>" + manaText);
Image image = ManaSymbols.getSizedManaSymbol(input);
if (image != null) {
label.setIcon(new ImageIcon(image));
}
// mana mode
info.setText("{" + input + "}" + "&nbsp;" + manaText);
} else {
// text mode
label.setText("<html>" + ManaSymbols.replaceSymbolsWithHTML(input, ManaSymbols.Type.DIALOG));
info.setText(input);
}
labelC.weightx = 0.5;
labelC.gridx = 0;
labelC.gridy = i;
jPanel1.add(label, labelC);
labelList.add(label);
GridBagConstraints infoC = new GridBagConstraints();
infoC.weightx = 0.5;
infoC.gridx = 0;
infoC.gridy = i;
jPanel1.add(info, infoC);
infoList.add(info);
JSpinner spinner = new JSpinner();
spinner.setModel(new SpinnerNumberModel(messages.get(i).defaultValue, messages.get(i).min, messages.get(i).max, 1));
GridBagConstraints spinnerC = new GridBagConstraints();
spinnerC.weightx = 0.5;
spinnerC.gridx = 1;
spinnerC.gridy = i;

View file

@ -2010,7 +2010,6 @@
<Properties>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="GUI color style:"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="horizontalTextPosition" type="int" value="10"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[110, 16]"/>
@ -2184,7 +2183,7 @@
<Component id="panelCardStyles" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="panelCardImages" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="306" max="32767" attributes="0"/>
<EmptySpace pref="309" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -2225,7 +2224,7 @@
<Component id="cbPreferredImageLanguage" min="-2" pref="153" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="480" max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
@ -2284,7 +2283,6 @@
</Component>
<Component class="javax.swing.JComboBox" name="cbPreferredImageLanguage">
<Properties>
<Property name="maximumRowCount" type="int" value="20"/>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="4">
<StringItem index="0" value="Item 1"/>
@ -2319,15 +2317,48 @@
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="1"/>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cbCardRenderIconsForAbilities" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderIconsForPlayable" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator1" min="-2" pref="775" max="-2" attributes="0"/>
<Component id="cbCardRenderShowReminderText" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderHideSetSymbol" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderShowAbilityTextOverlay" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Component id="labelRenderMode" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbCardRenderImageFallback" min="-2" pref="122" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="labelRenderMode" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderImageFallback" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderIconsForAbilities" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderIconsForPlayable" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="jSeparator1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderShowReminderText" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderHideSetSymbol" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderShowAbilityTextOverlay" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="cbCardRenderImageFallback">
<Properties>
<Property name="text" type="java.lang.String" value="Render mode: MTGO style (off) or IMAGE style (on)"/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="cbCardRenderIconsForAbilities">
<Properties>
<Property name="text" type="java.lang.String" value="Enable card icons for abilities (example: flying, deathtouch)"/>
@ -2355,6 +2386,28 @@
<Property name="text" type="java.lang.String" value="Show ability text as overlay in big card view"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="labelRenderMode">
<Properties>
<Property name="text" type="java.lang.String" value="Render Mode:"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Image - Renders card image with text overlay&lt;br&gt; MTGO - Renders card frame around card art&lt;br&gt; Forced M15 - Renders all cards in the modern frame&lt;br&gt; Forced Retro - Renders all cards in the retro frame"/>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="cbCardRenderImageFallback">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="4">
<StringItem index="0" value="Item 1"/>
<StringItem index="1" value="Item 2"/>
<StringItem index="2" value="Item 3"/>
<StringItem index="3" value="Item 4"/>
</StringArray>
</Property>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Image - Renders card image with text overlay&lt;br&gt; MTGO - Renders card frame around card art&lt;br&gt; Forced M15 - Renders all cards in the MTGO style with the modern frame&lt;br&gt; Forced Retro - Renders all cards in the MTGO style with the retro frame"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
@ -2616,29 +2669,25 @@
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="STOP skips on declare attackers if attackers are available"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="actionCommand" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="cbStopBlockWithAny">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="STOP skips on declare blockers if ANY blockers are available"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="text" type="java.lang.String" value="STOP skips when attacked and on declare blockers if ANY blockers are available"/>
<Property name="actionCommand" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="cbStopBlockWithZero">
<Properties>
<Property name="text" type="java.lang.String" value="STOP skips on declare blockers if ZERO blockers are available"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="text" type="java.lang.String" value="STOP skips when attacked if ZERO blockers are available"/>
<Property name="actionCommand" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="cbStopOnNewStackObjects">
<Properties>
<Property name="text" type="java.lang.String" value="Skip to STACK resolved (F10): stop on new objects added (on) or stop until empty (off)"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="text" type="java.lang.String" value="Skip to STACK resolved (F10): stop on new objects added (on) or stop when stack empty (off)"/>
<Property name="actionCommand" type="java.lang.String" value=""/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 25]"/>
@ -2648,14 +2697,12 @@
<Component class="javax.swing.JCheckBox" name="cbStopOnAllMain">
<Properties>
<Property name="text" type="java.lang.String" value="Skip to MAIN step (F7): stop on any main steps (on) or stop on your main step (off)"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="actionCommand" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="cbStopOnAllEnd">
<Properties>
<Property name="text" type="java.lang.String" value="Skip to END step (F5): stop on any end steps (on) or stop on opponents end step (off)"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="actionCommand" type="java.lang.String" value=""/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 25]"/>
@ -3128,7 +3175,6 @@
<Component class="javax.swing.JLabel" name="jLabel16">
<Properties>
<Property name="text" type="java.lang.String" value="Playing from folder:"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JTextField" name="txtBattlefieldIBGMPath">
@ -3178,7 +3224,7 @@
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Proxy for server connection and images download">
<TitledBorder title="Proxy for server connection and images download (DO NOT SUPPORTED)">
<Border PropertyName="innerBorder" info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>

View file

@ -4,10 +4,7 @@ import mage.client.MageFrame;
import mage.client.SessionHandler;
import mage.client.components.KeyBindButton;
import mage.client.themes.ThemeType;
import mage.client.util.CardLanguage;
import mage.client.util.ClientDefaultSettings;
import mage.client.util.GUISizeHelper;
import mage.client.util.ImageHelper;
import mage.client.util.*;
import mage.client.util.audio.MusicPlayer;
import mage.client.util.gui.BufferedImageBuilder;
import mage.client.util.gui.GuiDisplayUtil;
@ -37,7 +34,6 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD;
import static mage.constants.Constants.*;
@ -55,6 +51,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
private static PreferencesDialog instance; // shared dialog instance
public static final boolean NETWORK_ENABLE_PROXY_SUPPORT = false; // TODO: delete proxy at all after few releases, 2025-02-09
// WARNING, do not change const values - it must be same for compatibility with user's saved settings
public static final String KEY_SHOW_TOOLTIPS_DELAY = "showTooltipsDelay";
public static final String KEY_SHOW_CARD_NAMES = "showCardNames";
@ -87,7 +85,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip";
public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage";
public static final String KEY_CARD_RENDERING_IMAGE_MODE = "cardRenderingFallback";
public static final String KEY_CARD_RENDERING_IMAGE_MODE = "cardRenderingMode";
public static final String KEY_CARD_RENDERING_ICONS_FOR_ABILITIES = "cardRenderingIconsForAbilities";
public static final String KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE = "cardRenderingIconsForPlayable";
public static final String KEY_CARD_RENDERING_REMINDER_TEXT = "cardRenderingReminderText";
@ -246,7 +244,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_NEW_TOURNAMENT_PACKS_DRAFT = "newTournamentPacksDraft";
public static final String KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT = "newTournamentPacksRandomDraft";
public static final String KEY_NEW_TOURNAMENT_NUMBER_PLAYERS = "newTournamentNumberPlayers";
public static final String KEY_NEW_TOURNAMENT_NUMBER_SEATS = "newTournamentNumberSeats";
public static final String KEY_NEW_TOURNAMENT_SINGLE_MULTIPLAYER_GAME = "newTournamentSingleMultiplayerGame";
public static final String KEY_NEW_TOURNAMENT_PLAYER_TYPES = "newTournamentPlayerTypes";
public static final String KEY_NEW_TOURNAMENT_PLAYER_SKILLS = "newTournamentPlayerSkills";
public static final String KEY_NEW_TOURNAMENT_DRAFT_TIMING = "newTournamentDraftTiming";
@ -265,12 +263,14 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_AUTO_TARGET_LEVEL = "autoTargetLevel";
// pref setting for deck generator
public static final String KEY_NEW_DECK_GENERATOR_COLORS = "newDeckGeneratorDeckColors";
public static final String KEY_NEW_DECK_GENERATOR_DECK_SIZE = "newDeckGeneratorDeckSize";
public static final String KEY_NEW_DECK_GENERATOR_SET = "newDeckGeneratorSet";
public static final String KEY_NEW_DECK_GENERATOR_SINGLETON = "newDeckGeneratorSingleton";
public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts";
public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands";
public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless";
public static final String KEY_NEW_DECK_GENERATOR_COMMANDER = "newDeckGeneratorCommander";
public static final String KEY_NEW_DECK_GENERATOR_ADVANCED = "newDeckGeneratorAdvanced";
public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage";
public static final String KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE = "newDeckGeneratorNonCreaturePercentage";
@ -758,6 +758,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
}
cbPreferredImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList()));
cbCardRenderImageFallback.setModel(new DefaultComboBoxModel<>(CardRenderMode.toList()));
}
private void createSizeSetting(Integer position, String key, Integer defaultValue, boolean useExample, String name, String hint) {
@ -959,13 +960,14 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbPreferredImageLanguage = new javax.swing.JComboBox<>();
labelPreferredImageLanguage = new javax.swing.JLabel();
panelCardStyles = new javax.swing.JPanel();
cbCardRenderImageFallback = new javax.swing.JCheckBox();
cbCardRenderIconsForAbilities = new javax.swing.JCheckBox();
cbCardRenderIconsForPlayable = new javax.swing.JCheckBox();
jSeparator1 = new javax.swing.JSeparator();
cbCardRenderShowReminderText = new javax.swing.JCheckBox();
cbCardRenderHideSetSymbol = new javax.swing.JCheckBox();
cbCardRenderShowAbilityTextOverlay = new javax.swing.JCheckBox();
labelRenderMode = new javax.swing.JLabel();
cbCardRenderImageFallback = new javax.swing.JComboBox<>();
tabPhases = new javax.swing.JPanel();
jLabelHeadLine = new javax.swing.JLabel();
jLabelYourTurn = new javax.swing.JLabel();
@ -2108,7 +2110,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
lbSelectLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
lbSelectLabel.setText("GUI color style:");
lbSelectLabel.setToolTipText("");
lbSelectLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
lbSelectLabel.setPreferredSize(new java.awt.Dimension(110, 16));
lbSelectLabel.setVerticalTextPosition(javax.swing.SwingConstants.TOP);
@ -2276,7 +2277,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
}
});
cbPreferredImageLanguage.setMaximumRowCount(20);
cbPreferredImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
labelPreferredImageLanguage.setText("Default images language:");
@ -2302,7 +2302,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(labelPreferredImageLanguage)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
.add(0, 480, Short.MAX_VALUE)))
.add(0, 0, Short.MAX_VALUE)))
.addContainerGap())
);
panelCardImagesLayout.setVerticalGroup(
@ -2322,26 +2322,59 @@ public class PreferencesDialog extends javax.swing.JDialog {
);
panelCardStyles.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)"));
panelCardStyles.setLayout(new javax.swing.BoxLayout(panelCardStyles, javax.swing.BoxLayout.Y_AXIS));
cbCardRenderImageFallback.setText("Render mode: MTGO style (off) or IMAGE style (on)");
panelCardStyles.add(cbCardRenderImageFallback);
cbCardRenderIconsForAbilities.setText("Enable card icons for abilities (example: flying, deathtouch)");
panelCardStyles.add(cbCardRenderIconsForAbilities);
cbCardRenderIconsForPlayable.setText("Enable card icons for playable abilities (example: if you can activate card's ability then show a special icon in the corner)");
panelCardStyles.add(cbCardRenderIconsForPlayable);
panelCardStyles.add(jSeparator1);
cbCardRenderShowReminderText.setText("Show reminder text in rendered card textboxes");
panelCardStyles.add(cbCardRenderShowReminderText);
cbCardRenderHideSetSymbol.setText("Hide set symbols on cards (more space on the type line for card types)");
panelCardStyles.add(cbCardRenderHideSetSymbol);
cbCardRenderShowAbilityTextOverlay.setText("Show ability text as overlay in big card view");
panelCardStyles.add(cbCardRenderShowAbilityTextOverlay);
labelRenderMode.setText("Render Mode:");
labelRenderMode.setToolTipText("<HTML>Image - Renders card image with text overlay<br> MTGO - Renders card frame around card art<br> Forced M15 - Renders all cards in the modern frame<br> Forced Retro - Renders all cards in the retro frame");
cbCardRenderImageFallback.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
cbCardRenderImageFallback.setToolTipText("<HTML>Image - Renders card image with text overlay<br> MTGO - Renders card frame around card art<br> Forced M15 - Renders all cards in the MTGO style with the modern frame<br> Forced Retro - Renders all cards in the MTGO style with the retro frame");
org.jdesktop.layout.GroupLayout panelCardStylesLayout = new org.jdesktop.layout.GroupLayout(panelCardStyles);
panelCardStyles.setLayout(panelCardStylesLayout);
panelCardStylesLayout.setHorizontalGroup(
panelCardStylesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(cbCardRenderIconsForAbilities)
.add(cbCardRenderIconsForPlayable)
.add(jSeparator1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 775, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(cbCardRenderShowReminderText)
.add(cbCardRenderHideSetSymbol)
.add(cbCardRenderShowAbilityTextOverlay)
.add(panelCardStylesLayout.createSequentialGroup()
.add(6, 6, 6)
.add(labelRenderMode)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbCardRenderImageFallback, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 122, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
);
panelCardStylesLayout.setVerticalGroup(
panelCardStylesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(panelCardStylesLayout.createSequentialGroup()
.add(0, 0, 0)
.add(panelCardStylesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(labelRenderMode)
.add(cbCardRenderImageFallback, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
.add(0, 0, 0)
.add(cbCardRenderIconsForAbilities)
.add(0, 0, 0)
.add(cbCardRenderIconsForPlayable)
.add(0, 0, 0)
.add(jSeparator1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(0, 0, 0)
.add(cbCardRenderShowReminderText)
.add(0, 0, 0)
.add(cbCardRenderHideSetSymbol)
.add(0, 0, 0)
.add(cbCardRenderShowAbilityTextOverlay))
);
org.jdesktop.layout.GroupLayout tabGuiImagesLayout = new org.jdesktop.layout.GroupLayout(tabGuiImages);
tabGuiImages.setLayout(tabGuiImagesLayout);
@ -2361,7 +2394,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(panelCardStyles, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(panelCardImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addContainerGap(306, Short.MAX_VALUE))
.addContainerGap(309, Short.MAX_VALUE))
);
tabsPanel.addTab("GUI Images", tabGuiImages);
@ -2391,34 +2424,28 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbStopAttack.setSelected(true);
cbStopAttack.setText("STOP skips on declare attackers if attackers are available");
cbStopAttack.setToolTipText("");
cbStopAttack.setActionCommand("");
phases_stopSettings.add(cbStopAttack);
cbStopBlockWithAny.setSelected(true);
cbStopBlockWithAny.setText("STOP skips on declare blockers if ANY blockers are available");
cbStopBlockWithAny.setToolTipText("");
cbStopBlockWithAny.setText("STOP skips when attacked and on declare blockers if ANY blockers are available");
cbStopBlockWithAny.setActionCommand("");
phases_stopSettings.add(cbStopBlockWithAny);
cbStopBlockWithZero.setText("STOP skips on declare blockers if ZERO blockers are available");
cbStopBlockWithZero.setToolTipText("");
cbStopBlockWithZero.setText("STOP skips when attacked if ZERO blockers are available");
cbStopBlockWithZero.setActionCommand("");
phases_stopSettings.add(cbStopBlockWithZero);
cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop until empty (off)");
cbStopOnNewStackObjects.setToolTipText("");
cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop when stack empty (off)");
cbStopOnNewStackObjects.setActionCommand("");
cbStopOnNewStackObjects.setPreferredSize(new java.awt.Dimension(300, 25));
phases_stopSettings.add(cbStopOnNewStackObjects);
cbStopOnAllMain.setText("Skip to MAIN step (F7): stop on any main steps (on) or stop on your main step (off)");
cbStopOnAllMain.setToolTipText("");
cbStopOnAllMain.setActionCommand("");
phases_stopSettings.add(cbStopOnAllMain);
cbStopOnAllEnd.setText("Skip to END step (F5): stop on any end steps (on) or stop on opponents end step (off)");
cbStopOnAllEnd.setToolTipText("");
cbStopOnAllEnd.setActionCommand("");
cbStopOnAllEnd.setPreferredSize(new java.awt.Dimension(300, 25));
phases_stopSettings.add(cbStopOnAllEnd);
@ -2743,7 +2770,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
});
jLabel16.setText("Playing from folder:");
jLabel16.setToolTipText("");
btnBattlefieldBGMBrowse.setText("Browse...");
btnBattlefieldBGMBrowse.addActionListener(new java.awt.event.ActionListener() {
@ -2803,7 +2829,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
tabsPanel.addTab("Sounds", tabSounds);
connection_Proxy.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Proxy for server connection and images download"));
connection_Proxy.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Proxy for server connection and images download (DO NOT SUPPORTED)"));
cbProxyType.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
@ -3039,7 +3065,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
save(prefs, dialog.cbUseRandomBattleImage, KEY_BATTLEFIELD_IMAGE_RANDOM, "true", "false");
// rendering
save(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_IMAGE_MODE, "true", "false");
save(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_IMAGE_MODE);
save(prefs, dialog.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "false");
save(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "false");
save(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true", "false");
@ -3325,6 +3351,11 @@ public class PreferencesDialog extends javax.swing.JDialog {
return;
}
if (!NETWORK_ENABLE_PROXY_SUPPORT) {
connection.setProxyType(ProxyType.NONE);
return;
}
connection.setProxyType(configProxyType);
if (configProxyType != ProxyType.NONE) {
String host = getCachedValue(KEY_PROXY_ADDRESS, "");
@ -3485,7 +3516,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode()));
// rendering settings
load(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_IMAGE_MODE, "true", "false");
dialog.cbCardRenderImageFallback.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_RENDERING_IMAGE_MODE, CardRenderMode.MTGO.toString()));
load(prefs, dialog.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "true");
load(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "true");
load(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true");
@ -3809,11 +3840,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
}
public static int getRenderMode() {
if (getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_IMAGE_MODE, "false").equals("false")) {
return 0; // mtgo
} else {
return 1; // image
}
return CardRenderMode.fromString(getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_IMAGE_MODE, CardRenderMode.MTGO.toString())).getId();
}
public static boolean getRenderIconsForAbilities() {
@ -4043,7 +4070,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JCheckBox cbCardRenderHideSetSymbol;
private javax.swing.JCheckBox cbCardRenderIconsForAbilities;
private javax.swing.JCheckBox cbCardRenderIconsForPlayable;
private javax.swing.JCheckBox cbCardRenderImageFallback;
private javax.swing.JComboBox<String> cbCardRenderImageFallback;
private javax.swing.JCheckBox cbCardRenderShowAbilityTextOverlay;
private javax.swing.JCheckBox cbCardRenderShowReminderText;
private javax.swing.JCheckBox cbConfirmEmptyManaPool;
@ -4175,6 +4202,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JLabel labelNextTurn;
private javax.swing.JLabel labelPreferredImageLanguage;
private javax.swing.JLabel labelPriorEnd;
private javax.swing.JLabel labelRenderMode;
private javax.swing.JLabel labelSizeGroup1;
private javax.swing.JLabel labelSizeGroup2;
private javax.swing.JLabel labelSkipStep;

View file

@ -165,7 +165,6 @@
</Component>
<Component class="javax.swing.JLabel" name="lblStatus">
<Properties>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JTextField" name="txtServer">
@ -212,7 +211,6 @@
<ComponentRef name="txtEmail"/>
</Property>
<Property name="text" type="java.lang.String" value="(used for password reset)"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component>
</SubComponents>

View file

@ -87,8 +87,6 @@ public class RegisterUserDialog extends MageDialog {
btnCancel.setText("Cancel");
btnCancel.addActionListener(evt -> btnCancelActionPerformed(evt));
lblStatus.setToolTipText("");
lblPasswordConfirmation.setLabelFor(txtPasswordConfirmation);
lblPasswordConfirmation.setText("Password:");
@ -102,7 +100,6 @@ public class RegisterUserDialog extends MageDialog {
lblEmailReasoning.setFont(new java.awt.Font("Lucida Grande", 0, 10)); // NOI18N
lblEmailReasoning.setLabelFor(txtEmail);
lblEmailReasoning.setText("(used for password reset and sending initial password)");
lblEmailReasoning.setToolTipText("");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);

View file

@ -94,7 +94,6 @@
<Property name="dividerLocation" type="int" value="300"/>
<Property name="dividerSize" type="int" value="3"/>
<Property name="resizeWeight" type="double" value="1.0"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>

View file

@ -207,7 +207,6 @@ public class TableWaitingDialog extends MageDialog {
jSplitPane1.setDividerLocation(300);
jSplitPane1.setDividerSize(3);
jSplitPane1.setResizeWeight(1.0);
jSplitPane1.setToolTipText("");
jTableSeats.setModel(tableWaitModel);
jTableSeats.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);

View file

@ -116,11 +116,9 @@
<Component class="javax.swing.JComboBox" name="comboRenderMode">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="2">
<StringItem index="0" value="MTGO"/>
<StringItem index="1" value="Image"/>
</StringArray>
<StringArray count="0"/>
</Property>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Image - Renders card image with text overlay&lt;br&gt; MTGO - Renders card frame around card art&lt;br&gt; Forced M15 - Renders all cards in the MTGO style with the modern frame&lt;br&gt; Forced Retro - Renders all cards in the MTGO style with the retro frame"/>
</Properties>
<Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboRenderModeItemStateChanged"/>
@ -300,7 +298,6 @@
<StringItem index="0" value="loading..."/>
</StringArray>
</Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboCardColorItemStateChanged"/>
@ -382,7 +379,6 @@
<StringItem index="2" value="Dead"/>
</StringArray>
</Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="alignmentX" type="float" value="0.0"/>
</Properties>
<Events>

View file

@ -16,7 +16,7 @@ import mage.client.cards.BigCard;
import mage.client.game.PlayAreaPanel;
import mage.client.game.PlayerPanelExt;
import mage.client.themes.ThemeType;
import mage.client.util.ClientEventType;
import mage.client.util.*;
import mage.client.util.Event;
import mage.client.util.GUISizeHelper;
import mage.client.util.Listener;
@ -82,6 +82,7 @@ public class TestCardRenderDialog extends MageDialog {
getRootPane().setDefaultButton(buttonCancel);
// init render mode
this.comboRenderMode.setModel(new DefaultComboBoxModel<>(CardRenderMode.toList()));
this.comboRenderMode.setSelectedIndex(PreferencesDialog.getRenderMode());
// init themes list
@ -326,6 +327,13 @@ public class TestCardRenderDialog extends MageDialog {
possibleTargets.add(playerYou.getId());
}
// chosen target
Set<UUID> chosenTargets = null;
if (false) { // TODO: add GUI's checkbox for checkPlayerAsChosen
chosenTargets = new LinkedHashSet<>();
chosenTargets.add(playerYou.getId());
}
// player's panel
if (this.player == null) {
// create new panel
@ -345,7 +353,7 @@ public class TestCardRenderDialog extends MageDialog {
.findFirst()
.orElse(null);
this.player.init(this.game.getId(), playerYou.getId(), isMe, this.bigCard, 0);
this.player.update(gameView, currentPlayerView, possibleTargets);
this.player.update(gameView, currentPlayerView, possibleTargets, chosenTargets);
this.player.sizePlayerPanel(smallMode);
// update CARDS
@ -424,6 +432,7 @@ public class TestCardRenderDialog extends MageDialog {
cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "185", 0, 0, 0, true, false, null)); // Judith, the Scourge Diva
cardViews.add(createHandCard(game, playerYou.getId(), "DIS", "153")); // Odds // Ends (split card)
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "38")); // Animating Faerie (adventure card)
cardViews.add(createHandCard(game, playerYou.getId(), "LEA", "278")); // Bayou (retro frame)
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, false, false)); // face down
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", true, false, true)); // morphed
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, true, false)); // manifested
@ -628,7 +637,7 @@ public class TestCardRenderDialog extends MageDialog {
labelRenderMode.setText("Render mode:");
comboRenderMode.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "MTGO", "Image" }));
comboRenderMode.setToolTipText("<HTML>Image - Renders card image with text overlay<br> MTGO - Renders card frame around card art<br> Forced M15 - Renders all cards in the MTGO style with the modern frame<br> Forced Retro - Renders all cards in the MTGO style with the retro frame");
comboRenderMode.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
comboRenderModeItemStateChanged(evt);
@ -736,7 +745,6 @@ public class TestCardRenderDialog extends MageDialog {
labelCardColor.setText("Card color:");
comboCardColor.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "loading..." }));
comboCardColor.setToolTipText("");
comboCardColor.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
comboCardColorItemStateChanged(evt);
@ -774,7 +782,6 @@ public class TestCardRenderDialog extends MageDialog {
playerOptions.add(checkPlayerSmallMode);
comboPlayerStatus.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Active", "Inactive", "Dead" }));
comboPlayerStatus.setToolTipText("");
comboPlayerStatus.setAlignmentX(0.0F);
comboPlayerStatus.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {

View file

@ -343,7 +343,6 @@
</Property>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="player 2"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="alignmentX" type="float" value="1.0"/>
<Property name="alignmentY" type="float" value="0.0"/>
<Property name="focusable" type="boolean" value="false"/>
@ -523,7 +522,6 @@
</Property>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="player 12"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="focusable" type="boolean" value="false"/>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
<Property name="verifyInputWhenFocusTarget" type="boolean" value="false"/>

View file

@ -45,8 +45,9 @@
/**
* ms delay between booster showing up and pick being allowed.
* Will be disabled in test mode
*/
private static final int protectionTime = 1500;
private static final int PROTECTION_CLICKS_TIMEOUT_MS = 1500;
/**
* Timer starting at booster being displayed, to protect from early pick due to clicking
* a little too much on the last pick.
@ -138,7 +139,11 @@
}
);
protectionTimer = new Timer(protectionTime, e -> protectionTimer.stop());
int protectionTimeout = PROTECTION_CLICKS_TIMEOUT_MS;
if (SessionHandler.isTestMode()) {
protectionTimeout = 100;
}
protectionTimer = new Timer(protectionTimeout, e -> protectionTimer.stop());
}
public void cleanUp() {
@ -186,15 +191,15 @@
}
public void updateDraft(DraftView draftView) {
if (draftView.getSets().size() != 3) {
if (draftView.getSetNames().size() != 3) {
// Random draft - TODO: can we access the type of draft here?
this.editPack1.setText("Random Boosters");
this.editPack2.setText("Random Boosters");
this.editPack3.setText("Random Boosters");
} else {
this.editPack1.setText(String.format("%s - %s", draftView.getSetCodes().get(0), draftView.getSets().get(0)));
this.editPack2.setText(String.format("%s - %s", draftView.getSetCodes().get(1), draftView.getSets().get(1)));
this.editPack3.setText(String.format("%s - %s", draftView.getSetCodes().get(2), draftView.getSets().get(2)));
this.editPack1.setText(draftView.getBoosterInfo(0));
this.editPack2.setText(draftView.getBoosterInfo(1));
this.editPack3.setText(draftView.getBoosterInfo(2));
}
// scroll too long text to the start
@ -396,7 +401,8 @@
}
if (!draftBooster.isEmptyGrid()) {
SessionHandler.setBoosterLoaded(draftId); // confirm to the server that the booster has been successfully loaded, otherwise the server will re-send the booster
// confirm to the server that the booster has been successfully loaded, otherwise the server will re-send the booster
SessionHandler.setBoosterLoaded(draftId);
if (pickNo != protectionPickNo && !protectionTimer.isRunning()) {
// Restart the protection timer.
@ -773,7 +779,6 @@
labelPlayer02.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N
labelPlayer02.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
labelPlayer02.setText("player 2");
labelPlayer02.setToolTipText("");
labelPlayer02.setAlignmentX(1.0F);
labelPlayer02.setAlignmentY(0.0F);
labelPlayer02.setFocusable(false);
@ -887,7 +892,6 @@
labelPlayer12.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N
labelPlayer12.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
labelPlayer12.setText("player 12");
labelPlayer12.setToolTipText("");
labelPlayer12.setFocusable(false);
labelPlayer12.setRequestFocusEnabled(false);
labelPlayer12.setVerifyInputWhenFocusTarget(false);

View file

@ -30,8 +30,10 @@ import mage.constants.*;
import mage.game.events.PlayerQueryEvent;
import mage.players.PlayableObjectStats;
import mage.players.PlayableObjectsList;
import mage.util.CardUtil;
import mage.util.DebugUtil;
import mage.util.MultiAmountMessage;
import mage.util.StreamUtils;
import mage.view.*;
import org.apache.log4j.Logger;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
@ -53,6 +55,7 @@ import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static mage.client.dialog.PreferencesDialog.*;
import static mage.constants.PlayerAction.*;
@ -213,6 +216,22 @@ public final class GamePanel extends javax.swing.JPanel {
});
}
public List<UUID> getPossibleTargets() {
if (options != null && options.containsKey("possibleTargets")) {
return (List<UUID>) options.get("possibleTargets");
} else {
return Collections.emptyList();
}
}
public Set<UUID> getChosenTargets() {
if (options != null && options.containsKey("chosenTargets")) {
return new HashSet<>((List<UUID>) options.get("chosenTargets"));
} else {
return Collections.emptySet();
}
}
public CardView findCard(UUID id) {
return this.allCardsIndex.getOrDefault(id, null);
}
@ -639,7 +658,7 @@ public final class GamePanel extends javax.swing.JPanel {
// see test render dialog for refresh commands order
playPanel.getPlayerPanel().fullRefresh(GUISizeHelper.playerPanelGuiScale);
playPanel.init(player, bigCard, gameId, player.getPriorityTimeLeftSecs());
playPanel.update(lastGameData.game, player, lastGameData.targets);
playPanel.update(lastGameData.game, player, lastGameData.targets, lastGameData.getChosenTargets());
playPanel.getPlayerPanel().sizePlayerPanel(false);
}
});
@ -650,6 +669,11 @@ public final class GamePanel extends javax.swing.JPanel {
this.abilityPicker.fullRefresh(GUISizeHelper.dialogGuiScale);
this.abilityPicker.init(gameId, bigCard);
}
if (this.pickMultiNumber != null && !this.pickMultiNumber.isVisible()) {
// TODO: add pick number dialogs support here
//this.pickMultiNumber.fullRefresh(GUISizeHelper.dialogGuiScale);
this.pickMultiNumber.init(gameId, bigCard);
}
}
private void setSkipButtonImage(JButton button, Image image) {
@ -819,6 +843,7 @@ public final class GamePanel extends javax.swing.JPanel {
MageFrame.addGame(gameId, this);
this.feedbackPanel.init(gameId, bigCard);
this.feedbackPanel.clear();
this.pickMultiNumber.init(gameId, bigCard);
this.abilityPicker.init(gameId, bigCard);
this.btnConcede.setVisible(true);
this.btnStopWatching.setVisible(false);
@ -1161,7 +1186,7 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
}
players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets);
players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets, lastGameData.getChosenTargets());
if (player.getPlayerId().equals(playerId)) {
skipButtons.updateFromPlayer(player);
}
@ -1777,12 +1802,7 @@ public final class GamePanel extends javax.swing.JPanel {
needZone = (Zone) lastGameData.options.get("targetZone");
}
List<UUID> needChosen;
if (lastGameData.options != null && lastGameData.options.containsKey("chosenTargets")) {
needChosen = (List<UUID>) lastGameData.options.get("chosenTargets");
} else {
needChosen = new ArrayList<>();
}
Set<UUID> needChosen = lastGameData.getChosenTargets();
Set<UUID> needSelectable;
if (lastGameData.targets != null) {
@ -1804,6 +1824,7 @@ public final class GamePanel extends javax.swing.JPanel {
// hand
if (needZone == Zone.HAND || needZone == Zone.ALL) {
// my hand
for (CardView card : lastGameData.game.getMyHand().values()) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
@ -1815,6 +1836,34 @@ public final class GamePanel extends javax.swing.JPanel {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
// opponent hands (switching by GUI's button with my hand)
List<SimpleCardView> list = lastGameData.game.getOpponentHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList());
for (SimpleCardView card : list) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
}
if (needChosen.contains(card.getId())) {
card.setSelected(true);
}
if (needPlayable.containsObject(card.getId())) {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
// watched hands (switching by GUI's button with my hand)
list = lastGameData.game.getWatchedHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList());
for (SimpleCardView card : list) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
}
if (needChosen.contains(card.getId())) {
card.setSelected(true);
}
if (needPlayable.containsObject(card.getId())) {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
}
// stack
@ -1983,7 +2032,7 @@ public final class GamePanel extends javax.swing.JPanel {
private void prepareSelectableWindows(
Collection<CardInfoWindowDialog> windows,
Set<UUID> needSelectable,
List<UUID> needChosen,
Set<UUID> needChosen,
PlayableObjectsList needPlayable
) {
// lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it
@ -2177,6 +2226,7 @@ public final class GamePanel extends javax.swing.JPanel {
hideAll();
DialogManager.getManager(gameId).fadeOut();
pickMultiNumber.init(gameId, bigCard);
pickMultiNumber.showDialog(messages, min, max, lastGameData.options, () -> {
if (pickMultiNumber.isCancel()) {
SessionHandler.sendPlayerBoolean(gameId, false);

View file

@ -57,7 +57,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
// data init
init(player, bigCard, gameId, priorityTime);
update(null, player, null);
update(null, player, null, null);
playerPanel.sizePlayerPanel(isSmallMode());
// init popup menu (must run after data init)
@ -510,8 +510,8 @@ public class PlayAreaPanel extends javax.swing.JPanel {
this.isMe = player.getControlled();
}
public final void update(GameView game, PlayerView player, Set<UUID> possibleTargets) {
this.playerPanel.update(game, player, possibleTargets);
public final void update(GameView game, PlayerView player, Set<UUID> possibleTargets, Set<UUID> chosenTargets) {
this.playerPanel.update(game, player, possibleTargets, chosenTargets);
this.battlefieldPanel.update(player.getBattlefield());
if (this.allowViewHandCardsMenuItem != null) {
this.allowViewHandCardsMenuItem.setSelected(player.getUserData().isAllowRequestHandToAll());

View file

@ -247,7 +247,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
.orElse(0);
}
public void update(GameView game, PlayerView player, Set<UUID> possibleTargets) {
public void update(GameView game, PlayerView player, Set<UUID> possibleTargets, Set<UUID> chosenTargets) {
this.player = player;
int pastLife = player.getLife();
if (playerLives != null) {
@ -427,6 +427,12 @@ public class PlayerPanelExt extends javax.swing.JPanel {
this.btnPlayer.setBorder(YELLOW_BORDER);
}
// selected targeting (draw as priority)
if (chosenTargets != null && chosenTargets.contains(this.playerId)) {
this.avatar.setBorder(GREEN_BORDER); // TODO: use diff green color for chosen targeting and current priority?
this.btnPlayer.setBorder(GREEN_BORDER);
}
update(player.getManaPool());
}
@ -687,11 +693,11 @@ public class PlayerPanelExt extends javax.swing.JPanel {
zonesPanel.setLayout(null);
zonesPanel.setOpaque(false);
// tools button like hints
// hints
toolHintsHelper = new JButton();
toolHintsHelper.setFont(this.getFont());
toolHintsHelper.setText("hints");
toolHintsHelper.setToolTipText("Open new card hints helper window");
toolHintsHelper.setText("Hints");
toolHintsHelper.setToolTipText("Open card hints helper window");
toolHintsHelper.addActionListener(this::btnToolHintsHelperActionPerformed);
toolHintsHelper.setBounds(sizeMod(3), sizeMod(2 + 21 + 2), sizeMod(73), sizeMod(21));
zonesPanel.add(toolHintsHelper);
@ -701,7 +707,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
image = ImageHelper.getImageFromResources("/info/command_zone.png");
resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r);
commandZone = new HoverButton(null, resized, resized, resized, r, this.guiScaleMod);
commandZone.setToolTipText("Command Zone (Commanders, Emblems and Planes)");
commandZone.setToolTipText("Command Zone (Commanders, Emblems, and Planes)");
commandZone.setOpaque(false);
commandZone.setObserver(() -> btnCommandZoneActionPerformed(null));
commandZone.setBounds(sizeMod(3), 0, sizeMod(21), sizeMod(21));

View file

@ -98,8 +98,8 @@ public class MageActionCallback implements ActionCallback {
private MageCard prevCardPanel;
private boolean startedDragging;
private boolean isDragging; // TODO: remove drag hand code to the hand panels
private Point initialCardPos;
private Point initialMousePos;
private Point initialCardPos = null;
private Point initialMousePos = null;
private final Set<MageCard> draggingCards = new HashSet<>();
public MageActionCallback() {
@ -351,6 +351,11 @@ public class MageActionCallback implements ActionCallback {
return;
}
if (this.initialMousePos == null || this.initialCardPos == null) {
// only allow really mouse pressed, e.g. ignore draft/game update on active card draging/pressing
return;
}
Point mouse = new Point(e.getX(), e.getY());
SwingUtilities.convertPointToScreen(mouse, data.getComponent());
if (!isDragging

View file

@ -10,6 +10,7 @@ import mage.client.draft.DraftPanel;
import mage.client.game.GamePanel;
import mage.client.plugins.impl.Plugins;
import mage.client.util.DeckUtil;
import mage.client.util.GUISizeHelper;
import mage.client.util.IgnoreList;
import mage.client.util.audio.AudioManager;
import mage.client.util.object.SaveObjectUtil;
@ -22,9 +23,12 @@ import mage.util.DebugUtil;
import mage.view.*;
import mage.view.ChatMessage.MessageType;
import org.apache.log4j.Logger;
import org.mage.card.arcane.ManaSymbols;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.*;
/**
@ -72,7 +76,7 @@ public class CallbackClientImpl implements CallbackClient {
SwingUtilities.invokeLater(() -> {
try {
if (DebugUtil.NETWORK_SHOW_CLIENT_CALLBACK_MESSAGES_LOG) {
logger.info("message " + callback.getMessageId() + " - " + callback.getMethod().getType() + " - " + callback.getMethod());
logger.info(callback.getInfo());
}
// process bad connection (events can income in wrong order, so outdated data must be ignored)
@ -203,11 +207,7 @@ public class CallbackClientImpl implements CallbackClient {
case SERVER_MESSAGE: {
if (callback.getData() != null) {
ChatMessage message = (ChatMessage) callback.getData();
if (message.getColor() == ChatMessage.MessageColor.RED) {
JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.WARNING_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.INFORMATION_MESSAGE);
}
showMessageDialog(null, message.getMessage(), "Server message");
}
break;
}
@ -401,7 +401,7 @@ public class CallbackClientImpl implements CallbackClient {
case SHOW_USERMESSAGE: {
List<String> messageData = (List<String>) callback.getData();
if (messageData.size() == 2) {
JOptionPane.showMessageDialog(null, messageData.get(1), messageData.get(0), JOptionPane.WARNING_MESSAGE);
showMessageDialog(null, messageData.get(1), messageData.get(0));
}
break;
}
@ -420,8 +420,7 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
JOptionPane.showMessageDialog(panel, message.getMessage(), "Game message",
JOptionPane.INFORMATION_MESSAGE);
showMessageDialog(panel, message.getMessage(), "Game message");
}
break;
}
@ -510,6 +509,18 @@ public class CallbackClientImpl implements CallbackClient {
});
}
/**
* Show modal message box, so try to use it only for critical errors or global message. As less as possible.
*/
private void showMessageDialog(Component parentComponent, String message, String title) {
// convert to html
// message - supported
// title - not supported
message = ManaSymbols.replaceSymbolsWithHTML(message, ManaSymbols.Type.DIALOG);
message = GUISizeHelper.textToHtmlWithSize(message, GUISizeHelper.dialogFont);
JOptionPane.showMessageDialog(parentComponent, message, title, JOptionPane.INFORMATION_MESSAGE);
}
private ActionData appendJsonEvent(String name, UUID gameId, Object value) {
Session session = SessionHandler.getSession();
if (session.isJsonLogActive()) {

View file

@ -48,6 +48,12 @@ public class XmageURLConnection {
private static final AtomicLong debugLastRequestTimeMs = new AtomicLong(0);
private static final ReentrantLock debugLogsWriterlock = new ReentrantLock();
static {
// add Authority Information Access (AIA) Extension support for certificates from Windows servers like gatherer website
// fix download errors like sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
}
final String url;
Proxy proxy = null;
HttpURLConnection connection = null;
@ -123,6 +129,10 @@ public class XmageURLConnection {
}
this.proxy = Proxy.NO_PROXY;
if (!PreferencesDialog.NETWORK_ENABLE_PROXY_SUPPORT) {
return;
}
if (type != Proxy.Type.DIRECT) {
try {
String address = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PROXY_ADDRESS, "");

View file

@ -41,6 +41,10 @@ public class TablePlayerPanel extends javax.swing.JPanel {
this.newPlayerPanel.setSkillLevel(playerSkill);
}
public static String extractAiPlayerNumberFromLabel(String label) {
return ClientDefaultSettings.computerName + " " + label.substring(Math.max(0, label.length() - 2)).trim();
}
public boolean joinTable(UUID roomId, UUID tableId) throws IOException, ClassNotFoundException {
if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) {
return SessionHandler.joinTable(roomId, tableId, this.newPlayerPanel.getPlayerName(), (PlayerType) this.cbPlayerType.getSelectedItem(), this.newPlayerPanel.getSkillLevel(), DeckImporter.importDeckFromFile(this.newPlayerPanel.getDeckFile(), true), "");
@ -124,7 +128,7 @@ public class TablePlayerPanel extends javax.swing.JPanel {
private void cbPlayerTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbPlayerTypeActionPerformed
if (getPlayerType() != PlayerType.HUMAN) {
this.newPlayerPanel.setVisible(true);
this.newPlayerPanel.setPlayerName(ClientDefaultSettings.computerName + " " + this.lblPlayerNum.getText().charAt(this.lblPlayerNum.getText().length() - 1));
this.newPlayerPanel.setPlayerName(extractAiPlayerNumberFromLabel(this.lblPlayerNum.getText()));
} else {
this.newPlayerPanel.setVisible(false);
}

View file

@ -201,7 +201,7 @@
<Component class="javax.swing.JToggleButton" name="btnTypeTourneyConstructed">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Constructed tourn."/>
<Property name="text" type="java.lang.String" value="Constructed tourney"/>
<Property name="toolTipText" type="java.lang.String" value="Shows all constructed tournament tables."/>
<Property name="actionCommand" type="java.lang.String" value="typeTourneyConstructed"/>
<Property name="focusPainted" type="boolean" value="false"/>
@ -216,7 +216,7 @@
<Component class="javax.swing.JToggleButton" name="btnTypeTourneyLimited">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Limited tourn."/>
<Property name="text" type="java.lang.String" value="Limited tourney"/>
<Property name="toolTipText" type="java.lang.String" value="Shows all limited tournament tables."/>
<Property name="actionCommand" type="java.lang.String" value="typeTourneyLimited"/>
<Property name="focusPainted" type="boolean" value="false"/>

View file

@ -1163,7 +1163,7 @@ public class TablesPanel extends javax.swing.JPanel {
filterBar1.add(btnTypeMatch);
btnTypeTourneyConstructed.setSelected(true);
btnTypeTourneyConstructed.setText("Constructed tourn.");
btnTypeTourneyConstructed.setText("Constructed tourney");
btnTypeTourneyConstructed.setToolTipText("Shows all constructed tournament tables.");
btnTypeTourneyConstructed.setActionCommand("typeTourneyConstructed");
btnTypeTourneyConstructed.setFocusPainted(false);
@ -1178,7 +1178,7 @@ public class TablesPanel extends javax.swing.JPanel {
filterBar1.add(btnTypeTourneyConstructed);
btnTypeTourneyLimited.setSelected(true);
btnTypeTourneyLimited.setText("Limited tourn.");
btnTypeTourneyLimited.setText("Limited tourney");
btnTypeTourneyLimited.setToolTipText("Shows all limited tournament tables.");
btnTypeTourneyLimited.setActionCommand("typeTourneyLimited");
btnTypeTourneyLimited.setFocusPainted(false);
@ -1694,13 +1694,13 @@ public class TablesPanel extends javax.swing.JPanel {
DeckCardLists testDeck = DeckImporter.importDeckFromFile(testDeckFile, false);
PlayerType aiType = useMonteCarloAI ? PlayerType.COMPUTER_MONTE_CARLO : PlayerType.COMPUTER_MAD;
int numSeats = gameName.contains("2") || gameName.contains("Monte Carlo") ? 2 : 4;
boolean multiPlayer = numSeats > 2;
int numPlayers = gameName.contains("2") || gameName.contains("Monte Carlo") ? 2 : 4;
boolean multiPlayer = numPlayers > 2;
MatchOptions options = new MatchOptions(gameName, gameType, multiPlayer, numSeats);
MatchOptions options = new MatchOptions(gameName, gameType, multiPlayer);
options.getPlayerTypes().add(PlayerType.HUMAN);
options.getPlayerTypes().add(aiType);
for (int i=2 ; i < numSeats ; i++) {
for (int i=2 ; i < numPlayers ; i++) {
options.getPlayerTypes().add(aiType);
}
options.setDeckType("Variant Magic - Freeform Commander");
@ -1720,7 +1720,7 @@ public class TablesPanel extends javax.swing.JPanel {
SessionHandler.joinTable(roomId, table.getTableId(), "Human", PlayerType.HUMAN, 1, testDeck, "");
SessionHandler.joinTable(roomId, table.getTableId(), "Computer", aiType, 1, testDeck, "");
for (int i=2 ; i < numSeats ; i++) {
for (int i=2 ; i < numPlayers ; i++) {
SessionHandler.joinTable(roomId, table.getTableId(), "Computer" + i, aiType, 1, testDeck, "");
}
SessionHandler.startMatch(roomId, table.getTableId());

View file

@ -145,7 +145,7 @@ public class TournamentPlayerPanel extends javax.swing.JPanel {
private void cbPlayerTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbPlayerTypeActionPerformed
if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) {
this.pnlPlayerName.setVisible(true);
this.txtPlayerName.setText(ClientDefaultSettings.computerName + " " + this.lblPlayerNum.getText().charAt(this.lblPlayerNum.getText().length() - 1));
this.txtPlayerName.setText(TablePlayerPanel.extractAiPlayerNumberFromLabel(this.lblPlayerNum.getText()));
this.txtPlayerName.setEditable(false);
this.txtPlayerName.setEnabled(false);
} else {

View file

@ -273,7 +273,6 @@
<Container class="javax.swing.JSplitPane" name="jSplitPane2">
<Properties>
<Property name="resizeWeight" type="double" value="1.0"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>

View file

@ -234,12 +234,15 @@ public class TournamentPanel extends javax.swing.JPanel {
if (tournament.getStepStartTime() != null) {
usedTime = Format.getDuration((tournament.getServerTime().getTime() - tournament.getStepStartTime().getTime()) / 1000);
}
txtTournamentState.setText(tournament.getTournamentState() + " (" + usedTime + ") " + tournament.getRunningInfo());
txtTournamentState.setText(tournament.getTournamentState() + " (" + usedTime + ")");
break;
default:
txtTournamentState.setText(tournament.getTournamentState());
break;
}
if (!tournament.getRunningInfo().isEmpty()) {
txtTournamentState.setText(txtTournamentState.getText() + ", " + tournament.getRunningInfo());
}
if (txtEndTime == null) {
return;
@ -444,7 +447,6 @@ public class TournamentPanel extends javax.swing.JPanel {
);
jSplitPane2.setResizeWeight(1.0);
jSplitPane2.setToolTipText("");
jSplitPane1.setDividerLocation(230);
jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);

View file

@ -0,0 +1,59 @@
package mage.client.util;
import java.util.ArrayList;
import java.util.List;
public enum CardRenderMode {
MTGO("MTGO", 0),
IMAGE("Image", 1),
FORCED_M15("Forced M15", 2),
FORCED_RETRO("Forced Retro", 3);
private final String text;
private final int id;
CardRenderMode(String text, int id) {
this.text = text;
this.id = id;
}
@Override
public String toString() {
return text;
}
public String getText() {
return text;
}
public int getId() {
return id;
}
public static String[] toList() {
List<String> list = new ArrayList<>();
for (CardRenderMode mode : CardRenderMode.values()) {
list.add(mode.toString());
}
return list.toArray(new String[0]);
}
public static CardRenderMode fromId(int id) {
for (CardRenderMode mode : CardRenderMode.values()) {
if (mode.getId() == id) {
return mode;
}
}
return MTGO;
}
public static CardRenderMode fromString(String text) {
for (CardRenderMode mode : CardRenderMode.values()) {
if (mode.text.equals(text)) {
return mode;
}
}
return MTGO;
}
}

View file

@ -6,6 +6,7 @@ import mage.client.util.gui.GuiDisplayUtil;
import org.mage.card.arcane.CardRenderer;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.Locale;
@ -108,7 +109,7 @@ public final class GUISizeHelper {
// app - frame/window title
// nimbus's LaF limited to static title size, so font can't be too big (related code in SynthInternalFrameTitlePane, BasicInternalFrameTitlePane)
UIManager.put("InternalFrame.titleFont", dialogFont.deriveFont(Font.BOLD, Math.min(17, 0.8f * dialogFont.getSize())));
UIManager.put("InternalFrame.titleFont", new FontUIResource(dialogFont.deriveFont(Font.BOLD, Math.min(17, 0.8f * dialogFont.getSize()))));
// app - tables
tableFont = new java.awt.Font("Arial", 0, dialogFontSize);
@ -150,7 +151,11 @@ public final class GUISizeHelper {
cardTooltipLargeImageHeight = 30 * tooltipFontSize;
cardTooltipLargeTextWidth = Math.max(150, 20 * tooltipFontSize - 50);
cardTooltipLargeTextHeight = Math.max(100, 12 * tooltipFontSize - 20);
UIManager.put("ToolTip.font", cardTooltipFont);
UIManager.put("ToolTip.font", new FontUIResource(cardTooltipFont));
// app - information boxes (only title, text controls by content)
// TODO: doesn't work
//UIManager.put("OptionPane.titleFont", new FontUIResource(dialogFont.deriveFont(Font.BOLD, Math.min(17, 1.3f * dialogFont.getSize()))));
// game - player panel
playerPanelGuiScale = (float) (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_PLAYER_PANEL_SIZE, 14) / 14.0);

View file

@ -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;
@ -322,7 +323,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
if (cn.equals("acid rain")
|| cn.equals("agent of treachery")
|| cn.equals("anafenza, the foremost")
|| cn.equals("ancient tomb")
|| cn.equals("animar, soul of element")
|| cn.equals("animate artifact")
|| cn.equals("apocalypse")
@ -352,7 +352,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("cabal coffers")
|| cn.equals("candelabra of tawnos")
|| cn.equals("captain sisay")
|| cn.equals("card view")
|| cn.equals("cataclysm")
|| cn.equals("catastrophe")
|| cn.equals("celestial dawn")
@ -368,7 +367,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("contamination")
|| cn.equals("craterhoof behemoth")
|| cn.equals("cryptic gateway")
|| cn.equals("cyclonic rift")
|| cn.equals("deadeye navigator")
|| cn.equals("death cloud")
|| cn.equals("decree of annihilation")
@ -377,6 +375,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("demonic consultation")
|| cn.equals("derevi, empyrial tactician")
|| cn.equals("devastation")
|| cn.equals("dictate of erebos")
|| cn.equals("dig through time")
|| cn.equals("divine intervention")
|| cn.equals("dockside extortionist")
@ -385,34 +384,35 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("drannith magistrate")
|| cn.equals("dross scorpion")
|| cn.equals("earthcraft")
|| cn.equals("edgar markov")
|| cn.equals("edric, spymaster of trest")
|| cn.equals("elesh norn, grand cenobite")
|| cn.equals("elesh norn, mother of machines")
|| cn.equals("embargo")
|| cn.equals("emrakul, the promised end")
|| cn.equals("enter the infinite")
|| cn.equals("entomb")
|| cn.equals("epicenter")
|| cn.equals("erratic portal")
|| cn.equals("expropriate")
|| cn.equals("exquisite blood")
|| cn.equals("fall of the thran")
|| cn.equals("fierce guardianship")
|| cn.equals("farewell")
|| cn.equals("flashfires")
|| cn.equals("food chain")
|| cn.equals("force of negation")
|| cn.equals("force of will")
|| cn.equals("future sight")
|| cn.equals("gaddock teeg")
|| cn.equals("gaea's cradle")
|| cn.equals("genesis chamber")
|| cn.equals("ghave, guru of spores")
|| cn.equals("gilded drake")
|| cn.equals("glenn, the voice of calm")
|| cn.equals("global ruin")
|| cn.equals("golos, tireless pilgrim")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("grave pact")
|| cn.equals("grave titan")
|| cn.equals("great whale")
|| cn.equals("gregor, shrewd magistrate")
|| cn.equals("greymond, avacyn's stalwart")
|| cn.equals("grim monolith")
|| cn.equals("grip of chaos")
|| cn.equals("gush")
@ -421,21 +421,23 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("hokori, dust drinker")
|| cn.equals("humility")
|| cn.equals("impending disaster")
|| cn.equals("imperial seal")
|| cn.equals("intruder alarm")
|| cn.equals("invoke prejudice")
|| cn.equals("iona, shield of emeria")
|| cn.equals("jin-gitaxias, core augur")
|| cn.equals("jeweled lotus")
|| cn.equals("jin-gitaxias, progress tyrant")
|| cn.equals("jokulhaups")
|| cn.equals("kaalia of the vast")
|| cn.equals("karador, ghost chieftain")
|| cn.equals("karakas")
|| cn.equals("karn, silver golem")
|| cn.equals("karn, the great creator")
|| cn.equals("kataki, war's wage")
|| cn.equals("keldon firebombers")
|| cn.equals("kiki-jiki, mirror breaker")
|| cn.equals("kinnan, bonder prodigy")
|| cn.equals("knowledge pool")
|| cn.equals("koma, cosmos serpent")
|| cn.equals("korvold, fae-cursed king")
|| cn.equals("kozilek, butcher of truth")
|| cn.equals("krark-clan ironworks")
|| cn.equals("krenko, mob boss")
@ -452,10 +454,10 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("maelstrom wanderer")
|| cn.equals("magister sphinx")
|| cn.equals("malfegor")
|| cn.equals("malik, grim manipulator")
|| cn.equals("mana breach")
|| cn.equals("mana crypt")
|| cn.equals("mana drain")
|| cn.equals("mana vault")
|| cn.equals("mana vortex")
|| cn.equals("master of cruelties")
|| cn.equals("memnarch")
@ -468,6 +470,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("minion reflector")
|| cn.equals("mycosynth lattice")
|| cn.equals("myr turbine")
|| cn.equals("nadu, winged wisdom")
|| cn.equals("narset, enlightened master")
|| cn.equals("narset, parter of veils")
|| cn.equals("nath of the gilt-leaf")
@ -487,8 +490,8 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("oloro, ageless ascetic")
|| cn.equals("omniscience")
|| cn.equals("opalescence")
|| cn.equals("opposition agent")
|| cn.equals("oppression")
|| cn.equals("orcish bowmasters")
|| cn.equals("ornithopter")
|| cn.equals("overwhelming splendor")
|| cn.equals("palinchron")
@ -504,7 +507,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("purphoros, god of the forge")
|| cn.equals("ravages of war")
|| cn.equals("reclamation sage")
|| cn.equals("rhystic study")
|| cn.equals("rick, steadfast leader")
|| cn.equals("rings of brighthearth")
|| cn.equals("rising waters")
@ -515,12 +517,11 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("seedborn muse")
|| cn.equals("sen triplets")
|| cn.equals("sensei's divining top")
|| cn.equals("serra's sanctum")
|| cn.equals("sheoldred, the apocalypse")
|| cn.equals("sheoldred, whispering one")
|| cn.equals("sire of insanity")
|| cn.equals("skithiryx, the blight dragon")
|| cn.equals("smokestack")
|| cn.equals("smothering tithe")
|| cn.equals("sol ring")
|| cn.equals("sorin markov")
|| cn.equals("splinter twin")
@ -532,8 +533,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("storm cauldron")
|| cn.equals("strip mine")
|| cn.equals("sunder")
|| cn.equals("survival of the fittest")
|| cn.equals("table view")
|| cn.equals("tainted aether")
|| cn.equals("tangle wire")
|| cn.equals("tectonic break")
@ -543,21 +542,19 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("teferi, master of time")
|| cn.equals("teferi, time raveler")
|| cn.equals("temporal manipulation")
|| cn.equals("tergrid, god of fright")
|| cn.equals("text view")
|| cn.equals("tezzeret the seeker")
|| cn.equals("thassa's oracle")
|| cn.equals("the chain veil")
|| cn.equals("the tabernacle at pendrell vale")
|| cn.equals("thieves' auction")
|| cn.equals("thoughts of ruin")
|| cn.equals("thrasios, triton hero")
|| cn.equals("time sieve")
|| cn.equals("time stretch")
|| cn.equals("time warp")
|| cn.equals("tinker")
|| cn.equals("tooth and nail")
|| cn.equals("torment of hailfire")
|| cn.equals("torpor orb")
|| cn.equals("toxrill, the corrosive")
|| cn.equals("training grounds")
|| cn.equals("treasure cruise")
|| cn.equals("triskelavus")
@ -566,18 +563,20 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("turnabout")
|| cn.equals("ugin, the spirit dragon")
|| cn.equals("ulamog, the ceaseless hunger")
|| cn.equals("ulamog, the defiler")
|| cn.equals("ulamog, the infinite gyre")
|| cn.equals("umbral mantle")
|| cn.equals("urabrask the hidden")
|| cn.equals("urza, lord high artificer")
|| cn.equals("uyo, silent prophet")
|| cn.equals("void winnower")
|| cn.equals("voltaic key")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("vorinclex, monstrous raider")
|| cn.equals("wake of destruction")
|| cn.equals("warp world")
|| cn.equals("winter moon")
|| cn.equals("winter orb")
|| cn.equals("workhorse")
|| cn.equals("worldfire")
|| cn.equals("worldgorger dragon")
|| cn.equals("worthy cause")
|| cn.equals("xanathar, guild kingpin")
@ -586,6 +585,111 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 12);
}
// Parts of infinite combos
if (cn.equals("animate artifact") || cn.equals("animar, soul of element")
|| cn.equals("archaeomancer")
|| cn.equals("ashnod's altar") || cn.equals("azami, lady of scrolls")
|| cn.equals("aura flux")
|| cn.equals("basalt monolith") || cn.equals("brago, king eternal")
|| cn.equals("candelabra of tawnos") || cn.equals("cephalid aristocrat")
|| cn.equals("cephalid illusionist") || cn.equals("changeling berserker")
|| cn.equals("consecrated sphinx")
|| cn.equals("cyclonic rift")
|| cn.equals("the chain veil")
|| cn.equals("cinderhaze wretch") || cn.equals("cryptic gateway")
|| cn.equals("deadeye navigator") || cn.equals("derevi, empyrial tactician")
|| cn.equals("doubling season") || cn.equals("dross scorpion")
|| cn.equals("earthcraft") || cn.equals("erratic portal")
|| cn.equals("enter the infinite") || cn.equals("omniscience")
|| cn.equals("exquisite blood") || cn.equals("future sight")
|| cn.equals("genesis chamber")
|| cn.equals("ghave, guru of spores")
|| cn.equals("grave pact")
|| cn.equals("grave titan") || cn.equals("great whale")
|| cn.equals("grim monolith") || cn.equals("gush")
|| cn.equals("hellkite charger") || cn.equals("intruder alarm")
|| cn.equals("helm of obedience")
|| cn.equals("hermit druid")
|| cn.equals("humility")
|| cn.equals("iona, shield of emeria")
|| cn.equals("karn, silver golem") || cn.equals("kiki-jiki, mirror breaker")
|| cn.equals("krark-clan ironworks") || cn.equals("krenko, mob boss")
|| cn.equals("krosan restorer") || cn.equals("laboratory maniac")
|| cn.equals("leonin relic-warder") || cn.equals("leyline of the void")
|| cn.equals("memnarch")
|| cn.equals("meren of clan nel toth") || cn.equals("mikaeus, the unhallowed")
|| cn.equals("mindcrank") || cn.equals("mindslaver")
|| cn.equals("minion reflector") || cn.equals("mycosynth lattice")
|| cn.equals("myr turbine") || cn.equals("narset, enlightened master")
|| cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary")
|| cn.equals("notion thief")
|| cn.equals("opalescence") || cn.equals("ornithopter")
|| cn.equals("paradox engine")
|| cn.equals("purphoros, god of the forge")
|| cn.equals("peregrine drake") || cn.equals("palinchron")
|| cn.equals("planar portal") || cn.equals("power artifact")
|| cn.equals("rings of brighthearth") || cn.equals("rite of replication")
|| cn.equals("sanguine bond") || cn.equals("sensei's divining top")
|| cn.equals("splinter twin") || cn.equals("stony silence")
|| cn.equals("sunder")
|| cn.equals("storm cauldron") || cn.equals("teferi's puzzle box")
|| cn.equals("tangle wire")
|| cn.equals("teferi, mage of zhalfir")
|| cn.equals("tezzeret the seeker") || cn.equals("time stretch")
|| cn.equals("time warp") || cn.equals("training grounds")
|| cn.equals("triskelavus") || cn.equals("triskelion")
|| cn.equals("turnabout") || cn.equals("umbral mantle")
|| cn.equals("uyo, silent prophet") || cn.equals("voltaic key")
|| cn.equals("workhorse") || cn.equals("worldgorger dragon")
|| cn.equals("worthy cause") || cn.equals("yawgmoth's will")
|| cn.equals("zealous conscripts")) {
thisMaxPower = Math.max(thisMaxPower, 15);
}
// Game changers
if (cn.equals("ad nauseam")
|| cn.equals("ancient tomb")
|| cn.equals("bolas's citadel")
|| cn.equals("chrome mox")
|| cn.equals("cyclonic rift")
|| cn.equals("demonic tutor")
|| cn.equals("drannith magistrate")
|| cn.equals("enlightened tutor")
|| cn.equals("expropriate")
|| cn.equals("fierce guardianship")
|| cn.equals("force of will")
|| cn.equals("gaea's cradle")
|| cn.equals("glacial chasm")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("grim monolith")
|| cn.equals("imperial seal")
|| cn.equals("jeska's will")
|| cn.equals("jin-gitaxias, core augur")
|| cn.equals("kinnan, bonder prodigy")
|| cn.equals("lion's eye diamond")
|| cn.equals("mana vault")
|| cn.equals("mox diamond")
|| cn.equals("mystical tutor")
|| cn.equals("opposition agent")
|| cn.equals("rhystic study")
|| cn.equals("serra's sanctum")
|| cn.equals("smothering tithe")
|| cn.equals("survival of the fittest")
|| cn.equals("tergrid, god of fright")
|| cn.equals("thassa's oracle")
|| cn.equals("the one ring")
|| cn.equals("the tabernacle at pendrell vale")
|| cn.equals("trinisphere")
|| cn.equals("trouble in pairs")
|| cn.equals("underworld breach")
|| cn.equals("urza, lord high artificer")
|| cn.equals("vampiric tutor")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("winota, joiner of forces")
|| cn.equals("yuriko, the tiger's shadow")) {
thisMaxPower = Math.max(thisMaxPower, 20);
}
return thisMaxPower;
}

View file

@ -474,7 +474,7 @@ public final class GuiDisplayUtil {
public static void refreshThemeSettings() {
// apply Nimbus's look and fill
// possible settings:
// https://docs.oracle.com/en%2Fjava%2Fjavase%2F17%2Fdocs%2Fapi%2F%2F/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html
// https://docs.oracle.com/en/java/javase/23/docs/api/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html
// enable nimbus
try {

View file

@ -50,6 +50,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
private CardRenderer cardRenderer;
private int updateArtImageStamp;
private final int cardRenderMode;
private static class ImageKey {
final BufferedImage artImage;
@ -84,7 +85,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
sb.append((char) (this.view.isCanAttack() ? 1 : 0));
sb.append((char) (this.view.isCanBlock() ? 1 : 0));
sb.append((char) (this.view.isFaceDown() ? 1 : 0));
sb.append((char) this.view.getFrameStyle().ordinal());
sb.append((char) (this.view.getFrameStyle() != null ? this.view.getFrameStyle().ordinal() : -1));
if (this.view instanceof PermanentView) {
sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0));
sb.append((char) (((PermanentView) this.view).getDamage()));
@ -143,12 +144,13 @@ public class CardPanelRenderModeMTGO extends CardPanel {
}
public CardPanelRenderModeMTGO(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback,
final boolean foil, Dimension dimension, boolean needFullPermanentRender) {
final boolean foil, Dimension dimension, boolean needFullPermanentRender, int renderMode) {
// Call to super
super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender);
// Renderer
cardRenderer = cardRendererFactory.create(getGameCard());
cardRenderMode = renderMode;
cardRenderer = cardRendererFactory.create(getGameCard(), cardRenderMode);
// Draw the parts
initialDraw();
@ -265,7 +267,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
// Update renderer
cardImage = null;
cardRenderer = cardRendererFactory.create(getGameCard());
cardRenderer = cardRendererFactory.create(getGameCard(), cardRenderMode);
cardRenderer.setArtImage(artImage);
// Repaint

View file

@ -1,5 +1,8 @@
package org.mage.card.arcane;
import mage.cards.FrameStyle;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.CardRenderMode;
import mage.view.CardView;
/**
@ -11,10 +14,27 @@ public class CardRendererFactory {
}
public CardRenderer create(CardView card) {
return create(card, -1);
}
public CardRenderer create(CardView card, int renderModeOverride) {
if (card.isSplitCard()) {
return new ModernSplitCardRenderer(card);
} else if (shouldRenderRetro(card, renderModeOverride)) {
// TODO: implement split card renderer for retro cards
return new RetroCardRenderer(card);
} else {
return new ModernCardRenderer(card);
}
}
private static boolean shouldRenderRetro(CardView card, int renderModeOverride) {
int renderMode = PreferencesDialog.getRenderMode();
if (renderModeOverride != -1) {
renderMode = renderModeOverride;
}
boolean renderMTGO = (card.getFrameStyle().equals(FrameStyle.RETRO) || card.getFrameStyle().equals(FrameStyle.LEA_ORIGINAL_DUAL_LAND_ART_BASIC)) && renderMode == CardRenderMode.MTGO.ordinal();
boolean forcedRetro = renderMode == CardRenderMode.FORCED_RETRO.ordinal();
return renderMTGO || forcedRetro;
}
}

View file

@ -123,6 +123,7 @@ public class ModernCardRenderer extends CardRenderer {
public static final Color ERROR_COLOR = new Color(255, 0, 255);
static String SUB_TYPE_ADVENTURE = "Adventure";
static String SUB_TYPE_OMEN = "Omen";
///////////////////////////////////////////////////////////////////////////
// Layout metrics for modern border cards
@ -168,8 +169,8 @@ public class ModernCardRenderer extends CardRenderer {
// Processed mana cost string
protected String manaCostString;
// Is an adventure
protected boolean isAdventure = false;
// Is an adventure or omen
protected boolean isCardWithSpellOption = false;
public ModernCardRenderer(CardView card) {
// Pass off to parent
@ -179,12 +180,13 @@ public class ModernCardRenderer extends CardRenderer {
manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr());
if (cardView.isSplitCard()) {
isAdventure = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE);
isCardWithSpellOption = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE)
|| cardView.getRightSplitTypeLine().contains(SUB_TYPE_OMEN);
}
}
protected boolean isAdventure() {
return isAdventure;
protected boolean isCardWithSpellOption() {
return isCardWithSpellOption;
}
@Override
@ -316,7 +318,8 @@ public class ModernCardRenderer extends CardRenderer {
} else if (isUnstableFullArtLand()) {
rect = new Rectangle2D.Float(.0f, .0f, 1.0f, 1.0f);
} else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT ||
cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) {
cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT ||
cardView.getArtRect() == ArtRect.RETRO) {
rect = cardView.getArtRect().rect;
} else if (cardView.getFrameStyle().isFullArt() || (cardView.isToken())) {
rect = new Rectangle2D.Float(.079f, .11f, .84f, .63f);
@ -660,7 +663,7 @@ public class ModernCardRenderer extends CardRenderer {
drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2,
contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false);
} else if (isAdventure) {
} else if (isCardWithSpellOption) {
drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2,
contentWidth / 2 - 8, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3, false);

View file

@ -56,7 +56,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
private boolean isAftermath = false;
private static String trimAdventure(String rule) {
if (rule.startsWith("Adventure")) {
if (rule.startsWith("Adventure") || rule.startsWith("Omen")) {
return rule.substring(rule.lastIndexOf("&mdash;") + 8);
}
return rule;
@ -71,7 +71,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr());
leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr());
if (isAdventure()) {
if (isCardWithSpellOption()) {
List<String> trimmedRules = new ArrayList<>();
for (String rule : view.getRightSplitRules()) {
trimmedRules.add(trimAdventure(rule));
@ -95,7 +95,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
// they "rotate" in opposite directions making consquence and normal split cards
// have the "right" vs "left" as the top half.
// Adventures are treated differently and not rotated at all.
if (isAdventure()) {
if (isCardWithSpellOption()) {
manaCostString = leftHalf.manaCostString;
textboxKeywords = leftHalf.keywords;
textboxRules = leftHalf.rules;
@ -159,7 +159,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
protected void drawBackground(Graphics2D g) {
if (cardView.isFaceDown()) {
drawCardBackTexture(g);
} if (isAdventure()) {
} if (isCardWithSpellOption()) {
super.drawBackground(g);
} else {
{ // Left half background (top of the card)
@ -204,7 +204,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override
protected void drawArt(Graphics2D g) {
if (isAdventure) {
if (isCardWithSpellOption) {
super.drawArt(g);
} else if (artImage != null) {
if (isAftermath()) {
@ -318,7 +318,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) {
if (isAdventure()) {
if (isCardWithSpellOption()) {
super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox);
CardPanelAttributes adventureAttribs = new CardPanelAttributes(

File diff suppressed because it is too large Load diff

View file

@ -143,7 +143,7 @@ public final class TextboxRuleParser {
index += 5;
++outputIndex;
} else {
LOGGER.error("Bad &...; sequence `" + rule.substring(index + 1, index + 10) + "` in rule.");
LOGGER.error("Bad &...; sequence `" + rule.substring(index, Math.min(rule.length(), index + 10)) + "` in rule.");
build.append('&');
++index;
++outputIndex;

View file

@ -3,7 +3,7 @@ package org.mage.plugins.card;
import mage.cards.MageCard;
import mage.cards.MagePermanent;
import mage.cards.action.ActionCallback;
import mage.client.util.GUISizeHelper;
import mage.client.util.*;
import mage.interfaces.plugin.CardPlugin;
import mage.view.CardView;
import mage.view.CounterView;
@ -35,6 +35,8 @@ import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static mage.client.util.CardRenderMode.*;
/**
* {@link CardPlugin} implementation.
*
@ -102,16 +104,19 @@ public class CardPluginImpl implements CardPlugin {
* yet, so use old component based rendering for the split cards.
*/
private CardPanel makeCardPanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback,
boolean isFoil, Dimension dimension, int renderMode, boolean needFullPermanentRender) {
switch (renderMode) {
case 0:
boolean isFoil, Dimension dimension, int renderModeId, boolean needFullPermanentRender) {
CardRenderMode cardRenderMode = CardRenderMode.fromId(renderModeId);
switch (cardRenderMode) {
case MTGO:
case FORCED_M15:
case FORCED_RETRO:
return new CardPanelRenderModeMTGO(view, gameId, loadImage, callback, isFoil, dimension,
needFullPermanentRender);
case 1:
needFullPermanentRender, renderModeId);
case IMAGE:
return new CardPanelRenderModeImage(view, gameId, loadImage, callback, isFoil, dimension,
needFullPermanentRender);
default:
throw new IllegalStateException("Unknown render mode " + renderMode);
throw new IllegalStateException("Unknown render mode " + cardRenderMode);
}
}

View file

@ -204,7 +204,7 @@ public enum GrabbagImageSource implements CardImageSource {
singleLinks.put("SWS/Hazard Trooper", "ZOutamG.jpeg");
singleLinks.put("SWS/Head Hunting", "7OT1bGZ.jpeg");
singleLinks.put("SWS/Heavy Trooper", "HhZWs2N.jpeg");
singleLinks.put("SWS/Hot Pursuit", "ih1GT5Z.jpeg");
singleLinks.put("SWS/Hot Pursuit (Star Wars)", "ih1GT5Z.jpeg");
singleLinks.put("SWS/Hungry Dragonsnake", "23v7RTm.jpeg");
singleLinks.put("SWS/Hunt to Extinction", "3eJyfzZ.jpeg");
singleLinks.put("SWS/Hutt Crime Lord", "NAzK7Hp.jpeg");

View file

@ -41,6 +41,7 @@ public class ScryfallApiCard {
//public String watermark; // background watermark image for some cards
public void prepareCompatibleData() {
// take images from main card
if (this.image_uris != null) {
this.imageSmall = this.image_uris.getOrDefault("small", "");
this.imageNormal = this.image_uris.getOrDefault("normal", "");
@ -48,11 +49,12 @@ public class ScryfallApiCard {
this.image_uris = null;
}
// take first available images from one of the faces
if (this.card_faces != null) {
this.card_faces.forEach(ScryfallApiCardFace::prepareCompatibleData);
}
// workaround for adventure card name fix:
// workaround for adventure/omen card name fix:
// - scryfall: Ondu Knotmaster // Throw a Line
// - xmage: Ondu Knotmaster
if (this.layout.equals("adventure")) {
@ -88,11 +90,50 @@ public class ScryfallApiCard {
// - scryfall: Command Tower // Command Tower
// - xmage: Command Tower (second side as diff card and direct link image), example: https://scryfall.com/card/rex/26/command-tower-command-tower
if (this.layout.equals("reversible_card")) {
if (!this.card_faces.get(0).name.equals(this.card_faces.get(1).name)) {
throw new IllegalArgumentException("Scryfall: unsupported data type, reversible_card has diff faces "
+ this.set + " - " + this.collector_number + " - " + this.name);
if (false) {
// ignore
} else if (this.card_faces == null) {
// TODO: temporary fix, delete after scryfall site update
// broken adventure/omen card (scryfall changed it for some reason)
// Scavenger Regent // Exude Toxin
// https://scryfall.com/card/tdm/90/scavenger-regent-exude-toxin
if (this.name.contains("//")) {
throw new IllegalArgumentException("Scryfall: unsupported data type, broken reversible_card must have same simple name"
+ this.set + " - " + this.collector_number + " - " + this.name);
}
} else if (this.card_faces.get(0).layout.equals("reversible_card")) {
// TODO: temporary fix, delete after scryfall site update
// broken adventure/omen card (scryfall changed it for some reason)
// Bloomvine Regent // Claim Territory
// https://scryfall.com/card/tdm/381/bloomvine-regent-claim-territory-bloomvine-regent
this.name = this.card_faces.get(0).name;
} else if (this.card_faces.get(0).layout == null || this.card_faces.get(0).layout.equals("normal")) {
// simple card
// Command Tower // Command Tower
// https://scryfall.com/card/rex/26/command-tower-command-tower
if (!this.card_faces.get(0).name.equals(this.card_faces.get(1).name)) {
throw new IllegalArgumentException("Scryfall: unsupported data type, normal reversible_card must have same name in faces"
+ this.set + " - " + this.collector_number + " - " + this.name);
}
this.name = this.card_faces.get(0).name;
} else if (this.card_faces.get(0).layout.equals("adventure")) {
// adventure/omen card
// Bloomvine Regent // Claim Territory
// https://scryfall.com/card/tdm/381/bloomvine-regent-claim-territory-bloomvine-regent
this.name = this.card_faces.get(0).name;
if (this.card_faces.get(0).name.equals(this.card_faces.get(1).name)) {
throw new IllegalArgumentException("Scryfall: unsupported data type, adventure/omen's reversible_card must have diff names in faces "
+ this.set + " - " + this.collector_number + " - " + this.name);
}
} else if (this.card_faces.get(0).layout.equals("token")) {
// token (it's not used for xmage's tokens, but must pass checks here anyway)
// Mechtitan // Mechtitan
// https://scryfall.com/card/sld/1969/mechtitan-mechtitan
this.name = this.card_faces.get(0).name;
} else {
throw new IllegalArgumentException("Scryfall: unsupported layout type in reversible_card - "
+ this.card_faces.get(0).layout + " - " + this.set + " - " + this.collector_number + " - " + this.name);
}
this.name = this.card_faces.get(0).name;
}
// workaround for non ascii names
@ -104,7 +145,6 @@ public class ScryfallApiCard {
// - scryfall uses unicode numbers for reprints like Chandra Nalaar - dd2 - 34 https://scryfall.com/card/dd2/34%E2%98%85/
// - xmage uses ascii alternative Chandra Nalaar - dd2 - 34*
this.collector_number = transformCardNumberFromScryfallToXmage(this.collector_number);
}
public static String transformCardNumberFromXmageToScryfall(String cardNumber) {

View file

@ -12,6 +12,7 @@ import java.util.Map;
*/
public class ScryfallApiCardFace {
public String name;
public String layout;
public Map<String, String> image_uris;
// fast access fields, fills on loading

View file

@ -504,7 +504,7 @@ public class ScryfallImageSource implements CardImageSource {
jsonReader.close();
return bulkCardsDatabaseAll.size() > 0;
} catch (Exception e) {
logger.error("Can't read bulk file (possible reason: broken format)");
logger.error("Can't read bulk file (possible reason: broken scryfall format), details: " + e, e);
try {
// clean up
if (!SCRYFALL_BULK_FILES_DEBUG_READ_ONLY_MODE) {

View file

@ -145,7 +145,7 @@ public class ScryfallImageSupportCards {
add("PAL06"); // Arena League 2006
//add("PMPS06"); // Magic Premiere Shop 2006
add("PHUK"); // Hachette UK
add("PDCI"); // DCI Promos
add("DCI"); // DCI Promos
add("P06"); // Magic Player Rewards 2006
add("G06"); // Judge Gift Cards 2006
add("F06"); // Friday Night Magic 2006
@ -190,7 +190,6 @@ public class ScryfallImageSupportCards {
add("P09"); // Magic Player Rewards 2009
add("G09"); // Judge Gift Cards 2009
add("F09"); // Friday Night Magic 2009
add("PBOOK"); // Miscellaneous Book Promos
add("CON"); // Conflux
add("DDC"); // Duel Decks: Divine vs. Demonic
add("ARB"); // Alara Reborn
@ -405,7 +404,7 @@ public class ScryfallImageSupportCards {
add("XANA"); // Arena New Player Experience Extras
add("OANA"); // Arena New Player Experience Cards
add("PS18"); // San Diego Comic-Con 2018
add("PH17"); // Heroes of the Realm 2017
add("PH17"); // 2017 Heroes of the Realm
add("C18"); // Commander 2018
add("PGRN"); // Guilds of Ravnica Promos
add("PRWK"); // GRN Ravnica Weekend
@ -440,7 +439,7 @@ public class ScryfallImageSupportCards {
add("ELD"); // Throne of Eldraine
//add("PTG"); // Ponies: The Galloping
add("CMB1"); // Mystery Booster Playtest Cards 2019
//add("MB1"); // Mystery Booster
add("MB1"); // Mystery Booster
add("GN2"); // Game Night 2019
add("HA1"); // Historic Anthology 1
//add("HHO"); // Happy Holidays
@ -457,7 +456,7 @@ public class ScryfallImageSupportCards {
//add("FMB1"); // Mystery Booster Retail Edition Foils
add("HA2"); // Historic Anthology 2
add("SLD"); // Secret Lair Drop
add("PMEI"); // Magazine Inserts
add("PMEI"); // Media and Collaboration Promos
add("SLU"); // Secret Lair: Ultimate Edition
add("SS3"); // Signature Spellbook: Chandra
add("HA3"); // Historic Anthology 3
@ -467,7 +466,7 @@ public class ScryfallImageSupportCards {
// add("DD3"); // Duel Decks Anthology
// add("PZ1"); // Legendary Cube
add("IKO"); // Ikoria: Lair of Behemoths
add("C20"); // Commander 2020 Edition
add("C20"); // Commander 2020
add("M21"); // Core Set 2021
add("JMP"); // Jumpstart
add("PH19"); // 2019 Heroes of the Realm
@ -480,6 +479,7 @@ public class ScryfallImageSupportCards {
add("KLR"); // Kaladesh Remastered
add("CMR"); // Commander Legends
add("CC1"); // Commander Collection: Green
add("PJ21"); // Judge Gift Cards 2021
add("PL21"); // Year of the Ox 2021
add("KHM"); // Kaldheim
add("KHC"); // Kaldheim Commander
@ -488,9 +488,10 @@ public class ScryfallImageSupportCards {
add("STA"); // Strixhaven Mystical Archive
add("HA4"); // Historic Anthology 4
add("HA5"); // Historic Anthology 5
add("C21"); // Commander 2021 Edition
add("C21"); // Commander 2021
add("MH2"); // Modern Horizons 2
add("H1R"); // Modern Horizons 1 Timeshifts
add("PW21"); // Wizards Play Network 2021
add("PLG21"); // Love Your LGS 2021
add("AFR"); // Adventures in the Forgotten Realms
add("AFC"); // Forgotten Realms Commander
@ -500,12 +501,15 @@ public class ScryfallImageSupportCards {
add("VOW"); // Innistrad: Crimson Vow
add("VOC"); // Crimson Vow Commander
add("YMID"); // Alchemy: Innistrad
add("P22"); // Judge Gift Cards 2022
add("DBL"); // Innistrad: Double Feature
add("CC2"); // Commander Collection: Black
add("NEO"); // Kamigawa: Neon Dynasty
add("YNEO"); // Alchemy: Kamigawa
add("NEC"); // Neon Dynasty Commander
add("PL22"); // Year of the Tiger 2022
add("PW22"); // Wizards Play Network 2022
add("GDY"); // Game Day Promos
add("SNC"); // Streets of New Capenna
add("NCC"); // New Capenna Commander
add("SLX"); // Universes Within
@ -514,7 +518,7 @@ public class ScryfallImageSupportCards {
add("DMU"); // Dominaria United
add("DMC"); // Dominaria United Commander
add("YDMU"); // Alchemy: Dominaria
add("40K"); // Warhammer 40,000
add("40K"); // Warhammer 40,000 Commander
add("UNF"); // Unfinity
add("GN3"); // Game Night: Free-for-All
add("BRO"); // The Brothers' War
@ -523,17 +527,25 @@ public class ScryfallImageSupportCards {
add("BOT"); // Transformers
add("J22"); // Jumpstart 2022
add("SCD"); // Starter Commander Decks
add("PW23"); // Wizards Play Network 2023
add("P23"); // Judge Gift Cards 2023
add("SLC"); // Secret Lair 30th Anniversary Countdown Kit
add("DMR"); // Dominaria Remastered
add("ONE"); // Phyrexia: All Will Be One
add("ONC"); // Phyrexia: All Will Be One Commander
add("PL23"); // Year of the Rabbit 2023
add("UNK"); // Unknown Event
add("SIS"); // Shadows of the Past
add("SIR"); // Shadows over Innistrad Remastered
add("SLP"); // Secret Lair Showdown
add("MOM"); // March of the Machine
add("MOC"); // March of the Machine Commander
add("MAT"); // March of the Machine: The Aftermath
add("MUL"); // Multiverse Legends
add("30A"); // Thirtieth Anniversary Edition
add("30A"); // 30th Anniversary Edition
add("P30A"); // 30th Anniversary Play Promos
add("P30M"); // 30th Anniversary Misc Promos
add("PEWK"); // Eternal Weekend
add("LTR"); // The Lord of the Rings: Tales of Middle-Earth
add("LTC"); // Tales of Middle-Earth Commander
add("CMM"); // Commander Masters
@ -542,9 +554,10 @@ public class ScryfallImageSupportCards {
add("WOT"); // Wilds of Eldraine: Enchanting Tales
add("WOC"); // Wilds of Eldraine Commander
add("LCI"); // The Lost Caverns of Ixalan
add("LCC"); // The Lost Caverns of Ixalan Commander
add("LCC"); // The The Lost Caverns of Ixalan Commander
add("REX"); // Jurassic World Collection
add("SPG"); // Special Guests
add("PW24"); // Wizards Play Network 2024
add("RVR"); // Ravnica Remastered
add("PIP"); // Fallout
add("MKM"); // Murders at Karlov Manor
@ -563,6 +576,19 @@ public class ScryfallImageSupportCards {
add("DSK"); // Duskmourn: House of Horror
add("DSC"); // Duskmourn: House of Horror Commander
add("FDN"); // Foundations
add("J25"); // Foundations Jumpstart
add("PIO"); // Pioneer Masters
add("PW25"); // Wizards Play Network 2025
add("INR"); // Innistrad Remastered
add("PF25"); // MagicFest 2025
add("DFT"); // Aetherdrift
add("DRC"); // Aetherdrift Commander
add("TDM"); // Tarkir: Dragonstorm
add("TDC"); // Tarkir: Dragonstorm Commander
add("FIN"); // Final Fantasy
add("FIC"); // Final Fantasy Commander
add("FCA"); // Final Fantasy: Through the Ages
add("SPE"); // Marvel's Spider-Man Eternal
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
add("CALC"); // Custom Alchemized versions of existing cards
@ -609,46 +635,85 @@ public class ScryfallImageSupportCards {
// SLD
// fake double faced cards
put("SLD/Adrix and Nev, Twincasters/1544b", "https://api.scryfall.com/cards/sld/1544/en?format=image&face=back");
put("SLD/Aesi, Tyrant of Gyre Strait/1873b", "https://api.scryfall.com/cards/sld/1873/en?format=image&face=back");
put("SLD/Ajani Goldmane/745b", "https://api.scryfall.com/cards/sld/745/en?format=image&face=back");
put("SLD/Ajani Goldmane/1453b", "https://api.scryfall.com/cards/sld/1453/en?format=image&face=back");
put("SLD/Anointed Procession/1511b", "https://api.scryfall.com/cards/sld/1453/en?format=image&face=back");
put("SLD/Anje Falkenrath/1874b", "https://api.scryfall.com/cards/sld/1874/en?format=image&face=back");
put("SLD/Anointed Procession/1511b", "https://api.scryfall.com/cards/sld/1511/en?format=image&face=back");
put("SLD/Birds of Paradise/1675b", "https://api.scryfall.com/cards/sld/1675/en?format=image&face=back");
put("SLD/Blightsteel Colossus/1079b", "https://api.scryfall.com/cards/sld/1079/en?format=image&face=back");
put("SLD/Chandra Nalaar/748b", "https://api.scryfall.com/cards/sld/748/en?format=image&face=back");
put("SLD/Chandra Nalaar/1456b", "https://api.scryfall.com/cards/sld/1456/en?format=image&face=back");
put("SLD/Chulane, Teller of Tales/1875b", "https://api.scryfall.com/cards/sld/1875/en?format=image&face=back");
put("SLD/Darksteel Colossus/1081b", "https://api.scryfall.com/cards/sld/1081/en?format=image&face=back");
put("SLD/Death Baron/1458b", "https://api.scryfall.com/cards/sld/1458/en?format=image&face=back");
put("SLD/Doubling Cube/1080b", "https://api.scryfall.com/cards/sld/1080/en?format=image&face=back");
put("SLD/Dragonlord Atarka/1970b", "https://api.scryfall.com/cards/sld/1970/en?format=image&face=back");
put("SLD/Dragonlord Dromoka/1971b", "https://api.scryfall.com/cards/sld/1971/en?format=image&face=back");
put("SLD/Dragonlord Kolaghan/1972b", "https://api.scryfall.com/cards/sld/1972/en?format=image&face=back");
put("SLD/Dragonlord Ojutai/1973b", "https://api.scryfall.com/cards/sld/1973/en?format=image&face=back");
put("SLD/Dragonlord Silumgar/1974b", "https://api.scryfall.com/cards/sld/1974/en?format=image&face=back");
put("SLD/Estrid's Invocation/1325b", "https://api.scryfall.com/cards/sld/1325/en?format=image&face=back");
put("SLD/Estrid, the Masked/1327b", "https://api.scryfall.com/cards/sld/1327/en?format=image&face=back");
put("SLD/Etali, Primal Storm/1123b", "https://api.scryfall.com/cards/sld/1123/en?format=image&face=back");
put("SLD/Garruk Wildspeaker/749b", "https://api.scryfall.com/cards/sld/749/en?format=image&face=back");
put("SLD/Garruk Wildspeaker/1457b", "https://api.scryfall.com/cards/sld/1457/en?format=image&face=back");
put("SLD/Ghalta, Primal Hunger/1124b", "https://api.scryfall.com/cards/sld/1124/en?format=image&face=back");
put("SLD/Grimgrin, Corpse-Born/1461b", "https://api.scryfall.com/cards/sld/1461/en?format=image&face=back");
put("SLD/Jace Beleren/746b", "https://api.scryfall.com/cards/sld/746/en?format=image&face=back");
put("SLD/Jace Beleren/1454b", "https://api.scryfall.com/cards/sld/1454/en?format=image&face=back");
put("SLD/Jetmir, Nexus of Revels/1509b", "https://api.scryfall.com/cards/sld/1509/en?format=image&face=back");
put("SLD/Jetmir, Nexus of Revels/1555b", "https://api.scryfall.com/cards/sld/1555/en?format=image&face=back");
put("SLD/Jinnie Fay, Jetmir's Second/1510b", "https://api.scryfall.com/cards/sld/1510/en?format=image&face=back");
put("SLD/Jinnie Fay, Jetmir's Second/1556b", "https://api.scryfall.com/cards/sld/1556/en?format=image&face=back");
put("SLD/Kardur, Doomscourge/1807b", "https://api.scryfall.com/cards/sld/1807/en?format=image&face=back");
put("SLD/Krark's Thumb/383b", "https://api.scryfall.com/cards/sld/383/en?format=image&face=back");
put("SLD/Krark, the Thumbless/1543b", "https://api.scryfall.com/cards/sld/1543/en?format=image&face=back");
put("SLD/Liliana Vess/747b", "https://api.scryfall.com/cards/sld/747/en?format=image&face=back");
put("SLD/Liliana Vess/1455b", "https://api.scryfall.com/cards/sld/1455/en?format=image&face=back");
put("SLD/Mechtitan Core/1969b", "https://api.scryfall.com/cards/sld/1969/en?format=image&face=back");
put("SLD/Mechtitan Core/1965b", "https://api.scryfall.com/cards/sld/1965/en?format=image&face=back");
put("SLD/Norin the Wary/827b", "https://api.scryfall.com/cards/sld/827/en?format=image&face=back");
put("SLD/Noxious Ghoul/1459b", "https://api.scryfall.com/cards/sld/1459/en?format=image&face=back");
put("SLD/Okaun, Eye of Chaos/380b", "https://api.scryfall.com/cards/sld/380/en?format=image&face=back");
put("SLD/Okaun, Eye of Chaos/380b*", "https://api.scryfall.com/cards/sld/380★/en?format=image&face=back");
put("SLD/Okaun, Eye of Chaos/380*b", "https://api.scryfall.com/cards/sld/380★/en?format=image&face=back");
put("SLD/Parhelion II/1964b", "https://api.scryfall.com/cards/sld/1964/en?format=image&face=back");
put("SLD/Peacewalker Colossus/1966b", "https://api.scryfall.com/cards/sld/1966/en?format=image&face=back");
put("SLD/Propaganda/381b", "https://api.scryfall.com/cards/sld/381/en?format=image&face=back");
put("SLD/Radha, Heart of Keld/1876b", "https://api.scryfall.com/cards/sld/1876/en?format=image&face=back");
put("SLD/Reckoner Bankbuster/1967b", "https://api.scryfall.com/cards/sld/1967/en?format=image&face=back");
put("SLD/Rin and Seri, Inseparable/1508b", "https://api.scryfall.com/cards/sld/1508/en?format=image&face=back");
put("SLD/Rin and Seri, Inseparable/1554b", "https://api.scryfall.com/cards/sld/1554/en?format=image&face=back");
put("SLD/Sakashima of a Thousand Faces/1541b", "https://api.scryfall.com/cards/sld/1541/en?format=image&face=back");
put("SLD/Smuggler's Copter/1968b", "https://api.scryfall.com/cards/sld/1968/en?format=image&face=back");
put("SLD/Sol Ring/1512b", "https://api.scryfall.com/cards/sld/1512/en?format=image&face=back");
put("SLD/Steely Resolve/1326b", "https://api.scryfall.com/cards/sld/1326/en?format=image&face=back");
put("SLD/Stitch in Time/382b", "https://api.scryfall.com/cards/sld/382/en?format=image&face=back");
put("SLD/Terror/750b", "https://api.scryfall.com/cards/sld/750/en?format=image&face=back");
put("SLD/Tuvasa the Sunlit/1328b", "https://api.scryfall.com/cards/sld/1328/en?format=image&face=back");
put("SLD/Ulamog, the Ceaseless Hunger/1122b", "https://api.scryfall.com/cards/sld/1122/en?format=image&face=back");
put("SLD/Unholy Grotto/1462b", "https://api.scryfall.com/cards/sld/1462/en?format=image&face=back");
put("SLD/Yargle, Glutton of Urborg/1542b", "https://api.scryfall.com/cards/sld/1542/en?format=image&face=back");
put("SLD/Zndrsplt, Eye of Wisdom/379b", "https://api.scryfall.com/cards/sld/379/en?format=image&face=back");
put("SLD/Zndrsplt, Eye of Wisdom/379b*", "https://api.scryfall.com/cards/sld/379★/en?format=image&face=back");
put("SLD/Zndrsplt, Eye of Wisdom/379*b", "https://api.scryfall.com/cards/sld/379★/en?format=image&face=back");
put("SLD/Zombie Master/1460b", "https://api.scryfall.com/cards/sld/1460/en?format=image&face=back");
// normal cards
put("SLD/Counterspell/99999SCTLR", "https://api.scryfall.com/cards/sld/SCTLR/"); // see issue 11157
put("SLD/Viscera Seer/99999VS", "https://api.scryfall.com/cards/sld/VS/"); // see issue 11157
// CALC - custom alchemy version of cards.
put("CALC/C-Pillar of the Paruns", "https://api.scryfall.com/cards/dis/176/");
// MB1
put("MB1/Goblin Trenches", "https://api.scryfall.com/cards/plst/EMA-203/");
put("MB1/Prophetic Bolt", "https://api.scryfall.com/cards/plst/C15-231/");
// LTR - 0 number for tokens only
put("LTR/The One Ring/001", "https://api.scryfall.com/cards/ltr/0/");
// Scryfall has a bug, for some reason this link doesn't work with ?format=image even though it works with ?format=json
// and ?format=text. Base url fails because language is qya and not en and alternate url fails because of this bug
// TODO: This should be reverted when Scryfall fixes the bug
// put("LTR/The One Ring/001", "https://api.scryfall.com/cards/ltr/0/");
put("LTR/The One Ring/001", "https://api.scryfall.com/cards/ltr/0/qya?format=image");
// REX - double faced lands (xmage uses two diff lands for it)
put("REX/Command Tower/26b", "https://api.scryfall.com/cards/rex/26/en?format=image&face=back");
@ -657,6 +722,15 @@ public class ScryfallImageSupportCards {
put("REX/Mountain/24b", "https://api.scryfall.com/cards/rex/24/en?format=image&face=back");
put("REX/Plains/21b", "https://api.scryfall.com/cards/rex/21/en?format=image&face=back");
put("REX/Swamp/23b", "https://api.scryfall.com/cards/rex/23/en?format=image&face=back");
// TDM - fake double faced cards
put("TDM/Bloomvine Regent/381b", "https://api.scryfall.com/cards/tdm/381/en?format=image&face=back");
put("TDM/Clarion Conqueror/377b", "https://api.scryfall.com/cards/tdm/377/en?format=image&face=back");
put("TDM/Magmatic Hellkite/380b", "https://api.scryfall.com/cards/tdm/380/en?format=image&face=back");
put("TDM/Marang River Regent/378b", "https://api.scryfall.com/cards/tdm/378/en?format=image&face=back");
put("TDM/Scavenger Regent/379b", "https://api.scryfall.com/cards/tdm/379/en?format=image&face=back");
put("TDM/Ugin, Eye of the Storms/382b", "https://api.scryfall.com/cards/tdm/382/en?format=image&face=back");
}
};

View file

@ -39,7 +39,8 @@ public class ScryfallImageSupportTokens {
putAll(TokenRepository.instance.prepareScryfallDownloadList());
// RIX
put("RIX/City's Blessing", "https://api.scryfall.com/cards/trix/6/en?format=image"); // TODO: missing from tokens data
// TODO: this should be readded when condition tokens are implemented
// put("RIX/City's Blessing", "https://api.scryfall.com/cards/trix/6/en?format=image");
put("RIX/Elemental/1", "https://api.scryfall.com/cards/trix/1/en?format=image");
put("RIX/Elemental/2", "https://api.scryfall.com/cards/trix/2/en?format=image");
put("RIX/Golem", "https://api.scryfall.com/cards/trix/4/en?format=image");
@ -61,7 +62,7 @@ public class ScryfallImageSupportTokens {
put("RNA/Treasure", "https://api.scryfall.com/cards/trna/12/en?format=image");
put("RNA/Zombie", "https://api.scryfall.com/cards/trna/3/en?format=image");
//GRN
// GRN
put("GRN/Angel", "https://api.scryfall.com/cards/tgrn/1/en?format=image");
put("GRN/Bird Illusion", "https://api.scryfall.com/cards/tgrn/3/en?format=image");
put("GRN/Elf Knight", "https://api.scryfall.com/cards/tgrn/6/en?format=image");
@ -71,7 +72,7 @@ public class ScryfallImageSupportTokens {
put("GRN/Soldier", "https://api.scryfall.com/cards/tgrn/2/en?format=image");
put("GRN/Emblem Vraska", "https://api.scryfall.com/cards/tgrn/8/en?format=image");
//DOM
// DOM
put("DOM/Cleric", "https://api.scryfall.com/cards/tdom/4/en?format=image");
put("DOM/Construct", "https://api.scryfall.com/cards/tdom/14/en?format=image");
put("DOM/Demon", "https://api.scryfall.com/cards/tdom/7/en?format=image");
@ -89,7 +90,7 @@ public class ScryfallImageSupportTokens {
put("DOM/Emblem Teferi", "https://api.scryfall.com/cards/tdom/16/en?format=image");
put("DOM/Zombie Knight", "https://api.scryfall.com/cards/tdom/5/en?format=image");
//XLN
// XLN
put("XLN/Dinosaur", "https://api.scryfall.com/cards/txln/5/en?format=image");
put("XLN/Illusion", "https://api.scryfall.com/cards/txln/2/en?format=image");
put("XLN/Merfolk", "https://api.scryfall.com/cards/txln/3/en?format=image");
@ -101,12 +102,12 @@ public class ScryfallImageSupportTokens {
put("XLN/Treasure/4", "https://api.scryfall.com/cards/txln/10/en?format=image");
put("XLN/Vampire", "https://api.scryfall.com/cards/txln/1/en?format=image");
//HOU
// HOU
put("HOU/Horse", "https://api.scryfall.com/cards/thou/10/en?format=image");
put("HOU/Insect", "https://api.scryfall.com/cards/thou/12/en?format=image");
put("HOU/Snake", "https://api.scryfall.com/cards/thou/11/en?format=image");
//AKH - tokens
// AKH - tokens
put("AKH/Beast", "https://api.scryfall.com/cards/takh/21/en?format=image");
put("AKH/Cat", "https://api.scryfall.com/cards/takh/16/en?format=image");
put("AKH/Drake", "https://api.scryfall.com/cards/takh/18/en?format=image");
@ -117,30 +118,14 @@ public class ScryfallImageSupportTokens {
put("AKH/Warrior", "https://api.scryfall.com/cards/takh/17/en?format=image");
put("AKH/Wurm", "https://api.scryfall.com/cards/takh/24/en?format=image");
put("AKH/Zombie", "https://api.scryfall.com/cards/takh/20/en?format=image");
//AKH - embalm ability (token from card)
put("AKH/Angel of Sanctions", "https://api.scryfall.com/cards/takh/1/en?format=image");
put("AKH/Anointer Priest", "https://api.scryfall.com/cards/takh/2/en?format=image");
put("AKH/Aven Initiate", "https://api.scryfall.com/cards/takh/3/en?format=image");
put("AKH/Aven Wind Guide", "https://api.scryfall.com/cards/takh/4/en?format=image");
put("AKH/Glyph Keeper", "https://api.scryfall.com/cards/takh/5/en?format=image");
put("AKH/Heart-Piercer Manticore", "https://api.scryfall.com/cards/takh/6/en?format=image");
put("AKH/Honored Hydra", "https://api.scryfall.com/cards/takh/7/en?format=image");
put("AKH/Labyrinth Guardian", "https://api.scryfall.com/cards/takh/8/en?format=image");
put("AKH/Oketra's Attendant", "https://api.scryfall.com/cards/takh/9/en?format=image");
put("AKH/Sacred Cat", "https://api.scryfall.com/cards/takh/10/en?format=image");
put("AKH/Tah-Crop Skirmisher", "https://api.scryfall.com/cards/takh/11/en?format=image");
put("AKH/Temmet, Vizier of Naktamun", "https://api.scryfall.com/cards/takh/12/en?format=image");
put("AKH/Trueheart Duelist", "https://api.scryfall.com/cards/takh/13/en?format=image");
put("AKH/Unwavering Initiate", "https://api.scryfall.com/cards/takh/14/en?format=image");
put("AKH/Vizier of Many Faces", "https://api.scryfall.com/cards/takh/15/en?format=image");
//AER
// AER
put("AER/Etherium Cell", "https://api.scryfall.com/cards/taer/3/en?format=image");
put("AER/Gremlin", "https://api.scryfall.com/cards/taer/1/en?format=image");
put("AER/Ragavan", "https://api.scryfall.com/cards/taer/2/en?format=image");
put("AER/Emblem Tezzeret", "https://api.scryfall.com/cards/taer/4/en?format=image");
//KLD
// KLD
put("KLD/Beast", "https://api.scryfall.com/cards/tkld/1/en?format=image");
put("KLD/Emblem Chandra", "https://api.scryfall.com/cards/tkld/10/en?format=image");
put("KLD/Construct/1", "https://api.scryfall.com/cards/tkld/2/en?format=image");
@ -154,7 +139,7 @@ public class ScryfallImageSupportTokens {
put("KLD/Thopter/2", "https://api.scryfall.com/cards/tkld/8/en?format=image");
put("KLD/Thopter/3", "https://api.scryfall.com/cards/tkld/9/en?format=image");
//EMN
// EMN
put("EMN/Eldrazi Horror", "https://api.scryfall.com/cards/temn/1/en?format=image");
put("EMN/Human", "https://api.scryfall.com/cards/temn/7/en?format=image");
put("EMN/Human Wizard", "https://api.scryfall.com/cards/temn/2/en?format=image");
@ -166,7 +151,7 @@ public class ScryfallImageSupportTokens {
put("EMN/Zombie/3", "https://api.scryfall.com/cards/temn/5/en?format=image");
put("EMN/Zombie/4", "https://api.scryfall.com/cards/temn/6/en?format=image");
//SOI
// SOI
put("SOI/Angel", "https://api.scryfall.com/cards/tsoi/1/en?format=image");
put("SOI/Emblem Arlinn", "https://api.scryfall.com/cards/tsoi/18/en?format=image");
put("SOI/Clue/1", "https://api.scryfall.com/cards/tsoi/11/en?format=image");
@ -186,7 +171,7 @@ public class ScryfallImageSupportTokens {
put("SOI/Wolf", "https://api.scryfall.com/cards/tsoi/9/en?format=image");
put("SOI/Zombie", "https://api.scryfall.com/cards/tsoi/5/en?format=image");
//OGW
// OGW
put("OGW/Angel", "https://api.scryfall.com/cards/togw/7/en?format=image");
put("OGW/Eldrazi Scion/1", "https://api.scryfall.com/cards/togw/1/en?format=image");
put("OGW/Eldrazi Scion/2", "https://api.scryfall.com/cards/togw/2/en?format=image");
@ -199,7 +184,7 @@ public class ScryfallImageSupportTokens {
put("OGW/Plant", "https://api.scryfall.com/cards/togw/11/en?format=image");
put("OGW/Zombie", "https://api.scryfall.com/cards/togw/8/en?format=image");
//BFZ
// BFZ
put("BFZ/Dragon", "https://api.scryfall.com/cards/tbfz/8/en?format=image");
put("BFZ/Eldrazi", "https://api.scryfall.com/cards/tbfz/1/en?format=image");
put("BFZ/Eldrazi Scion/1", "https://api.scryfall.com/cards/tbfz/2/en?format=image");
@ -319,7 +304,7 @@ public class ScryfallImageSupportTokens {
put("C18/Worm", "https://api.scryfall.com/cards/tc18/18/en?format=image");
put("C18/Zombie", "https://api.scryfall.com/cards/tc18/9/en?format=image");
//C19
// C19
put("C19/Assassin", "https://api.scryfall.com/cards/tc19/9/en?format=image");
put("C19/Beast/1", "https://api.scryfall.com/cards/tc19/13/en?format=image");
put("C19/Beast/2", "https://api.scryfall.com/cards/tc19/14/en?format=image");
@ -501,7 +486,7 @@ public class ScryfallImageSupportTokens {
put("ZNC/Elemental/1", "https://api.scryfall.com/cards/tznc/10/en?format=image"); // 5/5
put("ZNC/Elemental/2", "https://api.scryfall.com/cards/tznc/8/en?format=image"); // 2/2
put("ZNC/Faerie Rogue", "https://api.scryfall.com/cards/tznc/3/en?format=image");
put("ZNC/Germ", "https://api.scryfall.com/cards/tznc/4/en?format=image"); // must be in chest or antology
put("ZNC/Phyrexian Germ", "https://api.scryfall.com/cards/tznc/4/en?format=image"); // must be in chest or antology
put("ZNC/Goblin Rogue", "https://api.scryfall.com/cards/tznc/5/en?format=image");
put("ZNC/Kor Ally", "https://api.scryfall.com/cards/tznc/2/en?format=image");
put("ZNC/Rat", "https://api.scryfall.com/cards/tznc/6/en?format=image");
@ -596,7 +581,6 @@ public class ScryfallImageSupportTokens {
put("C21/Beast/1", "https://api.scryfall.com/cards/tc21/10/en?format=image"); // 3/3
put("C21/Beast/2", "https://api.scryfall.com/cards/tc21/11/en?format=image"); // 4/4
put("C21/Boar", "https://api.scryfall.com/cards/tc21/12/en?format=image");
put("C21/Champion of Wits", "https://api.scryfall.com/cards/tc21/6/en?format=image");
put("C21/Construct/1", "https://api.scryfall.com/cards/tc21/22/en?format=image"); // x/x
put("C21/Construct/2", "https://api.scryfall.com/cards/tc21/23/en?format=image"); // 0/0
put("C21/Demon", "https://api.scryfall.com/cards/tc21/7/en?format=image");
@ -829,17 +813,45 @@ public class ScryfallImageSupportTokens {
put("NEC/Thopter", "https://api.scryfall.com/cards/tnec/12/en?format=image");
// SLD
put("SLD/Angel", "https://api.scryfall.com/cards/sld/1340?format=image");
put("SLD/Cat/1", "https://api.scryfall.com/cards/sld/1517?format=image");
put("SLD/Cat/2", "https://api.scryfall.com/cards/sld/27?format=image");
put("SLD/Cat/3", "https://api.scryfall.com/cards/sld/28?format=image");
put("SLD/Clue", "https://api.scryfall.com/cards/sld/348/en?format=image");
put("SLD/Dog", "https://api.scryfall.com/cards/sld/1516?format=image");
put("SLD/Egg", "https://api.scryfall.com/cards/sld/1398?format=image");
put("SLD/Faerie Rogue/1", "https://api.scryfall.com/cards/sld/13/en?format=image");
put("SLD/Faerie Rogue/2", "https://api.scryfall.com/cards/sld/14/en?format=image");
put("SLD/Faerie Rogue/3", "https://api.scryfall.com/cards/sld/15/en?format=image");
put("SLD/Faerie Rogue/4", "https://api.scryfall.com/cards/sld/16/en?format=image");
put("SLD/Treasure", "https://api.scryfall.com/cards/sld/153/en?format=image");
put("SLD/Food/1", "https://api.scryfall.com/cards/sld/1938?format=image");
put("SLD/Food/2", "https://api.scryfall.com/cards/sld/2010?format=image");
put("SLD/Food/3", "https://api.scryfall.com/cards/sld/2011?format=image");
put("SLD/Food/4", "https://api.scryfall.com/cards/sld/2012?format=image");
put("SLD/Food/5", "https://api.scryfall.com/cards/sld/2013?format=image");
put("SLD/Goblin", "https://api.scryfall.com/cards/sld/219?format=image");
put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?format=image");
put("SLD/Icingdeath, Frost Tongue", "https://api.scryfall.com/cards/sld/1018?format=image");
put("SLD/Marit Lage", "https://api.scryfall.com/cards/sld/1681?format=image");
put("SLD/Mechtitan", "https://api.scryfall.com/cards/sld/1969?format=image");
put("SLD/Saproling", "https://api.scryfall.com/cards/sld/1139?format=image");
put("SLD/Shrine", "https://api.scryfall.com/cards/sld/1835?format=image");
put("SLD/Spirit/1", "https://api.scryfall.com/cards/sld/1341?format=image");
put("SLD/Spirit/2", "https://api.scryfall.com/cards/sld/1852?format=image");
put("SLD/Squirrel", "https://api.scryfall.com/cards/sld/200?format=image");
put("SLD/Treasure/1", "https://api.scryfall.com/cards/sld/1432/en?format=image");
put("SLD/Treasure/2", "https://api.scryfall.com/cards/sld/1736/en?format=image");
put("SLD/Treasure/3", "https://api.scryfall.com/cards/sld/1507/en?format=image");
put("SLD/Treasure/4", "https://api.scryfall.com/cards/sld/153/en?format=image");
put("SLD/Walker/1", "https://api.scryfall.com/cards/sld/148/en?format=image");
put("SLD/Walker/2", "https://api.scryfall.com/cards/sld/149/en?format=image");
put("SLD/Walker/3", "https://api.scryfall.com/cards/sld/150/en?format=image");
put("SLD/Walker/4", "https://api.scryfall.com/cards/sld/151/en?format=image");
put("SLD/Walker/5", "https://api.scryfall.com/cards/sld/152/en?format=image");
put("SLD/Warrior", "https://api.scryfall.com/cards/sld/1752?format=image");
put("SLD/Wolf", "https://api.scryfall.com/cards/sld/1613?format=image");
put("SLD/Wurm", "https://api.scryfall.com/cards/sld/1306?format=image");
put("SLD/Zombie", "https://api.scryfall.com/cards/sld/1357?format=image");
// 2XM
put("2XM/Angel", "https://api.scryfall.com/cards/t2xm/3/en?format=image");
@ -1707,6 +1719,7 @@ public class ScryfallImageSupportTokens {
put("CLB/Squid", "https://api.scryfall.com/cards/tclb/29/en?format=image");
put("CLB/Squirrel", "https://api.scryfall.com/cards/tclb/15/en?format=image");
put("CLB/Treasure", "https://api.scryfall.com/cards/tclb/17/en?format=image");
put("CLB/Undercity", "https://api.scryfall.com/cards/tclb/20/en?format=image");
put("CLB/Volo's Journal", "https://api.scryfall.com/cards/tclb/18/en?format=image");
put("CLB/Warrior", "https://api.scryfall.com/cards/tclb/32/en?format=image");
put("CLB/Emblem Will Kenrith", "https://api.scryfall.com/cards/tclb/50/en?format=image");
@ -2167,11 +2180,36 @@ public class ScryfallImageSupportTokens {
put("WOC/Spirit", "https://api.scryfall.com/cards/twoc/17/en?format=image");
put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/3/en?format=image");
//WHO
// WHO
put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image");
put("WHO/Alien Insect", "https://api.scryfall.com/cards/twho/19/en?format=image");
put("WHO/Alien Rhino", "https://api.scryfall.com/cards/twho/3/en?format=image");
put("WHO/Alien Salamander", "https://api.scryfall.com/cards/twho/16?format=image");
put("WHO/Alien Warrior", "https://api.scryfall.com/cards/twho/14?format=image");
put("WHO/Beast", "https://api.scryfall.com/cards/twho/17?format=image");
put("WHO/Clue/1", "https://api.scryfall.com/cards/twho/21?format=image");
put("WHO/Clue/2", "https://api.scryfall.com/cards/twho/22?format=image");
put("WHO/Clue/3", "https://api.scryfall.com/cards/twho/23?format=image");
put("WHO/Dalek", "https://api.scryfall.com/cards/twho/12?format=image");
put("WHO/Dinosaur", "https://api.scryfall.com/cards/twho/20?format=image");
put("WHO/Fish", "https://api.scryfall.com/cards/twho/10?format=image");
put("WHO/Food/1", "https://api.scryfall.com/cards/twho/25?format=image");
put("WHO/Food/2", "https://api.scryfall.com/cards/twho/26?format=image");
put("WHO/Food/3", "https://api.scryfall.com/cards/twho/27?format=image");
put("WHO/Horse", "https://api.scryfall.com/cards/twho/4/en?format=image");
put("WHO/Human/1", "https://api.scryfall.com/cards/twho/6/en?format=image");
put("WHO/Human/2", "https://api.scryfall.com/cards/twho/5/en?format=image");
put("WHO/Human Noble", "https://api.scryfall.com/cards/twho/7/en?format=image");
put("WHO/Mark of the Rani", "https://api.scryfall.com/cards/twho/15?format=image");
put("WHO/Soldier", "https://api.scryfall.com/cards/twho/8?format=image");
put("WHO/Treasure/1", "https://api.scryfall.com/cards/twho/28?format=image");
put("WHO/Treasure/2", "https://api.scryfall.com/cards/twho/29?format=image");
put("WHO/Treasure/3", "https://api.scryfall.com/cards/twho/30?format=image");
put("WHO/Treasure/4", "https://api.scryfall.com/cards/twho/31?format=image");
put("WHO/Warrior", "https://api.scryfall.com/cards/twho/9?format=image");
// 8ED
put("8ED/Rukh", "https://api.scryfall.com/cards/p03/7/en?format=image");
put("8ED/Bird", "https://api.scryfall.com/cards/p03/7/en?format=image");
// LCI
put("LCI/Angel", "https://api.scryfall.com/cards/tlci/2/en?format=image");
@ -2455,7 +2493,348 @@ public class ScryfallImageSupportTokens {
put("BLB/Wall", "https://api.scryfall.com/cards/tblb/4/en?format=image");
// BLC
put("BLC/Beast/1", "https://api.scryfall.com/cards/tblc/24/en?format=image");
put("BLC/Beast/2", "https://api.scryfall.com/cards/tblc/25/en?format=image");
put("BLC/Bird/1", "https://api.scryfall.com/cards/tblc/11/en?format=image");
put("BLC/Bird/2", "https://api.scryfall.com/cards/tblc/3/en?format=image");
put("BLC/Blood", "https://api.scryfall.com/cards/tblc/36/en?format=image");
put("BLC/Cat", "https://api.scryfall.com/cards/tblc/26/en?format=image");
put("BLC/Citizen", "https://api.scryfall.com/cards/tblc/33/en?format=image");
put("BLC/Clue", "https://api.scryfall.com/cards/tblc/37/en?format=image");
put("BLC/Eldrazi", "https://api.scryfall.com/cards/tblc/2/en?format=image");
put("BLC/Elemental", "https://api.scryfall.com/cards/tblc/4/en?format=image");
put("BLC/Elephant", "https://api.scryfall.com/cards/tblc/27/en?format=image");
put("BLC/Faerie", "https://api.scryfall.com/cards/tblc/12/en?format=image");
put("BLC/Frog Lizard", "https://api.scryfall.com/cards/tblc/28/en?format=image");
put("BLC/Goat", "https://api.scryfall.com/cards/tblc/5/en?format=image");
put("BLC/Goblin", "https://api.scryfall.com/cards/tblc/21/en?format=image");
put("BLC/Hamster", "https://api.scryfall.com/cards/tblc/22/en?format=image");
put("BLC/Human", "https://api.scryfall.com/cards/tblc/6/en?format=image");
put("BLC/Human Soldier", "https://api.scryfall.com/cards/tblc/7/en?format=image");
put("BLC/Illusion", "https://api.scryfall.com/cards/tblc/13/en?format=image");
put("BLC/Octopus", "https://api.scryfall.com/cards/tblc/14/en?format=image");
put("BLC/Pest", "https://api.scryfall.com/cards/tblc/34/en?format=image");
put("BLC/Phyrexian Golem", "https://api.scryfall.com/cards/tblc/38/en?format=image");
put("BLC/Raccoon", "https://api.scryfall.com/cards/tblc/29/en?format=image");
put("BLC/Rat", "https://api.scryfall.com/cards/tblc/19/en?format=image");
put("BLC/Saproling", "https://api.scryfall.com/cards/tblc/30/en?format=image");
put("BLC/Shapeshifter", "https://api.scryfall.com/cards/tblc/15/en?format=image");
put("BLC/Shark", "https://api.scryfall.com/cards/tblc/16/en?format=image");
put("BLC/Soldier", "https://api.scryfall.com/cards/tblc/8/en?format=image");
put("BLC/Spider", "https://api.scryfall.com/cards/tblc/31/en?format=image");
put("BLC/Spirit", "https://api.scryfall.com/cards/tblc/9/en?format=image");
put("BLC/Squid", "https://api.scryfall.com/cards/tblc/17/en?format=image");
put("BLC/Storm Crow", "https://api.scryfall.com/cards/tblc/18/en?format=image");
put("BLC/Thopter", "https://api.scryfall.com/cards/tblc/39/en?format=image");
put("BLC/Wolf/1", "https://api.scryfall.com/cards/tblc/35/en?format=image");
put("BLC/Wolf/2", "https://api.scryfall.com/cards/tblc/32/en?format=image");
// DSK
put("DSK/Beast", "https://api.scryfall.com/cards/tdsk/3?format=image");
put("DSK/Emblem Kaito", "https://api.scryfall.com/cards/tdsk/17/en?format=image");
put("DSK/Everywhere", "https://api.scryfall.com/cards/tdsk/16?format=image");
put("DSK/Glimmer", "https://api.scryfall.com/cards/tdsk/4?format=image");
put("DSK/Gremlin", "https://api.scryfall.com/cards/tdsk/11?format=image");
put("DSK/Insect/1", "https://api.scryfall.com/cards/tdsk/13?format=image");
put("DSK/Insect/2", "https://api.scryfall.com/cards/tdsk/5?format=image");
put("DSK/Primo, the Indivisible", "https://api.scryfall.com/cards/tdsk/14?format=image");
put("DSK/Shard", "https://api.scryfall.com/cards/tdsk/2?format=image");
put("DSK/Spider", "https://api.scryfall.com/cards/tdsk/12?format=image");
put("DSK/Spirit", "https://api.scryfall.com/cards/tdsk/8?format=image");
put("DSK/Treasure", "https://api.scryfall.com/cards/tdsk/15?format=image");
// DSC
put("DSC/Angel", "https://api.scryfall.com/cards/tdsc/2/en?format=image");
put("DSC/Beast", "https://api.scryfall.com/cards/tdsc/8/en?format=image");
put("DSC/Bird", "https://api.scryfall.com/cards/tdsc/5/en?format=image");
put("DSC/Demon", "https://api.scryfall.com/cards/tdsc/6/en?format=image");
put("DSC/Devil", "https://api.scryfall.com/cards/tdsc/7/en?format=image");
put("DSC/Elemental", "https://api.scryfall.com/cards/tdsc/9/en?format=image");
put("DSC/Fractal", "https://api.scryfall.com/cards/tdsc/20/en?format=image");
put("DSC/Human Soldier", "https://api.scryfall.com/cards/tdsc/3/en?format=image");
put("DSC/Inkling", "https://api.scryfall.com/cards/tdsc/21/en?format=image");
put("DSC/Insect/1", "https://api.scryfall.com/cards/tdsc/22/en?format=image");
put("DSC/Insect/2", "https://api.scryfall.com/cards/tdsc/10/en?format=image");
put("DSC/Insect/3", "https://api.scryfall.com/cards/tdsc/11/en?format=image");
put("DSC/Insect/4", "https://api.scryfall.com/cards/tdsc/12/en?format=image");
put("DSC/Ooze/1", "https://api.scryfall.com/cards/tdsc/14/en?format=image");
put("DSC/Ooze/2", "https://api.scryfall.com/cards/tdsc/13/en?format=image");
put("DSC/Phyrexian Beast", "https://api.scryfall.com/cards/tdsc/15/en?format=image");
put("DSC/Shapeshifter", "https://api.scryfall.com/cards/tdsc/1/en?format=image");
put("DSC/Shark", "https://api.scryfall.com/cards/tdsc/4/en?format=image");
put("DSC/Spider", "https://api.scryfall.com/cards/tdsc/16/en?format=image");
put("DSC/Treefolk", "https://api.scryfall.com/cards/tdsc/17/en?format=image");
put("DSC/Wurm/1", "https://api.scryfall.com/cards/tdsc/18/en?format=image");
put("DSC/Wurm/2", "https://api.scryfall.com/cards/tdsc/19/en?format=image");
// FDN
put("FDN/Beast/1", "https://api.scryfall.com/cards/tfdn/32/en?format=image");
put("FDN/Beast/2", "https://api.scryfall.com/cards/tfdn/33/en?format=image");
put("FDN/Cat/1", "https://api.scryfall.com/cards/tfdn/1/en?format=image");
put("FDN/Cat/2", "https://api.scryfall.com/cards/tfdn/27/en?format=image");
put("FDN/Cat/3", "https://api.scryfall.com/cards/tfdn/2/en?format=image");
put("FDN/Cat Beast", "https://api.scryfall.com/cards/tfdn/28/en?format=image");
put("FDN/Dog", "https://api.scryfall.com/cards/tfdn/29/en?format=image");
put("FDN/Dragon/1", "https://api.scryfall.com/cards/tfdn/16/en?format=image");
put("FDN/Dragon/2", "https://api.scryfall.com/cards/tfdn/17/en?format=image");
put("FDN/Drake", "https://api.scryfall.com/cards/tfdn/8/en?format=image");
put("FDN/Elf Warrior", "https://api.scryfall.com/cards/tfdn/19/en?format=image");
put("FDN/Faerie", "https://api.scryfall.com/cards/tfdn/9/en?format=image");
put("FDN/Fish", "https://api.scryfall.com/cards/tfdn/10/en?format=image");
put("FDN/Food", "https://api.scryfall.com/cards/tfdn/22/en?format=image");
put("FDN/Goblin", "https://api.scryfall.com/cards/tfdn/18/en?format=image");
put("FDN/Human", "https://api.scryfall.com/cards/tfdn/3/en?format=image");
put("FDN/Insect", "https://api.scryfall.com/cards/tfdn/21/en?format=image");
put("FDN/Emblem Kaito", "https://api.scryfall.com/cards/tfdn/24/en?format=image");
put("FDN/Knight", "https://api.scryfall.com/cards/tfdn/4/en?format=image");
put("FDN/Koma's Coil", "https://api.scryfall.com/cards/tfdn/11/en?format=image");
put("FDN/Ninja", "https://api.scryfall.com/cards/tfdn/12/en?format=image");
put("FDN/Phyrexian Goblin", "https://api.scryfall.com/cards/tfdn/31/en?format=image");
put("FDN/Rabbit", "https://api.scryfall.com/cards/tfdn/5/en?format=image");
put("FDN/Raccoon", "https://api.scryfall.com/cards/tfdn/20/en?format=image");
put("FDN/Rat/1", "https://api.scryfall.com/cards/tfdn/14/en?format=image");
put("FDN/Rat/2", "https://api.scryfall.com/cards/tfdn/30/en?format=image");
put("FDN/Scion of the Deep", "https://api.scryfall.com/cards/tfdn/13/en?format=image");
put("FDN/Soldier", "https://api.scryfall.com/cards/tfdn/6/en?format=image");
put("FDN/Spirit", "https://api.scryfall.com/cards/tfdn/7/en?format=image");
put("FDN/Treasure", "https://api.scryfall.com/cards/tfdn/23/en?format=image");
put("FDN/Emblem Vivien", "https://api.scryfall.com/cards/tfdn/25/en?format=image");
put("FDN/Zombie", "https://api.scryfall.com/cards/tfdn/15/en?format=image");
// H17
put("H17/Dragon", "https://api.scryfall.com/cards/h17/4/en?format=image");
// INR
put("INR/Emblem Arlinn", "https://api.scryfall.com/cards/tinr/23/en?format=image");
put("INR/Blood", "https://api.scryfall.com/cards/tinr/21/en?format=image");
put("INR/Emblem Chandra", "https://api.scryfall.com/cards/tinr/24/en?format=image");
put("INR/Clue", "https://api.scryfall.com/cards/tinr/22/en?format=image");
put("INR/Demon", "https://api.scryfall.com/cards/tinr/6/en?format=image");
put("INR/Eldrazi Horror", "https://api.scryfall.com/cards/tinr/1/en?format=image");
put("INR/Elemental", "https://api.scryfall.com/cards/tinr/13/en?format=image");
put("INR/Human/1", "https://api.scryfall.com/cards/tinr/14/en?format=image");
put("INR/Human/2", "https://api.scryfall.com/cards/tinr/2/en?format=image");
put("INR/Human Cleric", "https://api.scryfall.com/cards/tinr/19/en?format=image");
put("INR/Human Soldier/1", "https://api.scryfall.com/cards/tinr/3/en?format=image");
put("INR/Human Soldier/2", "https://api.scryfall.com/cards/tinr/20/en?format=image");
put("INR/Human Wizard", "https://api.scryfall.com/cards/tinr/5/en?format=image");
put("INR/Insect", "https://api.scryfall.com/cards/tinr/15/en?format=image");
put("INR/Emblem Jace", "https://api.scryfall.com/cards/tinr/25/en?format=image");
put("INR/Spider", "https://api.scryfall.com/cards/tinr/16/en?format=image");
put("INR/Spirit", "https://api.scryfall.com/cards/tinr/4/en?format=image");
put("INR/Emblem Tamiyo", "https://api.scryfall.com/cards/tinr/26/en?format=image");
put("INR/Treefolk", "https://api.scryfall.com/cards/tinr/17/en?format=image");
put("INR/Vampire/1", "https://api.scryfall.com/cards/tinr/7/en?format=image");
put("INR/Vampire/2", "https://api.scryfall.com/cards/tinr/8/en?format=image");
put("INR/Wolf/1", "https://api.scryfall.com/cards/tinr/9/en?format=image");
put("INR/Wolf/2", "https://api.scryfall.com/cards/tinr/18/en?format=image");
put("INR/Emblem Wrenn", "https://api.scryfall.com/cards/tinr/27/en?format=image");
put("INR/Zombie/1", "https://api.scryfall.com/cards/tinr/12/en?format=image");
put("INR/Zombie/2", "https://api.scryfall.com/cards/tinr/10/en?format=image");
put("INR/Zombie/3", "https://api.scryfall.com/cards/tinr/11/en?format=image");
// DFT
put("DFT/Cat", "https://api.scryfall.com/cards/tdft/2/en?format=image");
put("DFT/Emblem Chandra", "https://api.scryfall.com/cards/tdft/13/en?format=image");
put("DFT/Dinosaur Dragon", "https://api.scryfall.com/cards/tdft/4/en?format=image");
put("DFT/Elephant", "https://api.scryfall.com/cards/tdft/6/en?format=image");
put("DFT/Goblin", "https://api.scryfall.com/cards/tdft/5/en?format=image");
put("DFT/Insect", "https://api.scryfall.com/cards/tdft/7/en?format=image");
put("DFT/Pilot", "https://api.scryfall.com/cards/tdft/1/en?format=image");
put("DFT/Servo", "https://api.scryfall.com/cards/tdft/8/en?format=image");
put("DFT/Thopter/1", "https://api.scryfall.com/cards/tdft/9/en?format=image");
put("DFT/Thopter/2", "https://api.scryfall.com/cards/tdft/10/en?format=image");
put("DFT/Treasure", "https://api.scryfall.com/cards/tdft/11/en?format=image");
put("DFT/Vehicle", "https://api.scryfall.com/cards/tdft/12/en?format=image");
put("DFT/Zombie", "https://api.scryfall.com/cards/tdft/3/en?format=image");
// DRC
put("DRC/Beast/1", "https://api.scryfall.com/cards/tdrc/10/en?format=image");
put("DRC/Beast/2", "https://api.scryfall.com/cards/tdrc/11/en?format=image");
put("DRC/Construct", "https://api.scryfall.com/cards/tdrc/12/en?format=image");
put("DRC/Golem/1", "https://api.scryfall.com/cards/tdrc/13/en?format=image");
put("DRC/Golem/2", "https://api.scryfall.com/cards/tdrc/14/en?format=image");
put("DRC/Golem/3", "https://api.scryfall.com/cards/tdrc/15/en?format=image");
put("DRC/Nalaar Aetherjet", "https://api.scryfall.com/cards/tdrc/16/en?format=image");
put("DRC/Shapeshifter", "https://api.scryfall.com/cards/tdrc/4/en?format=image");
put("DRC/Zombie/1", "https://api.scryfall.com/cards/tdrc/3/en?format=image");
put("DRC/Zombie/2", "https://api.scryfall.com/cards/tdrc/7/en?format=image");
put("DRC/Zombie Army", "https://api.scryfall.com/cards/tdrc/8/en?format=image");
put("DRC/Zombie Warrior", "https://api.scryfall.com/cards/tdrc/9/en?format=image");
// TDM
put("TDM/Bird", "https://api.scryfall.com/cards/ttdm/2/en?format=image");
put("TDM/Dragon", "https://api.scryfall.com/cards/ttdm/11/en?format=image");
put("TDM/Elephant", "https://api.scryfall.com/cards/ttdm/14/en?format=image");
put("TDM/Goblin", "https://api.scryfall.com/cards/ttdm/12/en?format=image");
put("TDM/Monk", "https://api.scryfall.com/cards/ttdm/3/en?format=image");
put("TDM/Reliquary Dragon", "https://api.scryfall.com/cards/ttdm/15/en?format=image");
put("TDM/Soldier/1", "https://api.scryfall.com/cards/ttdm/4/en?format=image");
put("TDM/Soldier/2", "https://api.scryfall.com/cards/ttdm/5/en?format=image");
put("TDM/Spirit/1", "https://api.scryfall.com/cards/ttdm/9/en?format=image");
put("TDM/Spirit/2", "https://api.scryfall.com/cards/ttdm/6/en?format=image");
// TODO: 2/2 and 3/3 Spirit tokens (no relevant cards revealed, token not implemented)
put("TDM/Treasure", "https://api.scryfall.com/cards/ttdm/16/en?format=image");
put("TDM/Warrior", "https://api.scryfall.com/cards/ttdm/13/en?format=image");
put("TDM/Zombie Druid", "https://api.scryfall.com/cards/ttdm/10/en?format=image");
// TDC
put("TDC/Angel", "https://api.scryfall.com/cards/ttdc/2/en?format=image");
put("TDC/Beast", "https://api.scryfall.com/cards/ttdc/20?format=image");
put("TDC/Citizen", "https://api.scryfall.com/cards/ttdc/26/en?format=image");
put("TDC/Dog", "https://api.scryfall.com/cards/ttdc/3/en?format=image");
put("TDC/Dragon/1", "https://api.scryfall.com/cards/ttdc/13?format=image");
put("TDC/Dragon/2", "https://api.scryfall.com/cards/ttdc/14?format=image");
put("TDC/Dragon Egg", "https://api.scryfall.com/cards/ttdc/12?format=image");
put("TDC/Dragon Illusion", "https://api.scryfall.com/cards/ttdc/15/en?format=image");
put("TDC/Eldrazi", "https://api.scryfall.com/cards/ttdc/1/en?format=image");
put("TDC/Elemental/1", "https://api.scryfall.com/cards/ttdc/16/en?format=image");
put("TDC/Elemental/2", "https://api.scryfall.com/cards/ttdc/17/en?format=image");
put("TDC/Elemental/3", "https://api.scryfall.com/cards/ttdc/27/en?format=image");
put("TDC/First Mate Ragavan", "https://api.scryfall.com/cards/ttdc/18/en?format=image");
put("TDC/Frog Lizard", "https://api.scryfall.com/cards/ttdc/21?format=image");
put("TDC/Goat", "https://api.scryfall.com/cards/ttdc/4/en?format=image");
put("TDC/Gold", "https://api.scryfall.com/cards/ttdc/29/en?format=image");
put("TDC/Human", "https://api.scryfall.com/cards/ttdc/5/en?format=image");
put("TDC/Inkling", "https://api.scryfall.com/cards/ttdc/28?format=image");
put("TDC/Insect", "https://api.scryfall.com/cards/ttdc/22/en?format=image");
put("TDC/Karox Bladewing", "https://api.scryfall.com/cards/ttdc/19?format=image");
put("TDC/Myr", "https://api.scryfall.com/cards/ttdc/30/en?format=image");
put("TDC/Plant", "https://api.scryfall.com/cards/ttdc/24/en?format=image");
put("TDC/Rat", "https://api.scryfall.com/cards/ttdc/9/en?format=image");
put("TDC/Salamander Warrior", "https://api.scryfall.com/cards/ttdc/8/en?format=image");
put("TDC/Servo", "https://api.scryfall.com/cards/ttdc/31/en?format=image");
put("TDC/Snake", "https://api.scryfall.com/cards/ttdc/10/en?format=image");
put("TDC/Soldier", "https://api.scryfall.com/cards/ttdc/32/en?format=image");
put("TDC/Spider", "https://api.scryfall.com/cards/ttdc/25?format=image");
put("TDC/Spirit", "https://api.scryfall.com/cards/ttdc/6/en?format=image");
put("TDC/Thopter", "https://api.scryfall.com/cards/ttdc/33/en?format=image");
// ACR
put("ACR/Assassin", "https://api.scryfall.com/cards/tacr/4?format=image");
put("ACR/Emblem Capitoline Triad", "https://api.scryfall.com/cards/tacr/7/en?format=image");
put("ACR/Human Rogue", "https://api.scryfall.com/cards/tacr/3?format=image");
put("ACR/Phobos", "https://api.scryfall.com/cards/tacr/5?format=image");
put("ACR/Shapeshifter", "https://api.scryfall.com/cards/tacr/2?format=image");
put("ACR/Treasure", "https://api.scryfall.com/cards/tacr/6?format=image");
// DD2
put("DD2/Elemental Shaman", "https://api.scryfall.com/cards/tdd2/1?format=image");
// FIN
put("FIN/Food", "https://api.scryfall.com/cards/tfin/22?format=image");
// JVC
put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image");
// PIP
put("PIP/Alien", "https://api.scryfall.com/cards/tpip/6?format=image");
put("PIP/Clue", "https://api.scryfall.com/cards/tpip/11?format=image");
put("PIP/Food/1", "https://api.scryfall.com/cards/tpip/12?format=image");
put("PIP/Food/2", "https://api.scryfall.com/cards/tpip/13?format=image");
put("PIP/Food/3", "https://api.scryfall.com/cards/tpip/14?format=image");
put("PIP/Human Knight", "https://api.scryfall.com/cards/tpip/2?format=image");
put("PIP/Human Soldier", "https://api.scryfall.com/cards/tpip/3?format=image");
put("PIP/Junk", "https://api.scryfall.com/cards/tpip/15?format=image");
put("PIP/Robot", "https://api.scryfall.com/cards/tpip/16?format=image");
put("PIP/Settlement", "https://api.scryfall.com/cards/tpip/8?format=image");
put("PIP/Soldier/1", "https://api.scryfall.com/cards/tpip/10?format=image");
put("PIP/Soldier/2", "https://api.scryfall.com/cards/tpip/4?format=image");
put("PIP/Squirrel", "https://api.scryfall.com/cards/tpip/9?format=image");
put("PIP/Thopter", "https://api.scryfall.com/cards/tpip/17?format=image");
put("PIP/Treasure/1", "https://api.scryfall.com/cards/tpip/18?format=image");
put("PIP/Treasure/2", "https://api.scryfall.com/cards/tpip/19?format=image");
put("PIP/Warrior", "https://api.scryfall.com/cards/tpip/5?format=image");
put("PIP/Wasteland Survival Guide", "https://api.scryfall.com/cards/tpip/20?format=image");
put("PIP/Zombie Mutant", "https://api.scryfall.com/cards/tpip/7?format=image");
// REX
put("REX/Dinosaur", "https://api.scryfall.com/cards/trex/1?format=image");
put("REX/Treasure", "https://api.scryfall.com/cards/trex/2?format=image");
// UGL
put("UGL/Goblin", "https://api.scryfall.com/cards/tugl/4?format=image");
put("UGL/Pegasus", "https://api.scryfall.com/cards/tugl/1?format=image");
put("UGL/Soldier", "https://api.scryfall.com/cards/tugl/2?format=image");
put("UGL/Squirrel", "https://api.scryfall.com/cards/tugl/6?format=image");
put("UGL/Zombie", "https://api.scryfall.com/cards/tugl/3?format=image");
// UST
put("UST/Angel", "https://api.scryfall.com/cards/tust/1?format=image");
put("UST/Beast", "https://api.scryfall.com/cards/tust/13?format=image");
put("UST/Brainiac", "https://api.scryfall.com/cards/tust/10?format=image");
put("UST/Clue", "https://api.scryfall.com/cards/tust/18?format=image");
put("UST/Dragon", "https://api.scryfall.com/cards/tust/16?format=image");
put("UST/Elemental/1", "https://api.scryfall.com/cards/tust/11?format=image");
put("UST/Elemental/2", "https://api.scryfall.com/cards/tust/17?format=image");
put("UST/Gnome", "https://api.scryfall.com/cards/tust/20?format=image");
put("UST/Goat", "https://api.scryfall.com/cards/tust/2?format=image");
put("UST/Goblin", "https://api.scryfall.com/cards/tust/12?format=image");
put("UST/Saproling", "https://api.scryfall.com/cards/tust/14?format=image");
put("UST/Spirit", "https://api.scryfall.com/cards/tust/3?format=image");
put("UST/Squirrel", "https://api.scryfall.com/cards/tust/15?format=image");
put("UST/Storm Crow", "https://api.scryfall.com/cards/tust/5?format=image");
put("UST/Thopter", "https://api.scryfall.com/cards/tust/6?format=image");
put("UST/Vampire", "https://api.scryfall.com/cards/tust/8?format=image");
put("UST/Zombie", "https://api.scryfall.com/cards/tust/9?format=image");
// F12
put("F12/Human", "https://api.scryfall.com/cards/f12/1a?format=image");
put("F12/Wolf", "https://api.scryfall.com/cards/f12/1a?format=image&face=back");
// F17
put("F17/Dinosaur", "https://api.scryfall.com/cards/f17/11?format=image");
put("F17/Pirate", "https://api.scryfall.com/cards/f17/12?format=image");
put("F17/Vampire", "https://api.scryfall.com/cards/f17/10?format=image");
put("F17/Treasure/1", "https://api.scryfall.com/cards/f17/11?format=image&face=back");
put("F17/Treasure/2", "https://api.scryfall.com/cards/f17/12?format=image&face=back");
put("F17/Treasure/3", "https://api.scryfall.com/cards/f17/10?format=image&face=back");
// HHO
put("HHO/Treasure", "https://api.scryfall.com/cards/hho/21★?format=image");
// J12
put("J12/Centaur", "https://api.scryfall.com/cards/j12/9?format=image");
// J13
put("J13/Golem", "https://api.scryfall.com/cards/j13/9?format=image");
// MPR
put("MPR/Bear", "https://api.scryfall.com/cards/mpr/7?format=image");
put("MPR/Beast", "https://api.scryfall.com/cards/mpr/8?format=image");
put("MPR/Bird", "https://api.scryfall.com/cards/mpr/4?format=image");
put("MPR/Elephant", "https://api.scryfall.com/cards/mpr/3?format=image");
put("MPR/Goblin Soldier", "https://api.scryfall.com/cards/mpr/6?format=image");
put("MPR/Saproling", "https://api.scryfall.com/cards/mpr/2?format=image");
put("MPR/Spirit", "https://api.scryfall.com/cards/mpr/5?format=image");
// P03
put("P03/Bear", "https://api.scryfall.com/cards/p03/4?format=image");
put("P03/Demon", "https://api.scryfall.com/cards/p03/6?format=image");
put("P03/Goblin", "https://api.scryfall.com/cards/p03/5?format=image");
put("P03/Insect", "https://api.scryfall.com/cards/p03/2?format=image");
put("P03/Bird", "https://api.scryfall.com/cards/p03/7?format=image");
put("P03/Sliver", "https://api.scryfall.com/cards/p03/3?format=image");
// P04
put("P04/Angel", "https://api.scryfall.com/cards/p04/2?format=image");
put("P04/Beast", "https://api.scryfall.com/cards/p04/5?format=image");
put("P04/Myr", "https://api.scryfall.com/cards/p04/4?format=image");
put("P04/Pentavite", "https://api.scryfall.com/cards/p04/3?format=image");
put("P04/Spirit", "https://api.scryfall.com/cards/p04/6?format=image");
// PEMN
put("PEMN/Zombie/1", "https://api.scryfall.com/cards/pemn/1Z?format=image");
put("PEMN/Zombie/2", "https://api.scryfall.com/cards/pemn/1Z?format=image&face=back");
// PHEL
put("PHEL/Angel", "https://api.scryfall.com/cards/phel/1★?format=image");
// PL21
put("PL21/Minotaur", "https://api.scryfall.com/cards/pl21/2★?format=image");
// PL23
put("PL23/Food", "https://api.scryfall.com/cards/pl23/2?format=image");
// generate supported sets
supportedSets.clear();

View file

@ -282,7 +282,7 @@ public enum WizardCardsImageSource implements CardImageSource {
setsAliases.put("BOK", "Betrayers of Kamigawa");
setsAliases.put("BRB", "Battle Royale Box Set");
setsAliases.put("BTD", "Beatdown Box Set");
setsAliases.put("C13", "Commander 2013 Edition");
setsAliases.put("C13", "Commander 2013");
setsAliases.put("C14", "Commander 2014");
setsAliases.put("C15", "Commander 2015");
setsAliases.put("C16", "Commander 2016");
@ -365,7 +365,7 @@ public enum WizardCardsImageSource implements CardImageSource {
setsAliases.put("M13", "Magic 2013");
setsAliases.put("M14", "Magic 2014");
setsAliases.put("M15", "Magic 2015");
setsAliases.put("PMEI", "Media Inserts");
setsAliases.put("PMEI", "Media and Collaboration Promos");
setsAliases.put("MBS", "Mirrodin Besieged");
setsAliases.put("ME2", "Masters Edition II");
setsAliases.put("ME3", "Masters Edition III");
@ -386,7 +386,7 @@ public enum WizardCardsImageSource implements CardImageSource {
setsAliases.put("ODY", "Odyssey");
setsAliases.put("ONS", "Onslaught");
setsAliases.put("ORI", "Magic Origins");
setsAliases.put("PC2", "Planechase 2012 Edition");
setsAliases.put("PC2", "Planechase 2012");
setsAliases.put("PCY", "Prophecy");
setsAliases.put("PD2", "Premium Deck Series: Fire and Lightning");
setsAliases.put("PLC", "Planar Chaos");
@ -409,7 +409,7 @@ public enum WizardCardsImageSource implements CardImageSource {
setsAliases.put("TMP", "Tempest");
setsAliases.put("TOR", "Torment");
setsAliases.put("TPR", "Tempest Remastered");
setsAliases.put("TSB", "Time Spiral \"Timeshifted\"");
setsAliases.put("TSB", "Time Spiral Timeshifted");
setsAliases.put("TSP", "Time Spiral");
setsAliases.put("UDS", "Urza's Destiny");
setsAliases.put("UGL", "Unglued");

View file

@ -725,8 +725,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
try {
TVFS.umount();
} catch (FsSyncException e) {
logger.fatal("Couldn't unmount zip files " + e, e);
MageFrame.getInstance().showErrorDialog("Couldn't unmount zip files " + e, e);
logger.error("Couldn't unmount zip files " + e, e);
// this is not a critical error - just need to run it again - see issue #12833
}
}
@ -940,14 +940,13 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
logger.warn(err);
}
}
} catch (AccessDeniedException e) {
incErrorCount();
logger.error("Can't access to files: " + card.getName() + "(" + card.getSet() + "). Try rebooting your system to remove the file lock.");
} catch (Exception e) {
incErrorCount();
logger.error("Unknown error: " + e.getMessage(), e);
} finally {
String sampleUrl = (urls == null ? "null" : urls.getDownloadList().stream().findFirst().orElse(null));
logger.error("Unknown error: " + e.getMessage() + ", sample url: " + sampleUrl, e);
}
synchronized (sync) {

View file

@ -185,7 +185,7 @@ public final class CardImageUtils {
try {
TVFS.umount();
} catch (FsSyncException e) {
LOGGER.fatal("Couldn't unmount zip files on searching broken images " + e, e);
LOGGER.error("Couldn't unmount zip files on searching broken images " + e, e);
}
// real images check is slow, so it used on images download only (not here)

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View file

@ -43,7 +43,7 @@ public class ScryfallImagesDownloadTest {
.anyMatch(c -> c.getCardNumber().equals("001"))
);
urls = imageSource.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0));
Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image", urls.getBaseUrl());
Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/qya?format=image", urls.getBaseUrl());
// added same tests for small images
@ -74,6 +74,6 @@ public class ScryfallImagesDownloadTest {
.anyMatch(c -> c.getCardNumber().equals("001"))
);
urls = imageSourceSmall.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0));
Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image&version=small", urls.getBaseUrl());
Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/qya?format=image&version=small", urls.getBaseUrl());
}
}

View file

@ -2,6 +2,7 @@ package mage.client.util;
import mage.client.remote.XmageURLConnection;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import javax.imageio.ImageIO;
@ -12,24 +13,40 @@ import java.io.InputStream;
/**
* @author JayDi85
*/
@Ignore // TODO: too many fails due third party servers downtime, migrate to more stable resources or just run it manually
public class DownloaderTest {
@Test
public void test_DownloadText_ByHttp() {
String s = XmageURLConnection.downloadText("http://google.com");
String s = XmageURLConnection.downloadText("http://example.com");
Assert.assertTrue("must have text data", s.contains("<head>"));
}
@Test
public void test_DownloadText_ByHttps() {
String s = XmageURLConnection.downloadText("https://google.com");
String s = XmageURLConnection.downloadText("https://example.com");
Assert.assertTrue("must have text data", s.contains("<head>"));
}
@Test
public void test_DownloadText_ByRedirectProtocol() {
// http to https restricted by design, see https://stackoverflow.com/a/1884427/1276632
// it's not critical for a client (e.g. for images download), so no needs in custom implementation
// like xmage launcher does
String s = XmageURLConnection.downloadText("http://github.com");
Assert.assertTrue("must have fail on https redirect (301 result)", s.isEmpty());
}
@Test
public void test_DownloadText_ByRedirectUri() {
String s = XmageURLConnection.downloadText("https://github.com/magefree/mage/issues/new");
Assert.assertTrue("must have text data (redirect to login page)", s.contains("Sign in to GitHub"));
}
@Test
public void test_DownloadFile_ByHttp() throws IOException {
// use any public image here
InputStream stream = XmageURLConnection.downloadBinary("http://www.google.com/tia/tia.png");
InputStream stream = XmageURLConnection.downloadBinary("http://xmage.today/images/xmage-logo.png");
Assert.assertNotNull(stream);
BufferedImage image = ImageIO.read(stream);
Assert.assertNotNull(stream);
@ -39,10 +56,28 @@ public class DownloaderTest {
@Test
public void test_DownloadFile_ByHttps() throws IOException {
// use any public image here
InputStream stream = XmageURLConnection.downloadBinary("https://www.google.com/tia/tia.png");
InputStream stream = XmageURLConnection.downloadBinary("https://xmage.today/images/xmage-logo.png");
Assert.assertNotNull(stream);
BufferedImage image = ImageIO.read(stream);
Assert.assertNotNull(stream);
Assert.assertTrue("must have image data", image.getWidth() > 0);
}
@Test
@Ignore // TODO: enable after gatherer server maintenance done
public void test_DownloadFromWindowsServers() throws IOException {
// symbols download from gatherer website
// error example: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
InputStream stream = XmageURLConnection.downloadBinary("https://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=BIG&size=small&rarity=C");
Assert.assertNotNull(stream);
BufferedImage image = null;
try {
image = ImageIO.read(stream);
} catch (IOException e) {
Assert.fail("Can't download image file due error: " + e);
}
Assert.assertNotNull(stream);
Assert.assertNotNull(image);
Assert.assertTrue("must have image data", image.getWidth() > 0);
}
}

View file

@ -7,7 +7,7 @@
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.4.54</version>
<version>1.4.57</version>
</parent>
<artifactId>mage-common</artifactId>

View file

@ -64,10 +64,10 @@ public class MageTable extends JTable {
// html tooltip
java.awt.Point p = e.getPoint();
int colIndex = columnModel.getColumnIndexAtX(p.x);
TableColumn col = columnModel.getColumn(colIndex);
if (colIndex < 0) {
return "";
}
TableColumn col = columnModel.getColumn(colIndex);
int realIndex = col.getModelIndex();
String tip;

View file

@ -99,4 +99,7 @@ public class ClientCallback implements Serializable {
return messageId;
}
public String getInfo() {
return String.format("message %d - %s - %s", this.getMessageId(), this.getMethod().getType(), this.getMethod());
}
}

View file

@ -13,13 +13,12 @@ import mage.interfaces.ServerState;
import mage.interfaces.callback.ClientCallback;
import mage.players.PlayerType;
import mage.players.net.UserData;
import mage.utils.CompressUtil;
import mage.util.ThreadUtils;
import mage.utils.CompressUtil;
import mage.view.*;
import org.apache.log4j.Logger;
import org.jboss.remoting.*;
import org.jboss.remoting.callback.Callback;
import org.jboss.remoting.callback.HandleCallbackException;
import org.jboss.remoting.callback.InvokerCallbackHandler;
import org.jboss.remoting.transport.bisocket.Bisocket;
import org.jboss.remoting.transport.socket.SocketWrapper;
@ -31,6 +30,7 @@ import java.lang.reflect.UndeclaredThrowableException;
import java.net.*;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
/**
@ -47,6 +47,8 @@ public class SessionImpl implements Session {
private static final int SESSION_VALIDATOR_PING_PERIOD_SECS = 4;
private static final int SESSION_VALIDATOR_PING_TIMEOUT_SECS = 3;
private static final int CONNECT_WAIT_BEFORE_PROCESS_ANY_CALLBACKS_SECS = 3;
public static final String ADMIN_NAME = "Admin"; // if you change here then change in User too
public static final String KEEP_MY_OLD_SESSION = "keep_my_old_session"; // for disconnects without active session lose (keep tables/games)
@ -269,6 +271,7 @@ public class SessionImpl implements Session {
}
if (result) {
// server state used in client side to setup game panels and dialogs, e.g. test mode info or available game types
serverState = server.getServerState();
if (serverState == null) {
throw new MageVersionException(client.getVersion(), null);
@ -597,14 +600,43 @@ public class SessionImpl implements Session {
class CallbackHandler implements InvokerCallbackHandler {
final CopyOnWriteArrayList<ClientCallback> waitingCallbacks = new CopyOnWriteArrayList<>();
@Override
public void handleCallback(Callback callback) throws HandleCallbackException {
public void handleCallback(Callback callback) {
// keep callbacks
ClientCallback clientCallback = (ClientCallback) callback.getCallbackObject();
waitingCallbacks.add(clientCallback);
// wait for client ready
// on connection client will receive all waiting callbacks from a server, e.g. started table, draft pick, etc
// but it's require to get server settings first (server state), e.g. for test mode
// possible bugs:
// - hidden cheat button or enabled clicks protection in draft
// - miss dialogs like draft or game panels
// so wait for server state some time
if (serverState == null) {
ThreadUtils.sleep(CONNECT_WAIT_BEFORE_PROCESS_ANY_CALLBACKS_SECS * 1000);
if (serverState == null) {
logger.error("Can't receive server state before other data (possible reason: unstable network): "
+ clientCallback.getInfo());
}
}
// execute waiting queue
// client.onCallback must process and ignore outdated data inside
List<ClientCallback> executingCallbacks;
synchronized (waitingCallbacks) {
executingCallbacks = new ArrayList<>(waitingCallbacks);
executingCallbacks.sort(Comparator.comparingInt(ClientCallback::getMessageId));
waitingCallbacks.clear();
}
try {
client.onCallback((ClientCallback) callback.getCallbackObject());
executingCallbacks.forEach(client::onCallback);
} catch (Exception ex) {
logger.error("handleCallback error", ex);
}
}
}

View file

@ -17,8 +17,8 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
// * launcher gives priority to 1.4.48 instead 1.4.48-any-text, so don't use empty release info
public static final int MAGE_VERSION_MAJOR = 1;
public static final int MAGE_VERSION_MINOR = 4;
public static final int MAGE_VERSION_RELEASE = 54;
public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas
public static final int MAGE_VERSION_RELEASE = 57;
public static final String MAGE_VERSION_RELEASE_INFO = "V2"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas
// strict mode
// Each update requires a strict version

View file

@ -1,8 +1,6 @@
package mage.utils;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
@ -24,9 +22,13 @@ import mage.game.command.Plane;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPlayer;
import mage.target.common.TargetOpponent;
import mage.util.CardUtil;
import mage.util.MultiAmountMessage;
import mage.util.RandomUtil;
import mage.utils.testers.TestableDialogsRunner;
import java.io.File;
import java.lang.reflect.Constructor;
@ -66,26 +68,31 @@ public final class SystemUtil {
// [@mana add] -> MANA ADD
private static final String COMMAND_CARDS_ADD_TO_HAND = "@card add to hand";
private static final String COMMAND_LANDS_ADD_TO_BATTLEFIELD = "@lands add";
private static final String COMMAND_UNDER_CONTROL_TAKE = "@under control take";
private static final String COMMAND_UNDER_CONTROL_GIVE = "@under control give";
private static final String COMMAND_SHOW_TEST_DIALOGS = "@show dialog";
private static final String COMMAND_MANA_ADD = "@mana add"; // TODO: not implemented
private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; // TODO: not implemented
private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand";
private static final String COMMAND_SHOW_OPPONENT_LIBRARY = "@show opponent library";
private static final String COMMAND_SHOW_MY_HAND = "@show my hand";
private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library";
private static final String COMMAND_ACTIVATE_OPPONENT_ABILITY = "@activate opponent ability";
private static final Map<String, String> supportedCommands = new HashMap<>();
private static final TestableDialogsRunner testableDialogsRunner = new TestableDialogsRunner(); // for tests
static {
// special commands names in choose dialog
supportedCommands.put(COMMAND_CARDS_ADD_TO_HAND, "CARDS: ADD TO HAND");
supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD");
supportedCommands.put(COMMAND_LANDS_ADD_TO_BATTLEFIELD, "LANDS: ADD TO BATTLEFIELD");
supportedCommands.put(COMMAND_UNDER_CONTROL_TAKE, "UNDER CONTROL: TAKE");
supportedCommands.put(COMMAND_UNDER_CONTROL_GIVE, "UNDER CONTROL: GIVE");
supportedCommands.put(COMMAND_RUN_CUSTOM_CODE, "RUN CUSTOM CODE");
supportedCommands.put(COMMAND_SHOW_OPPONENT_HAND, "SHOW OPPONENT HAND");
supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY");
supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND");
supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY");
supportedCommands.put(COMMAND_ACTIVATE_OPPONENT_ABILITY, "ACTIVATE OPPONENT ABILITY");
supportedCommands.put(COMMAND_SHOW_TEST_DIALOGS, "SHOW TEST DIALOGS");
}
private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card]
@ -255,13 +262,18 @@ public final class SystemUtil {
*
* @param game
* @param commandsFilePath file path with commands in init.txt format
* @param feedbackPlayer player to execute that cheats (will see choose dialogs)
* @param feedbackPlayer player to execute that cheats (will see choose dialogs)
*/
public static void executeCheatCommands(Game game, String commandsFilePath, Player feedbackPlayer) {
// fake test ability for triggers and events
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards"));
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("fake ability"));
fakeSourceAbilityTemplate.setControllerId(feedbackPlayer.getId());
Card fakeSourceCard = feedbackPlayer.getLibrary().getFromTop(game);
if (fakeSourceCard != null) {
// set any existing card as source, so dialogs will show all GUI elements, including source and workable popup info
fakeSourceAbilityTemplate.setSourceId(fakeSourceCard.getId());
}
List<String> errorsList = new ArrayList<>();
try {
@ -301,6 +313,9 @@ public final class SystemUtil {
// add default commands
initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD));
initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND));
initLines.add(2, String.format("[%s]", COMMAND_SHOW_TEST_DIALOGS));
initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE));
initLines.add(4, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE));
// collect all commands
CommandGroup currentGroup = null;
@ -386,7 +401,7 @@ public final class SystemUtil {
// 3. system commands
if (runGroup.isSpecialCommand) {
Player opponent = game.getPlayer(game.getOpponents(feedbackPlayer.getId()).stream().findFirst().orElse(null));
Player opponent = game.getPlayer(game.getOpponents(feedbackPlayer.getId(), true).stream().findFirst().orElse(null));
String info;
switch (runGroup.name) {
@ -419,53 +434,6 @@ public final class SystemUtil {
break;
}
case COMMAND_ACTIVATE_OPPONENT_ABILITY: {
// WARNING, maybe very bugged if called in wrong priority
// uses choose triggered ability dialog to select it
UUID savedPriorityPlayer = null;
if (game.getActivePlayerId() != opponent.getId()) {
savedPriorityPlayer = game.getActivePlayerId();
}
// change active player to find and play selected abilities (it's danger and buggy code)
if (savedPriorityPlayer != null) {
game.getState().setPriorityPlayerId(opponent.getId());
game.firePriorityEvent(opponent.getId());
}
List<ActivatedAbility> abilities = opponent.getPlayable(game, true);
Map<String, String> choices = new HashMap<>();
abilities.forEach(ability -> {
MageObject object = ability.getSourceObject(game);
choices.put(ability.getId().toString(), object.getName() + ": " + ability.toString());
});
// TODO: set priority for us?
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose playable ability to activate by opponent " + opponent.getName());
choice.setKeyChoices(choices);
if (feedbackPlayer.choose(Outcome.Detriment, choice, game) && choice.getChoiceKey() != null) {
String needId = choice.getChoiceKey();
Optional<ActivatedAbility> ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst();
if (ability.isPresent()) {
// TODO: set priority for player?
ActivatedAbility activatedAbility = ability.get();
game.informPlayers(feedbackPlayer.getLogName() + " as another player " + opponent.getLogName()
+ " trying to force an activate ability: " + activatedAbility.getGameLogMessage(game));
if (opponent.activateAbility(activatedAbility, game)) {
game.informPlayers("Force to activate ability: DONE");
} else {
game.informPlayers("Force to activate ability: FAIL");
}
}
}
// restore original priority player
if (savedPriorityPlayer != null) {
game.getState().setPriorityPlayerId(savedPriorityPlayer);
game.firePriorityEvent(savedPriorityPlayer);
}
break;
}
case COMMAND_CARDS_ADD_TO_HAND: {
// card
@ -480,7 +448,7 @@ public final class SystemUtil {
cardName = cardChoice.getChoice();
// amount
int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", game);
int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", null, game);
if (cardAmount == 0) {
break;
}
@ -544,6 +512,47 @@ public final class SystemUtil {
break;
}
case COMMAND_UNDER_CONTROL_TAKE: {
Target target = new TargetOpponent().withNotTarget(true).withChooseHint("to take under your control");
Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy();
if (feedbackPlayer.chooseTarget(Outcome.GainControl, target, fakeSourceAbility, game)) {
Player targetPlayer = game.getPlayer(target.getFirstTarget());
if (!targetPlayer.getId().equals(feedbackPlayer.getId())) {
CardUtil.takeControlUnderPlayerStart(game, fakeSourceAbility, feedbackPlayer, targetPlayer, false);
// allow priority play again in same step (for better cheat UX)
targetPlayer.resetPassed();
}
// workaround for refresh priority dialog like avatar click (cheats called from priority in 99%)
game.firePriorityEvent(feedbackPlayer.getId());
}
break;
}
case COMMAND_UNDER_CONTROL_GIVE: {
Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to give control of your player");
Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy();
if (feedbackPlayer.chooseTarget(Outcome.GainControl, target, fakeSourceAbility, game)) {
Player targetPlayer = game.getPlayer(target.getFirstTarget());
if (targetPlayer != null) {
if (!targetPlayer.getId().equals(feedbackPlayer.getId())) {
// give control to another player
CardUtil.takeControlUnderPlayerStart(game, fakeSourceAbility, targetPlayer, feedbackPlayer, false);
} else {
// return control to itself
CardUtil.takeControlUnderPlayerEnd(game, fakeSourceAbility, feedbackPlayer, targetPlayer);
}
}
// workaround for refresh priority dialog like avatar click (cheats called from priority in 99%)
game.firePriorityEvent(feedbackPlayer.getId());
}
break;
}
case COMMAND_SHOW_TEST_DIALOGS: {
testableDialogsRunner.selectAndShowTestableDialog(feedbackPlayer, fakeSourceAbilityTemplate.copy(), game, opponent);
break;
}
default: {
String mes = String.format("Unknown system command: %s", runGroup.name);
errorsList.add(mes);
@ -551,7 +560,6 @@ public final class SystemUtil {
break;
}
}
sendCheatCommandsFeedback(game, feedbackPlayer, errorsList);
return;
}

View file

@ -0,0 +1,65 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.announceX()
*
* @author JayDi85
*/
class AnnounceXTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
boolean isMana; // reason - for mana payment or another value
int min;
int max;
public AnnounceXTestableDialog(boolean isYou, boolean isMana, int min, int max) {
super(String.format("player.announceX(%s)", isYou ? "you" : "AI"),
String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), "");
this.isYou = isYou;
this.isMana = isMana;
this.min = min;
this.max = max;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
String message = "<font color=green>message</font> with html";
int chooseRes;
chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
return result;
}
static public void register(TestableDialogsRunner runner) {
List<Boolean> isYous = Arrays.asList(false, true);
List<Boolean> isManas = Arrays.asList(false, true);
for (boolean isYou : isYous) {
for (boolean isMana : isManas) {
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10));
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10));
}
}
}
}

View file

@ -0,0 +1,74 @@
package mage.utils.testers;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetPermanentOrPlayer;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
abstract class BaseTestableDialog implements TestableDialog {
private final String group;
private final String name;
private final String description;
public BaseTestableDialog(String group, String name, String description) {
this.group = group;
this.name = name;
this.description = description;
}
@Override
final public String getGroup() {
return this.group;
}
@Override
final public String getName() {
return this.name;
}
@Override
final public String getDescription() {
return this.description;
}
@Override
final public void showResult(Player player, Game game, String result) {
// show message with result
game.informPlayer(player, result);
// reset game and gui (in most use cases it must return to player's priority)
game.firePriorityEvent(player.getId());
}
static Target createAnyTarget(int min, int max) {
return createAnyTarget(min, max, false);
}
private static Target createAnyTarget(int min, int max, boolean notTarget) {
return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget);
}
static Target createCreatureTarget(int min, int max) {
return createCreatureTarget(min, max, false);
}
private static Target createCreatureTarget(int min, int max, boolean notTarget) {
return new TargetCreaturePermanent(min, max).withNotTarget(notTarget);
}
static Target createImpossibleTarget(int min, int max) {
return createImpossibleTarget(min, max, false);
}
private static Target createImpossibleTarget(int min, int max, boolean notTarget) {
return new TargetCreaturePermanent(min, max, new FilterCreaturePermanent(SubType.TROOPER, "rare type"), notTarget);
}
}

View file

@ -0,0 +1,111 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetAmount;
import mage.target.Targets;
import mage.target.common.TargetAnyTargetAmount;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.chooseTarget(amount)
*
* @author JayDi85
*/
class ChooseAmountTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
int distributeAmount;
int targetsMin;
int targetsMax;
public ChooseAmountTestableDialog(boolean isYou, String name, int distributeAmount, int targetsMin, int targetsMax) {
super(String.format("player.chooseTarget(%s, amount)", isYou ? "you" : "AI"),
name,
String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax));
this.isYou = isYou;
this.distributeAmount = distributeAmount;
this.targetsMin = targetsMin;
this.targetsMax = targetsMax;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax);
Player choosingPlayer = this.isYou ? player : opponent;
// TODO: add "damage" word in ability text, so chooseTargetAmount an show diff dialog (due inner logic - distribute damage or 1/1)
boolean chooseRes = choosingPlayer.chooseTargetAmount(Outcome.Benefit, choosingTarget, source, game);
List<String> result = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// test game started with 2 players and 1 land on battlefield
// so it's better to use target limits like 0, 1, 3, 5, max
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
// up to
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5));
// need target
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5));
}
}
}

View file

@ -0,0 +1,111 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.Targets;
import mage.target.common.TargetCardInHand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.choose(cards)
* - player.chooseTarget(cards)
*
* @author JayDi85
*/
class ChooseCardsTestableDialog extends BaseTestableDialog {
TargetCard target;
boolean isTargetChoice; // how to choose - by xxx.choose or xxx.chooseTarget
boolean isYou; // who choose - you or opponent
public ChooseCardsTestableDialog(boolean isTargetChoice, boolean notTarget, boolean isYou, String name, TargetCard target) {
super(String.format("%s(%s, %s, cards)",
isTargetChoice ? "player.chooseTarget" : "player.choose",
isYou ? "you" : "AI",
notTarget ? "not target" : "target"), name, target.toString());
this.isTargetChoice = isTargetChoice;
this.target = target.withNotTarget(notTarget);
this.isYou = isYou;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
TargetCard choosingTarget = this.target.copy();
Player choosingPlayer = this.isYou ? player : opponent;
// make sure hand go first, so user can test diff type of targets
List<Card> all = new ArrayList<>();
all.addAll(choosingPlayer.getHand().getCards(game));
//all.addAll(choosingPlayer.getLibrary().getCards(game));
Cards choosingCards = new CardsImpl(all.stream().limit(100).collect(Collectors.toList()));
boolean chooseRes;
if (this.isTargetChoice) {
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingCards, choosingTarget, source, game);
} else {
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingCards, choosingTarget, source, game);
}
List<String> result = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// test game started with 2 players and 7 cards in hand and 1 draw
// so it's better to use target limits like 0, 1, 3, 9, max
FilterCard anyCard = StaticFilters.FILTER_CARD;
FilterCard impossibleCard = new FilterCard();
impossibleCard.add(SubType.TROOPER.getPredicate());
List<Boolean> notTargets = Arrays.asList(false, true);
List<Boolean> isYous = Arrays.asList(false, true);
List<Boolean> isTargetChoices = Arrays.asList(false, true);
for (boolean notTarget : notTargets) {
for (boolean isYou : isYous) {
for (boolean isTargetChoice : isTargetChoices) {
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0, X=0", new TargetCardInHand(0, 0, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 1", new TargetCardInHand(1, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 3", new TargetCardInHand(3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 9", new TargetCardInHand(9, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-1", new TargetCardInHand(0, 1, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-3", new TargetCardInHand(0, 3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-9", new TargetCardInHand(0, 9, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand any", new TargetCardInHand(0, Integer.MAX_VALUE, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 1-3", new TargetCardInHand(1, 3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 2-3", new TargetCardInHand(2, 3, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 2-9", new TargetCardInHand(2, 9, anyCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 8-9", new TargetCardInHand(8, 9, anyCard)));
//
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0, X=0", new TargetCardInHand(0, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 1", new TargetCardInHand(1, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 3", new TargetCardInHand(3, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0-1", new TargetCardInHand(0, 1, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0-3", new TargetCardInHand(0, 3, impossibleCard)));
runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible any", new TargetCardInHand(0, Integer.MAX_VALUE, impossibleCard)));
}
}
}
}
}

View file

@ -0,0 +1,66 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.choices.*;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.choose(choice)
*
* @author JayDi85
*/
class ChooseChoiceTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
Choice choice;
public ChooseChoiceTestableDialog(boolean isYou, String name, Choice choice) {
super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"), name, choice.getClass().getSimpleName());
this.isYou = isYou;
this.choice = choice;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
Choice dialog = this.choice.copy();
boolean chooseRes = choosingPlayer.choose(Outcome.Benefit, dialog, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
result.add("");
if (dialog.isKeyChoice()) {
String key = dialog.getChoiceKey();
result.add(String.format("* selected key: %s (%s)", key, dialog.getKeyChoices().getOrDefault(key, null)));
} else {
result.add(String.format("* selected value: %s", dialog.getChoice()));
}
return result;
}
static public void register(TestableDialogsRunner runner) {
// TODO: add require option
// TODO: add ChoiceImpl with diff popup hints
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceBasicLandType()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceCardType()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceColor()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceColorOrArtifact()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceCreatureType(null, null))); // TODO: must be dynamic to pass game/source
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceLandType()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceLeftOrRight()));
runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoicePlaneswalkerType())); // TODO: must be dynamic to pass game/source
}
}
}

View file

@ -0,0 +1,69 @@
package mage.utils.testers;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* Part of testable game dialogs
* <p>
* Supported methods:
* - player.choosePile()
*
* @author JayDi85
*/
class ChoosePileTestableDialog extends BaseTestableDialog {
boolean isYou; // who choose - you or opponent
int pileSize1;
int pileSize2;
public ChoosePileTestableDialog(boolean isYou, int pileSize1, int pileSize2) {
super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"), "pile sizes: " + pileSize1 + " and " + pileSize2, "");
this.isYou = isYou;
this.pileSize1 = pileSize1;
this.pileSize2 = pileSize2;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
// TODO: it's ok to show broken title - must add html support in windows's title someday
String mainMessage = "main <font color=green>message</font> with html" + CardUtil.getSourceLogName(game, source);
// random piles (make sure it contain good amount of cards)
List<Card> all = new ArrayList<>(game.getCards());
Collections.shuffle(all);
List<Card> pile1 = all.stream().limit(this.pileSize1).collect(Collectors.toList());
Collections.shuffle(all);
List<Card> pile2 = all.stream().limit(this.pileSize2).collect(Collectors.toList());
Player choosingPlayer = this.isYou ? player : opponent;
boolean chooseRes = choosingPlayer.choosePile(Outcome.Benefit, mainMessage, pile1, pile2, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
result.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2"));
return result;
}
static public void register(TestableDialogsRunner runner) {
List<Boolean> isYous = Arrays.asList(false, true);
for (boolean isYou : isYous) {
runner.registerDialog(new ChoosePileTestableDialog(isYou, 3, 5));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 10, 10));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 30, 30));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 90, 90));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 0, 10));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 10, 0));
runner.registerDialog(new ChoosePileTestableDialog(isYou, 0, 0));
}
}
}

Some files were not shown because too many files have changed in this diff Show more