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> <parent>
<groupId>org.mage</groupId> <groupId>org.mage</groupId>
<artifactId>mage-root</artifactId> <artifactId>mage-root</artifactId>
<version>1.4.54</version> <version>1.4.57</version>
</parent> </parent>
<artifactId>mage-client</artifactId> <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 CLASSPATH=%JAVA_HOME%/bin;%CLASSPATH%
set PATH=%JAVA_HOME%/bin;%PATH% set PATH=%JAVA_HOME%/bin;%PATH%
:NOJAVADIR :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"`" 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 #!/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 CLASSPATH=%JAVA_HOME%/bin;%CLASSPATH%
set PATH=%JAVA_HOME%/bin;%PATH% set PATH=%JAVA_HOME%/bin;%PATH%
:NOJAVADIR :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.plugins.impl.Plugins;
import mage.client.preference.MagePreferences; import mage.client.preference.MagePreferences;
import mage.client.remote.CallbackClientImpl; import mage.client.remote.CallbackClientImpl;
import mage.client.remote.XmageURLConnection;
import mage.client.table.TablesPane; import mage.client.table.TablesPane;
import mage.client.table.TablesPanel; import mage.client.table.TablesPanel;
import mage.client.tournament.TournamentPane; 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.access.TConfig;
import net.java.truevfs.kernel.spec.FsAccessOption; import net.java.truevfs.kernel.spec.FsAccessOption;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.junit.Assert;
import org.mage.card.arcane.ManaSymbols; import org.mage.card.arcane.ManaSymbols;
import org.mage.card.arcane.SvgUtils; import org.mage.card.arcane.SvgUtils;
import org.mage.plugins.card.images.DownloadPicturesService; import org.mage.plugins.card.images.DownloadPicturesService;
@ -196,19 +198,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
} }
public MageFrame() throws MageException { 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(); setWindowTitle();
// mac os only: enable full screen support in java 8 (java 11+ try to use it all the time) // 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() { private void bootstrapSetsAndFormats() {
LOGGER.info("Loading sets and formats..."); LOGGER.info("Loading sets and formats...");
ConstructedFormats.ensureLists(); ConstructedFormats.ensureLists();
@ -925,11 +949,15 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
} catch (SocketException ex) { } catch (SocketException ex) {
} }
currentConnection.setUserIdStr(System.getProperty("user.name") + ":" + System.getProperty("os.name") + ":" + MagePreferences.getUserNames() + ":" + allMAC); currentConnection.setUserIdStr(System.getProperty("user.name") + ":" + System.getProperty("os.name") + ":" + MagePreferences.getUserNames() + ":" + allMAC);
currentConnection.setProxyType(proxyType); if (PreferencesDialog.NETWORK_ENABLE_PROXY_SUPPORT) {
currentConnection.setProxyHost(proxyServer); currentConnection.setProxyType(proxyType);
currentConnection.setProxyPort(proxyPort); currentConnection.setProxyHost(proxyServer);
currentConnection.setProxyUsername(proxyUsername); currentConnection.setProxyPort(proxyPort);
currentConnection.setProxyPassword(proxyPassword); currentConnection.setProxyUsername(proxyUsername);
currentConnection.setProxyPassword(proxyPassword);
} else {
currentConnection.setProxyType(ProxyType.NONE);
}
setUserPrefsToConnection(currentConnection); setUserPrefsToConnection(currentConnection);
} }
@ -1502,7 +1530,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
if (!Charset.defaultCharset().toString().equals("UTF-8")) { if (!Charset.defaultCharset().toString().equals("UTF-8")) {
LOGGER.warn("WARNING, bad charset. Some images will not be downloaded. You must:"); LOGGER.warn("WARNING, bad charset. Some images will not be downloaded. You must:");
LOGGER.warn("* Open launcher -> settings -> java -> client java options"); 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(); startTime = System.currentTimeMillis();

View file

@ -114,12 +114,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
int cardWidth = getCardWidth(); int cardWidth = getCardWidth();
int cardHeight = getCardHeight(); int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth); int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING); int dx = x % (cardWidth + getGridPadding());
int col = x / (cardWidth + GRID_PADDING); int col = x / (cardWidth + getGridPadding());
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size(); int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
int countLabelHeight = getCountLabelHeight(); int countLabelHeight = getCountLabelHeight();
if (dx < GRID_PADDING && col < gridWidth) { if (dx < getGridPadding() && col < gridWidth) {
// Which row to add to? // Which row to add to?
int curY = countLabelHeight; int curY = countLabelHeight;
int rowIndex = 0; int rowIndex = 0;
@ -142,7 +142,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Insert between two columns // Insert between two columns
insertArrow.setIcon(INSERT_COL_ICON); insertArrow.setIcon(INSERT_COL_ICON);
insertArrow.setSize(64, 64); 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 { } else {
// Clamp to a new col one after the current last one // Clamp to a new col one after the current last one
col = Math.min(col, gridWidth); col = Math.min(col, gridWidth);
@ -184,7 +184,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Position arrow // Position arrow
insertArrow.setIcon(INSERT_ROW_ICON); insertArrow.setIcon(INSERT_ROW_ICON);
insertArrow.setSize(64, 32); 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 cardWidth = getCardWidth();
int cardHeight = getCardHeight(); int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth); int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING); int dx = x % (cardWidth + getGridPadding());
int col = x / (cardWidth + GRID_PADDING); int col = x / (cardWidth + getGridPadding());
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size(); int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
int countLabelHeight = getCountLabelHeight(); int countLabelHeight = getCountLabelHeight();
if (dx < GRID_PADDING && col < gridWidth) { if (dx < getGridPadding() && col < gridWidth) {
// Which row to add to? // Which row to add to?
int curY = countLabelHeight; int curY = countLabelHeight;
int rowIndex = 0; int rowIndex = 0;
@ -334,7 +334,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Add new cards to grid // Add new cards to grid
for (CardView card : cards) { for (CardView card : cards) {
card.setSelected(true); card.setSelected(true);
addCardView(card, false); addCardView(card, null);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
} }
layoutGrid(); layoutGrid();
@ -575,7 +575,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Constants // Constants
private static final int DEFAULT_COUNT_LABEL_HEIGHT = 40; // can contain 1 or 2 lines 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_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")); 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; selectBySearchPanelC.fill = GridBagConstraints.VERTICAL;
searchByTextField = new JTextField(); 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() { searchByTextField.addKeyListener(new KeyAdapter() {
@Override @Override
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
@ -1253,10 +1253,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
selectionPanel.setSize(x2 - x1, y2 - y1); selectionPanel.setSize(x2 - x1, y2 - y1);
// First and last cols // First and last cols
int col1 = x1 / (cardWidth + GRID_PADDING); int col1 = x1 / (cardWidth + getGridPadding());
int col2 = x2 / (cardWidth + GRID_PADDING); int col2 = x2 / (cardWidth + getGridPadding());
int offsetIntoCol2 = x2 % (cardWidth + GRID_PADDING); int offsetIntoCol2 = x2 % (cardWidth + getGridPadding());
if (offsetIntoCol2 < GRID_PADDING) { if (offsetIntoCol2 < getGridPadding()) {
--col2; --col2;
} }
@ -1322,7 +1322,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// re-insert // re-insert
for (CardView card : allCards) { for (CardView card : allCards) {
sortIntoGrid(card); sortIntoGrid(card, null);
} }
trimGrid(); trimGrid();
@ -1717,7 +1717,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (acard.getName().equals(card.getName())) { if (acard.getName().equals(card.getName())) {
CardView pimpedCard = new CardView(acard); CardView pimpedCard = new CardView(acard);
addCardView(pimpedCard, false); addCardView(pimpedCard, null);
eventSource.fireEvent(pimpedCard, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(pimpedCard, ClientEventType.DECK_ADD_SPECIFIC_CARD);
pimpedCards.put(pimpedCard, 1); pimpedCards.put(pimpedCard, 1);
didModify = true; didModify = true;
@ -1729,7 +1729,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (didModify) { if (didModify) {
for (CardView c : pimpedCards.keySet()) { for (CardView c : pimpedCards.keySet()) {
sortIntoGrid(c); sortIntoGrid(c, null);
} }
trimGrid(); trimGrid();
@ -1762,7 +1762,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
CardView oldestCardView = new CardView(oldestCardInfo.createMockCard()); CardView oldestCardView = new CardView(oldestCardInfo.createMockCard());
this.removeCardView(card); this.removeCardView(card);
eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_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); eventSource.fireEvent(oldestCardView, ClientEventType.DECK_ADD_SPECIFIC_CARD);
newStack.add(oldestCardView); newStack.add(oldestCardView);
} else { } else {
@ -1814,10 +1814,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView newCard : cardsView.values()) { for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) { if (!cardViews.containsKey(newCard.getId())) {
// Is a new card // Is a new card
addCardView(newCard, false); addCardView(newCard, null);
// Put it into the appropirate place in the grid given the current sort // Put it into the appropirate place in the grid given the current sort
sortIntoGrid(newCard); sortIntoGrid(newCard, null);
// Mark // Mark
didModify = true; didModify = true;
@ -1837,7 +1837,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView newCard : cardsView.values()) { for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) { if (!cardViews.containsKey(newCard.getId())) {
// Add the new card // Add the new card
addCardView(newCard, false); addCardView(newCard, null);
// Add the new card to tracking // Add the new card to tracking
Map<String, List<CardView>> forSetCode; Map<String, List<CardView>> forSetCode;
@ -1889,7 +1889,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (List<CardView> orphans : tracked.values()) { for (List<CardView> orphans : tracked.values()) {
for (CardView orphan : orphans) { for (CardView orphan : orphans) {
logger.info("Orphan when setting with layout: "); 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()); 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); allCards.add(card);
// Update counts // Update counts
@ -2019,8 +2019,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
cardContent.add(cardPanel); cardContent.add(cardPanel);
cardViews.put(card.getId(), cardPanel); cardViews.put(card.getId(), cardPanel);
if (duplicated) { if (duplicatedFromCard != null) {
sortIntoGrid(card); sortIntoGrid(card, duplicatedFromCard);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
// clear grid from empty rows // 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. * @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 // row 1 must exists
if (cardGrid.isEmpty()) { if (cardGrid.isEmpty()) {
cardGrid.add(0, new ArrayList<>()); cardGrid.add(0, new ArrayList<>());
@ -2336,7 +2350,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
} else { } else {
String description = cardSort.getComparator().getCategoryName(stack.get(0)); String description = cardSort.getComparator().getCategoryName(stack.get(0));
DragCardGrid.updateCountLabel(countLabel, stack.size(), description); 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.setSize(cardWidth, countLabelHeight);
countLabel.setVisible(true); countLabel.setVisible(true);
} }
@ -2348,7 +2362,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (int i = 0; i < stack.size(); ++i) { for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i); CardView card = stack.get(i);
MageCard view = cardViews.get(card.getId()); 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; int y = currentY + i * cardTopHeight;
view.setCardBounds(x, y, cardWidth, cardHeight); view.setCardBounds(x, y, cardWidth, cardHeight);
cardContent.setLayer(view, layerIndex++); 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 // 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); maxStackSize.set(rowIndex, rowMaxStackSize);
currentY += (cardTopHeight * (rowMaxStackSize - 1) + cardHeight) + countLabelHeight; currentY += (cardTopHeight * (rowMaxStackSize - 1) + cardHeight) + countLabelHeight;
} }
// Resize card container // Resize card container
cardContent.setPreferredSize(new Dimension(maxWidth, currentY - countLabelHeight + GRID_PADDING)); cardContent.setPreferredSize(new Dimension(maxWidth, currentY - countLabelHeight + getGridPadding()));
//cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + GRID_PADDING); //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() { 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"); 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; boolean isUserInfoOrGameOrStatus = messageType == MessageType.USER_INFO || messageType == MessageType.GAME || messageType == MessageType.STATUS;
if (isUserInfoOrGameOrStatus || cachedProfanityFilterValue.equals("0") || (!cachedProfanityFilterValue.equals("0") && !isContainsSwearing)) { if (isUserInfoOrGameOrStatus || cachedProfanityFilterValue.equals("0") || (!cachedProfanityFilterValue.equals("0") && !isContainsSwearing)) {
if (username != null && !username.isEmpty()) { if (username != null && !username.isEmpty()) {

View file

@ -83,7 +83,7 @@ public enum CombatManager {
UUID defenderId = group.getDefenderId(); UUID defenderId = group.getDefenderId();
if (defenderId != null) { if (defenderId != null) {
// if attacker was blocked then use another arrow color // 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); parentPoint = getParentPoint(attackerCard);
PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId); PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId);
if (p != null) { 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.Deck;
import mage.cards.decks.DeckValidator; import mage.cards.decks.DeckValidator;
import mage.cards.decks.DeckValidatorError; import mage.cards.decks.DeckValidatorError;
import mage.cards.decks.importer.DeckImporter;
import org.unbescape.html.HtmlEscape; import org.unbescape.html.HtmlEscape;
import org.unbescape.html.HtmlEscapeLevel; import org.unbescape.html.HtmlEscapeLevel;
import org.unbescape.html.HtmlEscapeType; import org.unbescape.html.HtmlEscapeType;
import javax.swing.*; import javax.swing.*;
import java.awt.*; 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 { 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_MINIMUM = new Dimension(75, 25);
protected static final Dimension DIM_MAXIMUM = new Dimension(150, 75); 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 = 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 static final int TOOLTIP_MAX_ERRORS = 20; // max errors to show in tooltip
protected Deck currentDeck; protected Deck currentDeck;
@ -92,19 +95,6 @@ public class LegalityLabel extends JLabel {
return button; 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) { protected String escapeHtml(String string) {
return HtmlEscape.escapeHtml(string, HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, HtmlEscapeLevel.LEVEL_0_ONLY_MARKUP_SIGNIFICANT_EXCEPT_APOS); 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); setBackground(color);
} }
public void showState(Color color, String tooltip) { public void showState(Color color, String tooltip, boolean useErrors) {
setBackground(color); 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) { public void showStateUnknown(String tooltip) {
showState(COLOR_UNKNOWN, tooltip); showState(COLOR_UNKNOWN, tooltip, true);
} }
public void showStateLegal(String tooltip) { public void showStateLegal(String tooltip) {
showState(COLOR_LEGAL, tooltip); showState(COLOR_LEGAL, tooltip, true);
} }
public void showStatePartlyLegal(String tooltip) { public void showStatePartlyLegal(String tooltip) {
showState(COLOR_PARTLY_LEGAL, tooltip); showState(COLOR_PARTLY_LEGAL, tooltip, true);
} }
public void showStateNotLegal(String tooltip) { public void showStateNotLegal(String tooltip) {
showState(COLOR_NOT_LEGAL, tooltip); showState(COLOR_NOT_LEGAL, tooltip, true);
} }
public void validateDeck(Deck deck) { public void validateDeck(Deck deck) {
@ -191,28 +189,13 @@ public class LegalityLabel extends JLabel {
} }
} }
public void validateDeck(File deckFile) { public java.util.List<String> selectCards() {
deckFile = deckFile.getAbsoluteFile(); if (this.validator == null) {
if (!deckFile.exists()) { return Collections.emptyList();
errorMessage = String.format("Deck file '%s' does not exist.", deckFile.getAbsolutePath());
showStateUnknown("<html><body><b>No Deck loaded!</b></body></html>");
return;
} }
try { return this.validator.getErrorsList().stream()
StringBuilder errorMessages = new StringBuilder(); .map(DeckValidatorError::getCardName)
Deck deck = Deck.load(DeckImporter.importDeckFromFile(deckFile.getAbsolutePath(), errorMessages, false), true, true); .filter(Objects::nonNull)
errorMessage = errorMessages.toString(); .collect(Collectors.toList());
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));
} }
} }

View file

@ -41,7 +41,7 @@ public class MageTextArea extends MageEditorPane {
// prepare text format as header and details texts // 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 // 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("<body style='font-family:Dialog;font-size:");
buffer.append(GUISizeHelper.gameFeedbackPanelMainMessageFontSize); buffer.append(GUISizeHelper.gameFeedbackPanelMainMessageFontSize);

View file

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

View file

@ -81,7 +81,7 @@ public class DialogContainer extends JPanel {
case EMBLEMS: { case EMBLEMS: {
backgroundColor = new Color(0, 0, 50, 110); backgroundColor = new Color(0, 0, 50, 110);
alpha = 0; 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); add(dlg);
dlg.setLocation(X_OFFSET + 10, Y_OFFSET + 10); dlg.setLocation(X_OFFSET + 10, Y_OFFSET + 10);
dlg.updateSize(params.rect.width - 80, params.rect.height - 80); 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.CardCriteria;
import mage.cards.repository.CardInfo; import mage.cards.repository.CardInfo;
import mage.cards.repository.ExpansionRepository; import mage.cards.repository.ExpansionRepository;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.sets.ConstructedFormats; import mage.client.util.sets.ConstructedFormats;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.ColoredManaSymbol; import mage.constants.ColoredManaSymbol;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.util.RandomUtil; 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. * Generates random card pool and builds a deck.
* *
* @author nantuko * @author nantuko, Simown, JayDi85
* @author Simown
*/ */
public final class DeckGenerator { public final class DeckGenerator {
@ -124,7 +125,7 @@ public final class DeckGenerator {
genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(), genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(),
genDialog.getLandPercentage(), allowedColors, genDialog.isSingleton(), genDialog.isColorless(), 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()]); final String[] sets = setsToUse.toArray(new String[setsToUse.size()]);
@ -155,8 +156,8 @@ public final class DeckGenerator {
// Generate basic land cards // Generate basic land cards
Map<String, List<CardInfo>> basicLands = DeckGeneratorPool.generateBasicLands(setsToUse); Map<String, List<CardInfo>> basicLands = DeckGeneratorPool.generateBasicLands(setsToUse);
DeckGeneratorPool.generateSpells(creatureCriteria, genPool.getCreatureCount()); DeckGeneratorPool.generateSpells(creatureCriteria, genPool.getCreatureCount(), genPool.getCommandersCount());
DeckGeneratorPool.generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount()); DeckGeneratorPool.generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount(), 0);
DeckGeneratorPool.generateLands(genDialog.useNonBasicLand(), nonBasicLandCriteria, basicLands); DeckGeneratorPool.generateLands(genDialog.useNonBasicLand(), nonBasicLandCriteria, basicLands);
// Reconstructs the final deck and adjusts for Math rounding and/or missing cards // Reconstructs the final deck and adjusts for Math rounding and/or missing cards

View file

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

View file

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

View file

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

View file

@ -780,7 +780,7 @@
</Component> </Component>
<Component class="javax.swing.JTextField" name="jTextFieldSearch"> <Component class="javax.swing.JTextField" name="jTextFieldSearch">
<Properties> <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> </Properties>
</Component> </Component>
<Component class="javax.swing.JCheckBox" name="chkNames"> <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.setSelected(true);
chkNames.setText("Names"); chkNames.setText("Names");

View file

@ -110,7 +110,7 @@ public class DeckArea extends javax.swing.JPanel {
sideboardList.deselectAll(); sideboardList.deselectAll();
for (CardView card : cards) { for (CardView card : cards) {
CardView newCard = new CardView(card); 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(); deckList.deselectAll();
for (CardView card : cards) { for (CardView card : cards) {
CardView newCard = new CardView(card); 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"> <Component class="mage.client.deckeditor.DeckLegalityPanel" name="deckLegalityDisplay">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[245, 155]"/> <Dimension value="[245, 255]"/>
</Property> </Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[85, 155]"/> <Dimension value="[85, 255]"/>
</Property> </Property>
<Property name="opaque" type="boolean" value="false"/> <Property name="opaque" type="boolean" value="false"/>
</Properties> </Properties>

View file

@ -121,12 +121,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
if (!SwingUtilities.isLeftMouseButton(e)) { if (!SwingUtilities.isLeftMouseButton(e)) {
return; return;
} }
List<String> cardNames = new ArrayList<>();
LegalityLabel label = (LegalityLabel) e.getComponent(); LegalityLabel label = (LegalityLabel) e.getComponent();
label.getValidator().getErrorsList().stream() List<String> cardNames = new ArrayList<>(label.selectCards());
.map(DeckValidatorError::getCardName)
.filter(Objects::nonNull)
.forEach(cardNames::add);
deckArea.getDeckList().deselectAll(); deckArea.getDeckList().deselectAll();
deckArea.getDeckList().selectByName(cardNames); deckArea.getDeckList().selectByName(cardNames);
deckArea.getSideboardList().deselectAll(); deckArea.getSideboardList().deselectAll();
@ -1290,8 +1286,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
panelInfo.setOpaque(false); panelInfo.setOpaque(false);
deckLegalityDisplay.setMaximumSize(new java.awt.Dimension(245, 155)); deckLegalityDisplay.setMaximumSize(new java.awt.Dimension(245, 255));
deckLegalityDisplay.setMinimumSize(new java.awt.Dimension(85, 155)); deckLegalityDisplay.setMinimumSize(new java.awt.Dimension(85, 255));
deckLegalityDisplay.setOpaque(false); deckLegalityDisplay.setOpaque(false);
deckLegalityDisplay.setVisible(false); deckLegalityDisplay.setVisible(false);
@ -1313,7 +1309,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
panelInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) panelInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(panelInfoLayout.createSequentialGroup() .addGroup(panelInfoLayout.createSequentialGroup()
.addContainerGap() .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) .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) .addComponent(bigCard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap()) .addContainerGap())

View file

@ -5,6 +5,8 @@ import mage.cards.decks.Deck;
import mage.cards.decks.DeckValidator; import mage.cards.decks.DeckValidator;
import mage.cards.mock.MockCard; import mage.cards.mock.MockCard;
import mage.cards.mock.MockSplitCard; import mage.cards.mock.MockSplitCard;
import mage.client.components.BracketLegalityLabel;
import mage.client.components.EdhPowerLevelLegalityLabel;
import mage.client.components.LegalityLabel; import mage.client.components.LegalityLabel;
import mage.deck.*; import mage.deck.*;
import org.apache.log4j.Logger; 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 { 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() new Frontier(), new HistoricalType2(), new PennyDreadfulCommander(), new EuropeanHighlander(), new CanadianHighlander()
// not used: new Eternal(), new Momir(), new TinyLeaders() // not used: new Eternal(), new Momir(), new TinyLeaders()
).forEach(this::addLegalityLabel); ).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(); addHidePanelButton();
revalidate(); revalidate();
@ -147,5 +157,4 @@ public class DeckLegalityPanel extends javax.swing.JPanel {
.map(LegalityLabel.class::cast) .map(LegalityLabel.class::cast)
.forEach(label -> label.validateDeck(deckToValidate)); .forEach(label -> label.validateDeck(deckToValidate));
} }
} }

View file

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

View file

@ -87,7 +87,7 @@ public class AddLandDialog extends MageDialog {
landSetNames.add(expansionInfo.getName()); landSetNames.add(expansionInfo.getName());
} }
if (landSetNames.isEmpty()) { 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) { if (landSetNames.size() > 1) {
landSetNames.add("<Random lands>"); landSetNames.add("<Random lands>");
@ -271,7 +271,6 @@ public class AddLandDialog extends MageDialog {
spnForest.setModel(new javax.swing.SpinnerNumberModel(0, 0, null, 1)); spnForest.setModel(new javax.swing.SpinnerNumberModel(0, 0, null, 1));
lblForestIcon.setToolTipText("");
lblForestIcon.setMaximumSize(new java.awt.Dimension(22, 20)); lblForestIcon.setMaximumSize(new java.awt.Dimension(22, 20));
lblForestIcon.setMinimumSize(new java.awt.Dimension(22, 20)); lblForestIcon.setMinimumSize(new java.awt.Dimension(22, 20));
lblForestIcon.setPreferredSize(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 }//GEN-LAST:event_btnSetFastSearchActionPerformed
private void autoAddLands() { private void autoAddLands() {
// suggest lands amount for deck without lands
int deckSize = ((Number) spnDeckSize.getValue()).intValue(); int deckSize = ((Number) spnDeckSize.getValue()).intValue();
int[] lands = DeckBuildUtils.landCountSuggestion(deckSize, deck.getMaindeckCards()); int[] lands = DeckBuildUtils.landCountSuggestion(deckSize, deck.getMaindeckCards());
spnPlains.setValue(lands[0]); spnPlains.setValue(lands[0]);

View file

@ -177,7 +177,7 @@ public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconi
super.show(); super.show();
// auto-position on first usage // auto-position on first usage
if (positioned) { if (!positioned) {
showAndPositionWindow(); showAndPositionWindow();
} }
} }
@ -190,9 +190,23 @@ public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconi
Point centered = SettingsManager.instance.getComponentPosition(width, height); Point centered = SettingsManager.instance.getComponentPosition(width, height);
if (!positioned) { if (!positioned) {
// starting position // 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 // little randomize to see multiple opened windows
int xPos = centered.x / 2 + RandomUtil.nextInt(50); int xPos = centered.x / 2 + RandomUtil.nextInt(50);
int yPos = centered.y / 2 + RandomUtil.nextInt(50); int yPos = centered.y / 2 + RandomUtil.nextInt(50);
CardInfoWindowDialog.this.setLocation(xPos, yPos); CardInfoWindowDialog.this.setLocation(xPos, yPos);
show(); show();
positioned = true; positioned = true;

View file

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

View file

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

View file

@ -150,7 +150,6 @@ public class NewTableDialog extends MageDialog {
popupSaveSettings.add(menuSaveSettings2); popupSaveSettings.add(menuSaveSettings2);
menuLoadSettingsLast.setText("Load from last time"); menuLoadSettingsLast.setText("Load from last time");
menuLoadSettingsLast.setToolTipText("");
menuLoadSettingsLast.addActionListener(new java.awt.event.ActionListener() { menuLoadSettingsLast.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsLastActionPerformed(evt); menuLoadSettingsLastActionPerformed(evt);
@ -160,7 +159,6 @@ public class NewTableDialog extends MageDialog {
popupLoadSettings.add(separator1); popupLoadSettings.add(separator1);
menuLoadSettings1.setText("Load from config 1"); menuLoadSettings1.setText("Load from config 1");
menuLoadSettings1.setToolTipText("");
menuLoadSettings1.addActionListener(new java.awt.event.ActionListener() { menuLoadSettings1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettings1ActionPerformed(evt); menuLoadSettings1ActionPerformed(evt);
@ -178,7 +176,6 @@ public class NewTableDialog extends MageDialog {
popupLoadSettings.add(separator2); popupLoadSettings.add(separator2);
menuLoadSettingsDefault.setText("Load default settings"); menuLoadSettingsDefault.setText("Load default settings");
menuLoadSettingsDefault.setToolTipText("");
menuLoadSettingsDefault.addActionListener(new java.awt.event.ActionListener() { menuLoadSettingsDefault.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsDefaultActionPerformed(evt); menuLoadSettingsDefaultActionPerformed(evt);
@ -228,7 +225,6 @@ public class NewTableDialog extends MageDialog {
}); });
lblSkillLevel.setText("Skill Level:"); 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."); 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() { private MatchOptions getMatchOptions() {
// current settings // current settings
GameTypeView gameType = (GameTypeView) cbGameType.getSelectedItem(); 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); options.getPlayerTypes().add(PlayerType.HUMAN);
for (TablePlayerPanel player : players) { for (TablePlayerPanel player : players) {
options.getPlayerTypes().add(player.getPlayerType()); options.getPlayerTypes().add(player.getPlayerType());

View file

@ -35,7 +35,6 @@
<MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsLast"> <MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsLast">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="Load from last time"/> <Property name="text" type="java.lang.String" value="Load from last time"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Events> <Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettingsLastActionPerformed"/> <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"> <MenuItem class="javax.swing.JMenuItem" name="menuLoadSettings1">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="Load from config 1"/> <Property name="text" type="java.lang.String" value="Load from config 1"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Events> <Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettings1ActionPerformed"/> <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"> <MenuItem class="javax.swing.JMenuItem" name="menuLoadSettingsDefault">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="Load default settings"/> <Property name="text" type="java.lang.String" value="Load default settings"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Events> <Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="menuLoadSettingsDefaultActionPerformed"/> <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"/> <Component id="pnlPacks" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0"> <Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="0" 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"> <Group type="102" alignment="0" attributes="0">
<Component id="lblNbrPlayers" min="-2" max="-2" attributes="0"/> <Component id="lblNbrPlayers" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="spnNumPlayers" min="-2" pref="46" 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"/> <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> </Group>
<Component id="lblPacks" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblPlayer1" alignment="0" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<EmptySpace min="-2" pref="28" max="-2" attributes="0"/> <EmptySpace min="-2" pref="28" max="-2" attributes="0"/>
@ -122,10 +121,7 @@
<EmptySpace max="32767" attributes="0"/> <EmptySpace max="32767" attributes="0"/>
<Component id="lblNumRounds" min="-2" max="-2" attributes="0"/> <Component id="lblNumRounds" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Group type="102" attributes="0"> <Component id="lblConstructionTime" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="lblConstructionTime" min="-2" max="-2" attributes="0"/>
</Group>
</Group> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
@ -203,11 +199,7 @@
<Component id="lbBufferTime" min="-2" max="-2" attributes="0"/> <Component id="lbBufferTime" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="cbBufferTime" min="-2" pref="101" max="-2" attributes="1"/> <Component id="cbBufferTime" min="-2" pref="101" max="-2" attributes="1"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" pref="87" 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="chkRollbackTurnsAllowed" min="-2" max="-2" attributes="0"/> <Component id="chkRollbackTurnsAllowed" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="cbAllowSpectators" min="-2" 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"/> <EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="3" 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="chkRollbackTurnsAllowed" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbAllowSpectators" alignment="3" max="32767" attributes="0"/> <Component id="cbAllowSpectators" alignment="3" max="32767" attributes="0"/>
</Group> </Group>
@ -290,7 +280,7 @@
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<Component id="pnlPacks" min="-2" max="-2" attributes="0"/> <Component id="pnlPacks" min="-2" max="-2" attributes="0"/>
<EmptySpace 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"/> <EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" max="-2" attributes="0"> <Group type="103" groupAlignment="1" max="-2" attributes="0">
<Group type="103" alignment="1" groupAlignment="3" 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"/> <Component id="lblNumRounds" alignment="3" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="lblNbrPlayers" alignment="1" max="32767" attributes="0"/> <Component id="lblNbrPlayers" alignment="1" max="32767" attributes="0"/>
<Component id="spnNumPlayers" alignment="1" pref="23" max="32767" attributes="1"/> <Group type="103" alignment="1" groupAlignment="3" attributes="0">
<Component id="pnlDraftOptions" alignment="1" pref="23" max="32767" attributes="1"/> <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> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" pref="27" 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"/>
<Component id="lblPlayer1" min="-2" pref="25" max="-2" attributes="0"/> <Component id="lblPlayer1" min="-2" pref="25" max="-2" attributes="0"/>
</Group> </Group>
<Group type="103" groupAlignment="3" attributes="0"> <Group type="103" groupAlignment="3" attributes="0">
@ -507,19 +499,18 @@
<Property name="text" type="java.lang.String" value="Players:"/> <Property name="text" type="java.lang.String" value="Players:"/>
</Properties> </Properties>
</Component> </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"> <Component class="javax.swing.JSpinner" name="spnNumPlayers">
<Events> <Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="spnNumPlayersStateChanged"/> <EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="spnNumPlayersStateChanged"/>
</Events> </Events>
</Component> </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> <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> </Events>
</Component> </Component>
<Container class="javax.swing.JPanel" name="pnlDraftOptions"> <Container class="javax.swing.JPanel" name="pnlDraftOptions">
@ -618,7 +609,7 @@
</DimensionLayout> </DimensionLayout>
<DimensionLayout dim="1"> <DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0"> <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> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
@ -663,7 +654,6 @@
<EtchetBorder/> <EtchetBorder/>
</Border> </Border>
</Property> </Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
@ -676,9 +666,6 @@
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JSpinner" name="spnQuitRatio"> <Component class="javax.swing.JSpinner" name="spnQuitRatio">
<Properties>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="lblMinimumRating"> <Component class="javax.swing.JLabel" name="lblMinimumRating">
<Properties> <Properties>

View file

@ -40,6 +40,11 @@ public class NewTournamentDialog extends MageDialog {
private static final Logger logger = Logger.getLogger(NewTournamentDialog.class); 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 // temp settings on loading players list
private final List<PlayerType> prefPlayerTypes = new ArrayList<>(); private final List<PlayerType> prefPlayerTypes = new ArrayList<>();
private final List<Integer> prefPlayerSkills = new ArrayList<>(); private final List<Integer> prefPlayerSkills = new ArrayList<>();
@ -77,12 +82,15 @@ public class NewTournamentDialog extends MageDialog {
private int getCurrentNumPlayers() { private int getCurrentNumPlayers() {
int res = (Integer) spnNumPlayers.getValue(); int res = (Integer) spnNumPlayers.getValue();
return res > 0 ? res : 2; return Math.max(2, res);
} }
private int getCurrentNumSeats() { private int getCurrentNumSeats() {
int res = (Integer) spnNumSeats.getValue(); if (chkSingleMultiplayerGame.isSelected()) {
return res > 0 ? res : 2; return getCurrentNumPlayers();
} else {
return 2;
}
} }
public void showDialog(UUID roomId) { public void showDialog(UUID roomId) {
@ -159,9 +167,8 @@ public class NewTournamentDialog extends MageDialog {
lblPacks = new javax.swing.JLabel(); lblPacks = new javax.swing.JLabel();
pnlPacks = new javax.swing.JPanel(); pnlPacks = new javax.swing.JPanel();
lblNbrPlayers = new javax.swing.JLabel(); lblNbrPlayers = new javax.swing.JLabel();
lblNbrSeats = new javax.swing.JLabel();
spnNumPlayers = new javax.swing.JSpinner(); spnNumPlayers = new javax.swing.JSpinner();
spnNumSeats = new javax.swing.JSpinner(); chkSingleMultiplayerGame = new javax.swing.JCheckBox();
pnlDraftOptions = new javax.swing.JPanel(); pnlDraftOptions = new javax.swing.JPanel();
jLabel6 = new javax.swing.JLabel(); jLabel6 = new javax.swing.JLabel();
cbDraftTiming = new javax.swing.JComboBox(); cbDraftTiming = new javax.swing.JComboBox();
@ -203,7 +210,6 @@ public class NewTournamentDialog extends MageDialog {
popupSaveSettings.add(menuSaveSettings2); popupSaveSettings.add(menuSaveSettings2);
menuLoadSettingsLast.setText("Load from last time"); menuLoadSettingsLast.setText("Load from last time");
menuLoadSettingsLast.setToolTipText("");
menuLoadSettingsLast.addActionListener(new java.awt.event.ActionListener() { menuLoadSettingsLast.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsLastActionPerformed(evt); menuLoadSettingsLastActionPerformed(evt);
@ -213,7 +219,6 @@ public class NewTournamentDialog extends MageDialog {
popupLoadSettings.add(separator1); popupLoadSettings.add(separator1);
menuLoadSettings1.setText("Load from config 1"); menuLoadSettings1.setText("Load from config 1");
menuLoadSettings1.setToolTipText("");
menuLoadSettings1.addActionListener(new java.awt.event.ActionListener() { menuLoadSettings1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettings1ActionPerformed(evt); menuLoadSettings1ActionPerformed(evt);
@ -231,7 +236,6 @@ public class NewTournamentDialog extends MageDialog {
popupLoadSettings.add(separator2); popupLoadSettings.add(separator2);
menuLoadSettingsDefault.setText("Load default settings"); menuLoadSettingsDefault.setText("Load default settings");
menuLoadSettingsDefault.setToolTipText("");
menuLoadSettingsDefault.addActionListener(new java.awt.event.ActionListener() { menuLoadSettingsDefault.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { public void actionPerformed(java.awt.event.ActionEvent evt) {
menuLoadSettingsDefaultActionPerformed(evt); menuLoadSettingsDefaultActionPerformed(evt);
@ -324,17 +328,17 @@ public class NewTournamentDialog extends MageDialog {
lblNbrPlayers.setText("Players:"); lblNbrPlayers.setText("Players:");
lblNbrSeats.setText("Seats:");
spnNumPlayers.addChangeListener(new javax.swing.event.ChangeListener() { spnNumPlayers.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) { public void stateChanged(javax.swing.event.ChangeEvent evt) {
spnNumPlayersStateChanged(evt); spnNumPlayersStateChanged(evt);
} }
}); });
spnNumSeats.addChangeListener(new javax.swing.event.ChangeListener() { chkSingleMultiplayerGame.setText("play as single game");
public void stateChanged(javax.swing.event.ChangeEvent evt) { chkSingleMultiplayerGame.setToolTipText("<HTML>Allow to play single game with all tourney's players -- e.g. play one game after draft with 4 players");
spnNumSeatsStateChanged(evt); 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.setVerticalGroup(
pnlPlayersLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 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"); btnOk.setText("Create");
@ -411,13 +415,10 @@ public class NewTournamentDialog extends MageDialog {
}); });
pnlRandomPacks.setBorder(javax.swing.BorderFactory.createEtchedBorder()); pnlRandomPacks.setBorder(javax.swing.BorderFactory.createEtchedBorder());
pnlRandomPacks.setToolTipText("");
pnlRandomPacks.setLayout(new javax.swing.BoxLayout(pnlRandomPacks, javax.swing.BoxLayout.Y_AXIS)); pnlRandomPacks.setLayout(new javax.swing.BoxLayout(pnlRandomPacks, javax.swing.BoxLayout.Y_AXIS));
lblQuitRatio.setText("Allowed quit %"); lblQuitRatio.setText("Allowed quit %");
spnQuitRatio.setToolTipText("");
lblMinimumRating.setText("Minimum rating:"); lblMinimumRating.setText("Minimum rating:");
lblMinimumRating.setToolTipText("Players with rating less than this value can't join this table"); 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) .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(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblPacks)
.addComponent(lblPlayer1)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addComponent(lblNbrPlayers) .addComponent(lblNbrPlayers)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addComponent(lblNbrSeats)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnNumSeats, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(lblNumWins)
.addComponent(lblPacks) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblPlayer1)) .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.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGap(28, 28, 28) .addGap(28, 28, 28)
.addComponent(pnlDraftOptions, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(lblNumRounds)) .addComponent(lblNumRounds))
.addGroup(layout.createSequentialGroup() .addComponent(lblConstructionTime))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(lblConstructionTime)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -551,11 +553,7 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(lbBufferTime) .addComponent(lbBufferTime)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cbBufferTime, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(cbBufferTime, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGap(87, 87, 87)
.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(chkRollbackTurnsAllowed) .addComponent(chkRollbackTurnsAllowed)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cbAllowSpectators))))) .addComponent(cbAllowSpectators)))))
@ -567,8 +565,6 @@ public class NewTournamentDialog extends MageDialog {
.addGap(2, 2, 2) .addGap(2, 2, 2)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .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(chkRollbackTurnsAllowed)
.addComponent(cbAllowSpectators, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addComponent(cbAllowSpectators, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
@ -618,20 +614,21 @@ public class NewTournamentDialog extends MageDialog {
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addComponent(pnlPacks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(pnlPacks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .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) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .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(spnNumRounds, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblNumRounds)) .addComponent(lblNumRounds))
.addComponent(lblNbrPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .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) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(pnlDraftOptions, javax.swing.GroupLayout.PREFERRED_SIZE, 23, Short.MAX_VALUE)) .addComponent(spnNumPlayers)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(chkSingleMultiplayerGame)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblNbrSeats, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(lblNumWins)
.addComponent(spnNumSeats)) .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .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)) .addComponent(lblPlayer1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .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) .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 }// </editor-fold>//GEN-END:initComponents
private void cbTournamentTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTournamentTypeActionPerformed 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 = ""; jumpstartPacksFilename = "";
if (cbTournamentType.getSelectedItem().toString().matches(".*Jumpstart.*Custom.*")) { 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 // save last settings
onSaveSettings(0, tOptions); onSaveSettings(0, tOptions);
@ -747,44 +776,29 @@ public class NewTournamentDialog extends MageDialog {
doClose(); doClose();
}//GEN-LAST:event_btnCancelActionPerformed }//GEN-LAST:event_btnCancelActionPerformed
private void updateNumSeats() { private void applyNewPlayersCount() {
int numSeats = (Integer) this.spnNumSeats.getValue(); // 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) { // make sure wins is compatible
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); // is's can be a too long match for 2+ wins in 4+ game
if (numSeats >= tournamentType.getMinPlayers()) { if (chkSingleMultiplayerGame.isSelected()) {
createPlayers(numSeats - 1);
spnNumPlayers.setValue(numSeats);
} else {
numSeats = tournamentType.getMinPlayers();
createPlayers(numSeats - 1);
spnNumPlayers.setValue(numSeats);
spnNumSeats.setValue(numSeats);
}
spnNumWins.setValue(1); spnNumWins.setValue(1);
} }
} }
private void spnNumPlayersStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumPlayersStateChanged private void spnNumPlayersStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumPlayersStateChanged
int numPlayers = getCurrentNumPlayers(); applyNewPlayersCount();
createPlayers(numPlayers - 1);
int numSeats = (Integer) this.spnNumSeats.getValue();
if (numSeats > 2 && numPlayers != numSeats) {
updateNumSeats();
}
}//GEN-LAST:event_spnNumPlayersStateChanged }//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 private void spnNumWinsnumPlayersChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumWinsnumPlayersChanged
int numSeats = getCurrentNumSeats(); applyNewPlayersCount();
if (numSeats > 2) {
spnNumWins.setValue(1);
}
}//GEN-LAST:event_spnNumWinsnumPlayersChanged }//GEN-LAST:event_spnNumWinsnumPlayersChanged
private JFileChooser fcSelectDeck = null; 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 private void cbDraftCubeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDraftCubeActionPerformed
cubeFromDeckFilename = ""; cubeFromDeckFilename = "";
if (cbDraftCube.getSelectedItem().toString().equals("Cube From Deck")) { if (cbDraftCube.getSelectedItem().toString().startsWith(CUBE_FROM_DECK_NAME)) {
cubeFromDeckFilename = playerLoadDeck(); cubeFromDeckFilename = playerLoadDeck();
} }
}//GEN-LAST:event_cbDraftCubeActionPerformed }//GEN-LAST:event_cbDraftCubeActionPerformed
@ -886,27 +900,52 @@ public class NewTournamentDialog extends MageDialog {
customOptions.showDialog(); customOptions.showDialog();
}//GEN-LAST:event_btnCustomOptionsActionPerformed }//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() { private void setGameOptions() {
createPlayers(getCurrentNumPlayers() - 1); 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(); TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
activatePanelElements(tournamentType); activatePanelElements(tournamentType);
if (numPlayers < tournamentType.getMinPlayers() || numPlayers > tournamentType.getMaxPlayers()) { numPlayers = getCompatiblePlayersCount(numPlayers);
numPlayers = tournamentType.getMinPlayers();
}
this.spnNumPlayers.setModel(new SpinnerNumberModel(numPlayers, tournamentType.getMinPlayers(), tournamentType.getMaxPlayers(), 1)); this.spnNumPlayers.setModel(new SpinnerNumberModel(numPlayers, tournamentType.getMinPlayers(), tournamentType.getMaxPlayers(), 1));
this.spnNumPlayers.setEnabled(tournamentType.getMinPlayers() != tournamentType.getMaxPlayers()); 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 // TODO: refactor to use isLoading and restrictions from a code instead restrictions from a component
this.chkSingleMultiplayerGame.setSelected(isSingleMultiplayerGame);
this.spnNumPlayers.setValue(numPlayers); this.spnNumPlayers.setValue(numPlayers);
spnNumPlayersStateChanged(null); 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) { if (loadPlayerSettings) {
// load player data // load player data
@ -1265,8 +1304,7 @@ public class NewTournamentDialog extends MageDialog {
private TournamentOptions getTournamentOptions() { private TournamentOptions getTournamentOptions() {
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
int numSeats = (Integer) this.spnNumSeats.getValue(); TournamentOptions tOptions = new TournamentOptions(this.txtName.getText(), "", chkSingleMultiplayerGame.isSelected());
TournamentOptions tOptions = new TournamentOptions(this.txtName.getText(), "", numSeats);
tOptions.setTournamentType(tournamentType.getName()); tOptions.setTournamentType(tournamentType.getName());
tOptions.setPassword(txtPassword.getText()); tOptions.setPassword(txtPassword.getText());
tOptions.getPlayerTypes().add(PlayerType.HUMAN); tOptions.getPlayerTypes().add(PlayerType.HUMAN);
@ -1429,7 +1467,6 @@ public class NewTournamentDialog extends MageDialog {
break; 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.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"))); 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); activatePanelElements(tournamentType);
int defaultNumberPlayers = 2; int defaultNumberPlayers = 2;
int defaultNumberSeats = 2;
if (tournamentType.isLimited()) { if (tournamentType.isLimited()) {
if (tournamentType.isDraft()) { if (tournamentType.isDraft()) {
defaultNumberPlayers = 4; 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 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))); boolean isSingleMultiplayerGame = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_SINGLE_MULTIPLAYER_GAME + versionStr, "No").equals("Yes");
prepareTourneyView(true, versionStr, numPlayers, numSeats); loadTourneyView(true, versionStr, numPlayers, isSingleMultiplayerGame);
this.customOptions.onLoadSettings(version); 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_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 // save player data
// player type // player type
@ -1558,6 +1594,7 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JComboBox cbTournamentType; private javax.swing.JComboBox cbTournamentType;
private javax.swing.JCheckBox chkRated; private javax.swing.JCheckBox chkRated;
private javax.swing.JCheckBox chkRollbackTurnsAllowed; private javax.swing.JCheckBox chkRollbackTurnsAllowed;
private javax.swing.JCheckBox chkSingleMultiplayerGame;
private javax.swing.JLabel jLabel6; private javax.swing.JLabel jLabel6;
private javax.swing.JLabel lbBufferTime; private javax.swing.JLabel lbBufferTime;
private javax.swing.JLabel lbDeckType; private javax.swing.JLabel lbDeckType;
@ -1569,7 +1606,6 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JLabel lblMinimumRating; private javax.swing.JLabel lblMinimumRating;
private javax.swing.JLabel lblName; private javax.swing.JLabel lblName;
private javax.swing.JLabel lblNbrPlayers; private javax.swing.JLabel lblNbrPlayers;
private javax.swing.JLabel lblNbrSeats;
private javax.swing.JLabel lblNumRounds; private javax.swing.JLabel lblNumRounds;
private javax.swing.JLabel lblNumWins; private javax.swing.JLabel lblNumWins;
private javax.swing.JLabel lblPacks; private javax.swing.JLabel lblPacks;
@ -1598,7 +1634,6 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JSpinner spnMinimumRating; private javax.swing.JSpinner spnMinimumRating;
private javax.swing.JSpinner spnNumPlayers; private javax.swing.JSpinner spnNumPlayers;
private javax.swing.JSpinner spnNumRounds; private javax.swing.JSpinner spnNumRounds;
private javax.swing.JSpinner spnNumSeats;
private javax.swing.JSpinner spnNumWins; private javax.swing.JSpinner spnNumWins;
private javax.swing.JSpinner spnQuitRatio; private javax.swing.JSpinner spnQuitRatio;
private javax.swing.JTextField txtName; private javax.swing.JTextField txtName;

View file

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

View file

@ -2010,7 +2010,6 @@
<Properties> <Properties>
<Property name="horizontalAlignment" type="int" value="2"/> <Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="GUI color style:"/> <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="horizontalTextPosition" type="int" value="10"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[110, 16]"/> <Dimension value="[110, 16]"/>
@ -2184,7 +2183,7 @@
<Component id="panelCardStyles" min="-2" max="-2" attributes="0"/> <Component id="panelCardStyles" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="panelCardImages" min="-2" 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>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -2225,7 +2224,7 @@
<Component id="cbPreferredImageLanguage" min="-2" pref="153" max="-2" attributes="0"/> <Component id="cbPreferredImageLanguage" min="-2" pref="153" max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
<EmptySpace min="0" pref="480" max="32767" attributes="0"/> <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
@ -2284,7 +2283,6 @@
</Component> </Component>
<Component class="javax.swing.JComboBox" name="cbPreferredImageLanguage"> <Component class="javax.swing.JComboBox" name="cbPreferredImageLanguage">
<Properties> <Properties>
<Property name="maximumRowCount" type="int" value="20"/>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor"> <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="4"> <StringArray count="4">
<StringItem index="0" value="Item 1"/> <StringItem index="0" value="Item 1"/>
@ -2319,15 +2317,48 @@
</Property> </Property>
</Properties> </Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"> <Layout>
<Property name="axis" type="int" value="1"/> <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> </Layout>
<SubComponents> <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"> <Component class="javax.swing.JCheckBox" name="cbCardRenderIconsForAbilities">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="Enable card icons for abilities (example: flying, deathtouch)"/> <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"/> <Property name="text" type="java.lang.String" value="Show ability text as overlay in big card view"/>
</Properties> </Properties>
</Component> </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> </SubComponents>
</Container> </Container>
</SubComponents> </SubComponents>
@ -2616,29 +2669,25 @@
<Properties> <Properties>
<Property name="selected" type="boolean" value="true"/> <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="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=""/> <Property name="actionCommand" type="java.lang.String" value=""/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JCheckBox" name="cbStopBlockWithAny"> <Component class="javax.swing.JCheckBox" name="cbStopBlockWithAny">
<Properties> <Properties>
<Property name="selected" type="boolean" value="true"/> <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="text" type="java.lang.String" value="STOP skips when attacked and on declare blockers if ANY blockers are available"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="actionCommand" type="java.lang.String" value=""/> <Property name="actionCommand" type="java.lang.String" value=""/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JCheckBox" name="cbStopBlockWithZero"> <Component class="javax.swing.JCheckBox" name="cbStopBlockWithZero">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="STOP skips on declare blockers if ZERO blockers are available"/> <Property name="text" type="java.lang.String" value="STOP skips when attacked if ZERO blockers are available"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="actionCommand" type="java.lang.String" value=""/> <Property name="actionCommand" type="java.lang.String" value=""/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JCheckBox" name="cbStopOnNewStackObjects"> <Component class="javax.swing.JCheckBox" name="cbStopOnNewStackObjects">
<Properties> <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="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="toolTipText" type="java.lang.String" value=""/>
<Property name="actionCommand" 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"> <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 25]"/> <Dimension value="[300, 25]"/>
@ -2648,14 +2697,12 @@
<Component class="javax.swing.JCheckBox" name="cbStopOnAllMain"> <Component class="javax.swing.JCheckBox" name="cbStopOnAllMain">
<Properties> <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="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=""/> <Property name="actionCommand" type="java.lang.String" value=""/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JCheckBox" name="cbStopOnAllEnd"> <Component class="javax.swing.JCheckBox" name="cbStopOnAllEnd">
<Properties> <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="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="actionCommand" type="java.lang.String" value=""/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 25]"/> <Dimension value="[300, 25]"/>
@ -3128,7 +3175,6 @@
<Component class="javax.swing.JLabel" name="jLabel16"> <Component class="javax.swing.JLabel" name="jLabel16">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="Playing from folder:"/> <Property name="text" type="java.lang.String" value="Playing from folder:"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JTextField" name="txtBattlefieldIBGMPath"> <Component class="javax.swing.JTextField" name="txtBattlefieldIBGMPath">
@ -3178,7 +3224,7 @@
<Properties> <Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> <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"> <Border PropertyName="innerBorder" info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/> <EtchetBorder/>
</Border> </Border>

View file

@ -4,10 +4,7 @@ import mage.client.MageFrame;
import mage.client.SessionHandler; import mage.client.SessionHandler;
import mage.client.components.KeyBindButton; import mage.client.components.KeyBindButton;
import mage.client.themes.ThemeType; import mage.client.themes.ThemeType;
import mage.client.util.CardLanguage; import mage.client.util.*;
import mage.client.util.ClientDefaultSettings;
import mage.client.util.GUISizeHelper;
import mage.client.util.ImageHelper;
import mage.client.util.audio.MusicPlayer; import mage.client.util.audio.MusicPlayer;
import mage.client.util.gui.BufferedImageBuilder; import mage.client.util.gui.BufferedImageBuilder;
import mage.client.util.gui.GuiDisplayUtil; 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.concurrent.locks.ReentrantReadWriteLock;
import java.util.prefs.BackingStoreException; import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD; import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD;
import static mage.constants.Constants.*; import static mage.constants.Constants.*;
@ -55,6 +51,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
private static PreferencesDialog instance; // shared dialog instance 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 // 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_TOOLTIPS_DELAY = "showTooltipsDelay";
public static final String KEY_SHOW_CARD_NAMES = "showCardNames"; 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_SAVE_TO_ZIP = "cardImagesSaveToZip";
public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage"; 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_ABILITIES = "cardRenderingIconsForAbilities";
public static final String KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE = "cardRenderingIconsForPlayable"; public static final String KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE = "cardRenderingIconsForPlayable";
public static final String KEY_CARD_RENDERING_REMINDER_TEXT = "cardRenderingReminderText"; 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_DRAFT = "newTournamentPacksDraft";
public static final String KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT = "newTournamentPacksRandomDraft"; 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_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_TYPES = "newTournamentPlayerTypes";
public static final String KEY_NEW_TOURNAMENT_PLAYER_SKILLS = "newTournamentPlayerSkills"; public static final String KEY_NEW_TOURNAMENT_PLAYER_SKILLS = "newTournamentPlayerSkills";
public static final String KEY_NEW_TOURNAMENT_DRAFT_TIMING = "newTournamentDraftTiming"; 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"; public static final String KEY_AUTO_TARGET_LEVEL = "autoTargetLevel";
// pref setting for deck generator // 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_DECK_SIZE = "newDeckGeneratorDeckSize";
public static final String KEY_NEW_DECK_GENERATOR_SET = "newDeckGeneratorSet"; 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_SINGLETON = "newDeckGeneratorSingleton";
public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts"; 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_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands";
public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless"; 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_ADVANCED = "newDeckGeneratorAdvanced";
public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage"; public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage";
public static final String KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE = "newDeckGeneratorNonCreaturePercentage"; 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())); 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) { 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<>(); cbPreferredImageLanguage = new javax.swing.JComboBox<>();
labelPreferredImageLanguage = new javax.swing.JLabel(); labelPreferredImageLanguage = new javax.swing.JLabel();
panelCardStyles = new javax.swing.JPanel(); panelCardStyles = new javax.swing.JPanel();
cbCardRenderImageFallback = new javax.swing.JCheckBox();
cbCardRenderIconsForAbilities = new javax.swing.JCheckBox(); cbCardRenderIconsForAbilities = new javax.swing.JCheckBox();
cbCardRenderIconsForPlayable = new javax.swing.JCheckBox(); cbCardRenderIconsForPlayable = new javax.swing.JCheckBox();
jSeparator1 = new javax.swing.JSeparator(); jSeparator1 = new javax.swing.JSeparator();
cbCardRenderShowReminderText = new javax.swing.JCheckBox(); cbCardRenderShowReminderText = new javax.swing.JCheckBox();
cbCardRenderHideSetSymbol = new javax.swing.JCheckBox(); cbCardRenderHideSetSymbol = new javax.swing.JCheckBox();
cbCardRenderShowAbilityTextOverlay = new javax.swing.JCheckBox(); cbCardRenderShowAbilityTextOverlay = new javax.swing.JCheckBox();
labelRenderMode = new javax.swing.JLabel();
cbCardRenderImageFallback = new javax.swing.JComboBox<>();
tabPhases = new javax.swing.JPanel(); tabPhases = new javax.swing.JPanel();
jLabelHeadLine = new javax.swing.JLabel(); jLabelHeadLine = new javax.swing.JLabel();
jLabelYourTurn = 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.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
lbSelectLabel.setText("GUI color style:"); lbSelectLabel.setText("GUI color style:");
lbSelectLabel.setToolTipText("");
lbSelectLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); lbSelectLabel.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
lbSelectLabel.setPreferredSize(new java.awt.Dimension(110, 16)); lbSelectLabel.setPreferredSize(new java.awt.Dimension(110, 16));
lbSelectLabel.setVerticalTextPosition(javax.swing.SwingConstants.TOP); 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" })); cbPreferredImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
labelPreferredImageLanguage.setText("Default images language:"); labelPreferredImageLanguage.setText("Default images language:");
@ -2302,7 +2302,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(labelPreferredImageLanguage) .add(labelPreferredImageLanguage)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) .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()) .addContainerGap())
); );
panelCardImagesLayout.setVerticalGroup( 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.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)"); 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)"); 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"); 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)"); 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"); 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); org.jdesktop.layout.GroupLayout tabGuiImagesLayout = new org.jdesktop.layout.GroupLayout(tabGuiImages);
tabGuiImages.setLayout(tabGuiImagesLayout); 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) .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) .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) .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); tabsPanel.addTab("GUI Images", tabGuiImages);
@ -2391,34 +2424,28 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbStopAttack.setSelected(true); cbStopAttack.setSelected(true);
cbStopAttack.setText("STOP skips on declare attackers if attackers are available"); cbStopAttack.setText("STOP skips on declare attackers if attackers are available");
cbStopAttack.setToolTipText("");
cbStopAttack.setActionCommand(""); cbStopAttack.setActionCommand("");
phases_stopSettings.add(cbStopAttack); phases_stopSettings.add(cbStopAttack);
cbStopBlockWithAny.setSelected(true); cbStopBlockWithAny.setSelected(true);
cbStopBlockWithAny.setText("STOP skips on declare blockers if ANY blockers are available"); cbStopBlockWithAny.setText("STOP skips when attacked and on declare blockers if ANY blockers are available");
cbStopBlockWithAny.setToolTipText("");
cbStopBlockWithAny.setActionCommand(""); cbStopBlockWithAny.setActionCommand("");
phases_stopSettings.add(cbStopBlockWithAny); phases_stopSettings.add(cbStopBlockWithAny);
cbStopBlockWithZero.setText("STOP skips on declare blockers if ZERO blockers are available"); cbStopBlockWithZero.setText("STOP skips when attacked if ZERO blockers are available");
cbStopBlockWithZero.setToolTipText("");
cbStopBlockWithZero.setActionCommand(""); cbStopBlockWithZero.setActionCommand("");
phases_stopSettings.add(cbStopBlockWithZero); phases_stopSettings.add(cbStopBlockWithZero);
cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop until empty (off)"); cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop when stack empty (off)");
cbStopOnNewStackObjects.setToolTipText("");
cbStopOnNewStackObjects.setActionCommand(""); cbStopOnNewStackObjects.setActionCommand("");
cbStopOnNewStackObjects.setPreferredSize(new java.awt.Dimension(300, 25)); cbStopOnNewStackObjects.setPreferredSize(new java.awt.Dimension(300, 25));
phases_stopSettings.add(cbStopOnNewStackObjects); 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.setText("Skip to MAIN step (F7): stop on any main steps (on) or stop on your main step (off)");
cbStopOnAllMain.setToolTipText("");
cbStopOnAllMain.setActionCommand(""); cbStopOnAllMain.setActionCommand("");
phases_stopSettings.add(cbStopOnAllMain); 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.setText("Skip to END step (F5): stop on any end steps (on) or stop on opponents end step (off)");
cbStopOnAllEnd.setToolTipText("");
cbStopOnAllEnd.setActionCommand(""); cbStopOnAllEnd.setActionCommand("");
cbStopOnAllEnd.setPreferredSize(new java.awt.Dimension(300, 25)); cbStopOnAllEnd.setPreferredSize(new java.awt.Dimension(300, 25));
phases_stopSettings.add(cbStopOnAllEnd); phases_stopSettings.add(cbStopOnAllEnd);
@ -2743,7 +2770,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
}); });
jLabel16.setText("Playing from folder:"); jLabel16.setText("Playing from folder:");
jLabel16.setToolTipText("");
btnBattlefieldBGMBrowse.setText("Browse..."); btnBattlefieldBGMBrowse.setText("Browse...");
btnBattlefieldBGMBrowse.addActionListener(new java.awt.event.ActionListener() { btnBattlefieldBGMBrowse.addActionListener(new java.awt.event.ActionListener() {
@ -2803,7 +2829,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
tabsPanel.addTab("Sounds", tabSounds); 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() { cbProxyType.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) { 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"); save(prefs, dialog.cbUseRandomBattleImage, KEY_BATTLEFIELD_IMAGE_RANDOM, "true", "false");
// rendering // 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.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "false");
save(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "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"); save(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true", "false");
@ -3325,6 +3351,11 @@ public class PreferencesDialog extends javax.swing.JDialog {
return; return;
} }
if (!NETWORK_ENABLE_PROXY_SUPPORT) {
connection.setProxyType(ProxyType.NONE);
return;
}
connection.setProxyType(configProxyType); connection.setProxyType(configProxyType);
if (configProxyType != ProxyType.NONE) { if (configProxyType != ProxyType.NONE) {
String host = getCachedValue(KEY_PROXY_ADDRESS, ""); 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())); dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode()));
// rendering settings // 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.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "true");
load(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "true"); load(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "true");
load(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "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() { public static int getRenderMode() {
if (getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_IMAGE_MODE, "false").equals("false")) { return CardRenderMode.fromString(getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_IMAGE_MODE, CardRenderMode.MTGO.toString())).getId();
return 0; // mtgo
} else {
return 1; // image
}
} }
public static boolean getRenderIconsForAbilities() { public static boolean getRenderIconsForAbilities() {
@ -4043,7 +4070,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JCheckBox cbCardRenderHideSetSymbol; private javax.swing.JCheckBox cbCardRenderHideSetSymbol;
private javax.swing.JCheckBox cbCardRenderIconsForAbilities; private javax.swing.JCheckBox cbCardRenderIconsForAbilities;
private javax.swing.JCheckBox cbCardRenderIconsForPlayable; 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 cbCardRenderShowAbilityTextOverlay;
private javax.swing.JCheckBox cbCardRenderShowReminderText; private javax.swing.JCheckBox cbCardRenderShowReminderText;
private javax.swing.JCheckBox cbConfirmEmptyManaPool; 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 labelNextTurn;
private javax.swing.JLabel labelPreferredImageLanguage; private javax.swing.JLabel labelPreferredImageLanguage;
private javax.swing.JLabel labelPriorEnd; private javax.swing.JLabel labelPriorEnd;
private javax.swing.JLabel labelRenderMode;
private javax.swing.JLabel labelSizeGroup1; private javax.swing.JLabel labelSizeGroup1;
private javax.swing.JLabel labelSizeGroup2; private javax.swing.JLabel labelSizeGroup2;
private javax.swing.JLabel labelSkipStep; private javax.swing.JLabel labelSkipStep;

View file

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

View file

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

View file

@ -94,7 +94,6 @@
<Property name="dividerLocation" type="int" value="300"/> <Property name="dividerLocation" type="int" value="300"/>
<Property name="dividerSize" type="int" value="3"/> <Property name="dividerSize" type="int" value="3"/>
<Property name="resizeWeight" type="double" value="1.0"/> <Property name="resizeWeight" type="double" value="1.0"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> <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.setDividerLocation(300);
jSplitPane1.setDividerSize(3); jSplitPane1.setDividerSize(3);
jSplitPane1.setResizeWeight(1.0); jSplitPane1.setResizeWeight(1.0);
jSplitPane1.setToolTipText("");
jTableSeats.setModel(tableWaitModel); jTableSeats.setModel(tableWaitModel);
jTableSeats.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); jTableSeats.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);

View file

@ -116,11 +116,9 @@
<Component class="javax.swing.JComboBox" name="comboRenderMode"> <Component class="javax.swing.JComboBox" name="comboRenderMode">
<Properties> <Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor"> <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="2"> <StringArray count="0"/>
<StringItem index="0" value="MTGO"/>
<StringItem index="1" value="Image"/>
</StringArray>
</Property> </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> </Properties>
<Events> <Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboRenderModeItemStateChanged"/> <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboRenderModeItemStateChanged"/>
@ -300,7 +298,6 @@
<StringItem index="0" value="loading..."/> <StringItem index="0" value="loading..."/>
</StringArray> </StringArray>
</Property> </Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Events> <Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboCardColorItemStateChanged"/> <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboCardColorItemStateChanged"/>
@ -382,7 +379,6 @@
<StringItem index="2" value="Dead"/> <StringItem index="2" value="Dead"/>
</StringArray> </StringArray>
</Property> </Property>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="alignmentX" type="float" value="0.0"/> <Property name="alignmentX" type="float" value="0.0"/>
</Properties> </Properties>
<Events> <Events>

View file

@ -16,7 +16,7 @@ import mage.client.cards.BigCard;
import mage.client.game.PlayAreaPanel; import mage.client.game.PlayAreaPanel;
import mage.client.game.PlayerPanelExt; import mage.client.game.PlayerPanelExt;
import mage.client.themes.ThemeType; import mage.client.themes.ThemeType;
import mage.client.util.ClientEventType; import mage.client.util.*;
import mage.client.util.Event; import mage.client.util.Event;
import mage.client.util.GUISizeHelper; import mage.client.util.GUISizeHelper;
import mage.client.util.Listener; import mage.client.util.Listener;
@ -82,6 +82,7 @@ public class TestCardRenderDialog extends MageDialog {
getRootPane().setDefaultButton(buttonCancel); getRootPane().setDefaultButton(buttonCancel);
// init render mode // init render mode
this.comboRenderMode.setModel(new DefaultComboBoxModel<>(CardRenderMode.toList()));
this.comboRenderMode.setSelectedIndex(PreferencesDialog.getRenderMode()); this.comboRenderMode.setSelectedIndex(PreferencesDialog.getRenderMode());
// init themes list // init themes list
@ -326,6 +327,13 @@ public class TestCardRenderDialog extends MageDialog {
possibleTargets.add(playerYou.getId()); 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 // player's panel
if (this.player == null) { if (this.player == null) {
// create new panel // create new panel
@ -345,7 +353,7 @@ public class TestCardRenderDialog extends MageDialog {
.findFirst() .findFirst()
.orElse(null); .orElse(null);
this.player.init(this.game.getId(), playerYou.getId(), isMe, this.bigCard, 0); 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); this.player.sizePlayerPanel(smallMode);
// update CARDS // 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(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(), "DIS", "153")); // Odds // Ends (split card)
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "38")); // Animating Faerie (adventure 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", 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", true, false, true)); // morphed
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, true, false)); // manifested 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:"); 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() { comboRenderMode.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) { public void itemStateChanged(java.awt.event.ItemEvent evt) {
comboRenderModeItemStateChanged(evt); comboRenderModeItemStateChanged(evt);
@ -736,7 +745,6 @@ public class TestCardRenderDialog extends MageDialog {
labelCardColor.setText("Card color:"); labelCardColor.setText("Card color:");
comboCardColor.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "loading..." })); comboCardColor.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "loading..." }));
comboCardColor.setToolTipText("");
comboCardColor.addItemListener(new java.awt.event.ItemListener() { comboCardColor.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) { public void itemStateChanged(java.awt.event.ItemEvent evt) {
comboCardColorItemStateChanged(evt); comboCardColorItemStateChanged(evt);
@ -774,7 +782,6 @@ public class TestCardRenderDialog extends MageDialog {
playerOptions.add(checkPlayerSmallMode); playerOptions.add(checkPlayerSmallMode);
comboPlayerStatus.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Active", "Inactive", "Dead" })); comboPlayerStatus.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Active", "Inactive", "Dead" }));
comboPlayerStatus.setToolTipText("");
comboPlayerStatus.setAlignmentX(0.0F); comboPlayerStatus.setAlignmentX(0.0F);
comboPlayerStatus.addItemListener(new java.awt.event.ItemListener() { comboPlayerStatus.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) { public void itemStateChanged(java.awt.event.ItemEvent evt) {

View file

@ -343,7 +343,6 @@
</Property> </Property>
<Property name="horizontalAlignment" type="int" value="4"/> <Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" value="player 2"/> <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="alignmentX" type="float" value="1.0"/>
<Property name="alignmentY" type="float" value="0.0"/> <Property name="alignmentY" type="float" value="0.0"/>
<Property name="focusable" type="boolean" value="false"/> <Property name="focusable" type="boolean" value="false"/>
@ -523,7 +522,6 @@
</Property> </Property>
<Property name="horizontalAlignment" type="int" value="2"/> <Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" value="player 12"/> <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="focusable" type="boolean" value="false"/>
<Property name="requestFocusEnabled" type="boolean" value="false"/> <Property name="requestFocusEnabled" type="boolean" value="false"/>
<Property name="verifyInputWhenFocusTarget" 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. * 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 * Timer starting at booster being displayed, to protect from early pick due to clicking
* a little too much on the last pick. * 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() { public void cleanUp() {
@ -186,15 +191,15 @@
} }
public void updateDraft(DraftView draftView) { 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? // Random draft - TODO: can we access the type of draft here?
this.editPack1.setText("Random Boosters"); this.editPack1.setText("Random Boosters");
this.editPack2.setText("Random Boosters"); this.editPack2.setText("Random Boosters");
this.editPack3.setText("Random Boosters"); this.editPack3.setText("Random Boosters");
} else { } else {
this.editPack1.setText(String.format("%s - %s", draftView.getSetCodes().get(0), draftView.getSets().get(0))); this.editPack1.setText(draftView.getBoosterInfo(0));
this.editPack2.setText(String.format("%s - %s", draftView.getSetCodes().get(1), draftView.getSets().get(1))); this.editPack2.setText(draftView.getBoosterInfo(1));
this.editPack3.setText(String.format("%s - %s", draftView.getSetCodes().get(2), draftView.getSets().get(2))); this.editPack3.setText(draftView.getBoosterInfo(2));
} }
// scroll too long text to the start // scroll too long text to the start
@ -396,7 +401,8 @@
} }
if (!draftBooster.isEmptyGrid()) { 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()) { if (pickNo != protectionPickNo && !protectionTimer.isRunning()) {
// Restart the protection timer. // Restart the protection timer.
@ -773,7 +779,6 @@
labelPlayer02.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N labelPlayer02.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N
labelPlayer02.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); labelPlayer02.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
labelPlayer02.setText("player 2"); labelPlayer02.setText("player 2");
labelPlayer02.setToolTipText("");
labelPlayer02.setAlignmentX(1.0F); labelPlayer02.setAlignmentX(1.0F);
labelPlayer02.setAlignmentY(0.0F); labelPlayer02.setAlignmentY(0.0F);
labelPlayer02.setFocusable(false); labelPlayer02.setFocusable(false);
@ -887,7 +892,6 @@
labelPlayer12.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N labelPlayer12.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N
labelPlayer12.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); labelPlayer12.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
labelPlayer12.setText("player 12"); labelPlayer12.setText("player 12");
labelPlayer12.setToolTipText("");
labelPlayer12.setFocusable(false); labelPlayer12.setFocusable(false);
labelPlayer12.setRequestFocusEnabled(false); labelPlayer12.setRequestFocusEnabled(false);
labelPlayer12.setVerifyInputWhenFocusTarget(false); labelPlayer12.setVerifyInputWhenFocusTarget(false);

View file

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

View file

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

View file

@ -247,7 +247,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
.orElse(0); .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; this.player = player;
int pastLife = player.getLife(); int pastLife = player.getLife();
if (playerLives != null) { if (playerLives != null) {
@ -427,6 +427,12 @@ public class PlayerPanelExt extends javax.swing.JPanel {
this.btnPlayer.setBorder(YELLOW_BORDER); 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()); update(player.getManaPool());
} }
@ -687,11 +693,11 @@ public class PlayerPanelExt extends javax.swing.JPanel {
zonesPanel.setLayout(null); zonesPanel.setLayout(null);
zonesPanel.setOpaque(false); zonesPanel.setOpaque(false);
// tools button like hints // hints
toolHintsHelper = new JButton(); toolHintsHelper = new JButton();
toolHintsHelper.setFont(this.getFont()); toolHintsHelper.setFont(this.getFont());
toolHintsHelper.setText("hints"); toolHintsHelper.setText("Hints");
toolHintsHelper.setToolTipText("Open new card hints helper window"); toolHintsHelper.setToolTipText("Open card hints helper window");
toolHintsHelper.addActionListener(this::btnToolHintsHelperActionPerformed); toolHintsHelper.addActionListener(this::btnToolHintsHelperActionPerformed);
toolHintsHelper.setBounds(sizeMod(3), sizeMod(2 + 21 + 2), sizeMod(73), sizeMod(21)); toolHintsHelper.setBounds(sizeMod(3), sizeMod(2 + 21 + 2), sizeMod(73), sizeMod(21));
zonesPanel.add(toolHintsHelper); zonesPanel.add(toolHintsHelper);
@ -701,7 +707,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
image = ImageHelper.getImageFromResources("/info/command_zone.png"); image = ImageHelper.getImageFromResources("/info/command_zone.png");
resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r);
commandZone = new HoverButton(null, resized, resized, resized, r, this.guiScaleMod); 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.setOpaque(false);
commandZone.setObserver(() -> btnCommandZoneActionPerformed(null)); commandZone.setObserver(() -> btnCommandZoneActionPerformed(null));
commandZone.setBounds(sizeMod(3), 0, sizeMod(21), sizeMod(21)); commandZone.setBounds(sizeMod(3), 0, sizeMod(21), sizeMod(21));

View file

@ -98,8 +98,8 @@ public class MageActionCallback implements ActionCallback {
private MageCard prevCardPanel; private MageCard prevCardPanel;
private boolean startedDragging; private boolean startedDragging;
private boolean isDragging; // TODO: remove drag hand code to the hand panels private boolean isDragging; // TODO: remove drag hand code to the hand panels
private Point initialCardPos; private Point initialCardPos = null;
private Point initialMousePos; private Point initialMousePos = null;
private final Set<MageCard> draggingCards = new HashSet<>(); private final Set<MageCard> draggingCards = new HashSet<>();
public MageActionCallback() { public MageActionCallback() {
@ -351,6 +351,11 @@ public class MageActionCallback implements ActionCallback {
return; 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()); Point mouse = new Point(e.getX(), e.getY());
SwingUtilities.convertPointToScreen(mouse, data.getComponent()); SwingUtilities.convertPointToScreen(mouse, data.getComponent());
if (!isDragging if (!isDragging

View file

@ -10,6 +10,7 @@ import mage.client.draft.DraftPanel;
import mage.client.game.GamePanel; import mage.client.game.GamePanel;
import mage.client.plugins.impl.Plugins; import mage.client.plugins.impl.Plugins;
import mage.client.util.DeckUtil; import mage.client.util.DeckUtil;
import mage.client.util.GUISizeHelper;
import mage.client.util.IgnoreList; import mage.client.util.IgnoreList;
import mage.client.util.audio.AudioManager; import mage.client.util.audio.AudioManager;
import mage.client.util.object.SaveObjectUtil; import mage.client.util.object.SaveObjectUtil;
@ -22,9 +23,12 @@ import mage.util.DebugUtil;
import mage.view.*; import mage.view.*;
import mage.view.ChatMessage.MessageType; import mage.view.ChatMessage.MessageType;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.mage.card.arcane.ManaSymbols;
import javax.swing.*; import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.List;
import java.util.*; import java.util.*;
/** /**
@ -72,7 +76,7 @@ public class CallbackClientImpl implements CallbackClient {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
try { try {
if (DebugUtil.NETWORK_SHOW_CLIENT_CALLBACK_MESSAGES_LOG) { 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) // 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: { case SERVER_MESSAGE: {
if (callback.getData() != null) { if (callback.getData() != null) {
ChatMessage message = (ChatMessage) callback.getData(); ChatMessage message = (ChatMessage) callback.getData();
if (message.getColor() == ChatMessage.MessageColor.RED) { showMessageDialog(null, message.getMessage(), "Server message");
JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.WARNING_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.INFORMATION_MESSAGE);
}
} }
break; break;
} }
@ -401,7 +401,7 @@ public class CallbackClientImpl implements CallbackClient {
case SHOW_USERMESSAGE: { case SHOW_USERMESSAGE: {
List<String> messageData = (List<String>) callback.getData(); List<String> messageData = (List<String>) callback.getData();
if (messageData.size() == 2) { 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; break;
} }
@ -420,8 +420,7 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData(); GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId()); GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) { if (panel != null) {
JOptionPane.showMessageDialog(panel, message.getMessage(), "Game message", showMessageDialog(panel, message.getMessage(), "Game message");
JOptionPane.INFORMATION_MESSAGE);
} }
break; 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) { private ActionData appendJsonEvent(String name, UUID gameId, Object value) {
Session session = SessionHandler.getSession(); Session session = SessionHandler.getSession();
if (session.isJsonLogActive()) { if (session.isJsonLogActive()) {

View file

@ -48,6 +48,12 @@ public class XmageURLConnection {
private static final AtomicLong debugLastRequestTimeMs = new AtomicLong(0); private static final AtomicLong debugLastRequestTimeMs = new AtomicLong(0);
private static final ReentrantLock debugLogsWriterlock = new ReentrantLock(); 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; final String url;
Proxy proxy = null; Proxy proxy = null;
HttpURLConnection connection = null; HttpURLConnection connection = null;
@ -123,6 +129,10 @@ public class XmageURLConnection {
} }
this.proxy = Proxy.NO_PROXY; this.proxy = Proxy.NO_PROXY;
if (!PreferencesDialog.NETWORK_ENABLE_PROXY_SUPPORT) {
return;
}
if (type != Proxy.Type.DIRECT) { if (type != Proxy.Type.DIRECT) {
try { try {
String address = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PROXY_ADDRESS, ""); 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); 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 { public boolean joinTable(UUID roomId, UUID tableId) throws IOException, ClassNotFoundException {
if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) { 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), ""); 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 private void cbPlayerTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbPlayerTypeActionPerformed
if (getPlayerType() != PlayerType.HUMAN) { if (getPlayerType() != PlayerType.HUMAN) {
this.newPlayerPanel.setVisible(true); 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 { } else {
this.newPlayerPanel.setVisible(false); this.newPlayerPanel.setVisible(false);
} }

View file

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

View file

@ -1163,7 +1163,7 @@ public class TablesPanel extends javax.swing.JPanel {
filterBar1.add(btnTypeMatch); filterBar1.add(btnTypeMatch);
btnTypeTourneyConstructed.setSelected(true); btnTypeTourneyConstructed.setSelected(true);
btnTypeTourneyConstructed.setText("Constructed tourn."); btnTypeTourneyConstructed.setText("Constructed tourney");
btnTypeTourneyConstructed.setToolTipText("Shows all constructed tournament tables."); btnTypeTourneyConstructed.setToolTipText("Shows all constructed tournament tables.");
btnTypeTourneyConstructed.setActionCommand("typeTourneyConstructed"); btnTypeTourneyConstructed.setActionCommand("typeTourneyConstructed");
btnTypeTourneyConstructed.setFocusPainted(false); btnTypeTourneyConstructed.setFocusPainted(false);
@ -1178,7 +1178,7 @@ public class TablesPanel extends javax.swing.JPanel {
filterBar1.add(btnTypeTourneyConstructed); filterBar1.add(btnTypeTourneyConstructed);
btnTypeTourneyLimited.setSelected(true); btnTypeTourneyLimited.setSelected(true);
btnTypeTourneyLimited.setText("Limited tourn."); btnTypeTourneyLimited.setText("Limited tourney");
btnTypeTourneyLimited.setToolTipText("Shows all limited tournament tables."); btnTypeTourneyLimited.setToolTipText("Shows all limited tournament tables.");
btnTypeTourneyLimited.setActionCommand("typeTourneyLimited"); btnTypeTourneyLimited.setActionCommand("typeTourneyLimited");
btnTypeTourneyLimited.setFocusPainted(false); btnTypeTourneyLimited.setFocusPainted(false);
@ -1694,13 +1694,13 @@ public class TablesPanel extends javax.swing.JPanel {
DeckCardLists testDeck = DeckImporter.importDeckFromFile(testDeckFile, false); DeckCardLists testDeck = DeckImporter.importDeckFromFile(testDeckFile, false);
PlayerType aiType = useMonteCarloAI ? PlayerType.COMPUTER_MONTE_CARLO : PlayerType.COMPUTER_MAD; PlayerType aiType = useMonteCarloAI ? PlayerType.COMPUTER_MONTE_CARLO : PlayerType.COMPUTER_MAD;
int numSeats = gameName.contains("2") || gameName.contains("Monte Carlo") ? 2 : 4; int numPlayers = gameName.contains("2") || gameName.contains("Monte Carlo") ? 2 : 4;
boolean multiPlayer = numSeats > 2; 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(PlayerType.HUMAN);
options.getPlayerTypes().add(aiType); options.getPlayerTypes().add(aiType);
for (int i=2 ; i < numSeats ; i++) { for (int i=2 ; i < numPlayers ; i++) {
options.getPlayerTypes().add(aiType); options.getPlayerTypes().add(aiType);
} }
options.setDeckType("Variant Magic - Freeform Commander"); 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(), "Human", PlayerType.HUMAN, 1, testDeck, "");
SessionHandler.joinTable(roomId, table.getTableId(), "Computer", aiType, 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.joinTable(roomId, table.getTableId(), "Computer" + i, aiType, 1, testDeck, "");
} }
SessionHandler.startMatch(roomId, table.getTableId()); 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 private void cbPlayerTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbPlayerTypeActionPerformed
if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) { if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) {
this.pnlPlayerName.setVisible(true); 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.setEditable(false);
this.txtPlayerName.setEnabled(false); this.txtPlayerName.setEnabled(false);
} else { } else {

View file

@ -273,7 +273,6 @@
<Container class="javax.swing.JSplitPane" name="jSplitPane2"> <Container class="javax.swing.JSplitPane" name="jSplitPane2">
<Properties> <Properties>
<Property name="resizeWeight" type="double" value="1.0"/> <Property name="resizeWeight" type="double" value="1.0"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties> </Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> <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) { if (tournament.getStepStartTime() != null) {
usedTime = Format.getDuration((tournament.getServerTime().getTime() - tournament.getStepStartTime().getTime()) / 1000); usedTime = Format.getDuration((tournament.getServerTime().getTime() - tournament.getStepStartTime().getTime()) / 1000);
} }
txtTournamentState.setText(tournament.getTournamentState() + " (" + usedTime + ") " + tournament.getRunningInfo()); txtTournamentState.setText(tournament.getTournamentState() + " (" + usedTime + ")");
break; break;
default: default:
txtTournamentState.setText(tournament.getTournamentState()); txtTournamentState.setText(tournament.getTournamentState());
break; break;
} }
if (!tournament.getRunningInfo().isEmpty()) {
txtTournamentState.setText(txtTournamentState.getText() + ", " + tournament.getRunningInfo());
}
if (txtEndTime == null) { if (txtEndTime == null) {
return; return;
@ -444,7 +447,6 @@ public class TournamentPanel extends javax.swing.JPanel {
); );
jSplitPane2.setResizeWeight(1.0); jSplitPane2.setResizeWeight(1.0);
jSplitPane2.setToolTipText("");
jSplitPane1.setDividerLocation(230); jSplitPane1.setDividerLocation(230);
jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); 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 org.mage.card.arcane.CardRenderer;
import javax.swing.*; import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import java.awt.*; import java.awt.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Locale; import java.util.Locale;
@ -108,7 +109,7 @@ public final class GUISizeHelper {
// app - frame/window title // app - frame/window title
// nimbus's LaF limited to static title size, so font can't be too big (related code in SynthInternalFrameTitlePane, BasicInternalFrameTitlePane) // 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 // app - tables
tableFont = new java.awt.Font("Arial", 0, dialogFontSize); tableFont = new java.awt.Font("Arial", 0, dialogFontSize);
@ -150,7 +151,11 @@ public final class GUISizeHelper {
cardTooltipLargeImageHeight = 30 * tooltipFontSize; cardTooltipLargeImageHeight = 30 * tooltipFontSize;
cardTooltipLargeTextWidth = Math.max(150, 20 * tooltipFontSize - 50); cardTooltipLargeTextWidth = Math.max(150, 20 * tooltipFontSize - 50);
cardTooltipLargeTextHeight = Math.max(100, 12 * tooltipFontSize - 20); 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 // game - player panel
playerPanelGuiScale = (float) (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_PLAYER_PANEL_SIZE, 14) / 14.0); 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); return "EDH: " + getPowerLevel(sample);
} }
// TODO: it's outdated code, must migrate to shared code from AbstractCommander
private int getPowerLevel(CardView card) { private int getPowerLevel(CardView card) {
int thisMaxPower = 0; int thisMaxPower = 0;
@ -322,7 +323,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
if (cn.equals("acid rain") if (cn.equals("acid rain")
|| cn.equals("agent of treachery") || cn.equals("agent of treachery")
|| cn.equals("anafenza, the foremost") || cn.equals("anafenza, the foremost")
|| cn.equals("ancient tomb")
|| cn.equals("animar, soul of element") || cn.equals("animar, soul of element")
|| cn.equals("animate artifact") || cn.equals("animate artifact")
|| cn.equals("apocalypse") || cn.equals("apocalypse")
@ -352,7 +352,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("cabal coffers") || cn.equals("cabal coffers")
|| cn.equals("candelabra of tawnos") || cn.equals("candelabra of tawnos")
|| cn.equals("captain sisay") || cn.equals("captain sisay")
|| cn.equals("card view")
|| cn.equals("cataclysm") || cn.equals("cataclysm")
|| cn.equals("catastrophe") || cn.equals("catastrophe")
|| cn.equals("celestial dawn") || cn.equals("celestial dawn")
@ -368,7 +367,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("contamination") || cn.equals("contamination")
|| cn.equals("craterhoof behemoth") || cn.equals("craterhoof behemoth")
|| cn.equals("cryptic gateway") || cn.equals("cryptic gateway")
|| cn.equals("cyclonic rift")
|| cn.equals("deadeye navigator") || cn.equals("deadeye navigator")
|| cn.equals("death cloud") || cn.equals("death cloud")
|| cn.equals("decree of annihilation") || cn.equals("decree of annihilation")
@ -377,6 +375,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("demonic consultation") || cn.equals("demonic consultation")
|| cn.equals("derevi, empyrial tactician") || cn.equals("derevi, empyrial tactician")
|| cn.equals("devastation") || cn.equals("devastation")
|| cn.equals("dictate of erebos")
|| cn.equals("dig through time") || cn.equals("dig through time")
|| cn.equals("divine intervention") || cn.equals("divine intervention")
|| cn.equals("dockside extortionist") || cn.equals("dockside extortionist")
@ -385,34 +384,35 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("drannith magistrate") || cn.equals("drannith magistrate")
|| cn.equals("dross scorpion") || cn.equals("dross scorpion")
|| cn.equals("earthcraft") || cn.equals("earthcraft")
|| cn.equals("edgar markov")
|| cn.equals("edric, spymaster of trest") || cn.equals("edric, spymaster of trest")
|| cn.equals("elesh norn, grand cenobite") || cn.equals("elesh norn, grand cenobite")
|| cn.equals("elesh norn, mother of machines")
|| cn.equals("embargo") || cn.equals("embargo")
|| cn.equals("emrakul, the promised end") || cn.equals("emrakul, the promised end")
|| cn.equals("enter the infinite") || cn.equals("enter the infinite")
|| cn.equals("entomb") || cn.equals("entomb")
|| cn.equals("epicenter") || cn.equals("epicenter")
|| cn.equals("erratic portal") || cn.equals("erratic portal")
|| cn.equals("expropriate")
|| cn.equals("exquisite blood") || cn.equals("exquisite blood")
|| cn.equals("fall of the thran") || cn.equals("fall of the thran")
|| cn.equals("fierce guardianship") || cn.equals("farewell")
|| cn.equals("flashfires")
|| cn.equals("food chain") || cn.equals("food chain")
|| cn.equals("force of negation") || cn.equals("force of negation")
|| cn.equals("force of will")
|| cn.equals("future sight") || cn.equals("future sight")
|| cn.equals("gaddock teeg") || cn.equals("gaddock teeg")
|| cn.equals("gaea's cradle")
|| cn.equals("genesis chamber") || cn.equals("genesis chamber")
|| cn.equals("ghave, guru of spores") || cn.equals("ghave, guru of spores")
|| cn.equals("gilded drake") || cn.equals("gilded drake")
|| cn.equals("glenn, the voice of calm") || cn.equals("glenn, the voice of calm")
|| cn.equals("global ruin") || cn.equals("global ruin")
|| cn.equals("golos, tireless pilgrim") || cn.equals("golos, tireless pilgrim")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("grave pact") || cn.equals("grave pact")
|| cn.equals("grave titan") || cn.equals("grave titan")
|| cn.equals("great whale") || cn.equals("great whale")
|| cn.equals("gregor, shrewd magistrate")
|| cn.equals("greymond, avacyn's stalwart")
|| cn.equals("grim monolith") || cn.equals("grim monolith")
|| cn.equals("grip of chaos") || cn.equals("grip of chaos")
|| cn.equals("gush") || cn.equals("gush")
@ -421,21 +421,23 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("hokori, dust drinker") || cn.equals("hokori, dust drinker")
|| cn.equals("humility") || cn.equals("humility")
|| cn.equals("impending disaster") || cn.equals("impending disaster")
|| cn.equals("imperial seal")
|| cn.equals("intruder alarm") || cn.equals("intruder alarm")
|| cn.equals("invoke prejudice") || cn.equals("invoke prejudice")
|| cn.equals("iona, shield of emeria") || 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("jokulhaups")
|| cn.equals("kaalia of the vast") || cn.equals("kaalia of the vast")
|| cn.equals("karador, ghost chieftain") || cn.equals("karador, ghost chieftain")
|| cn.equals("karakas") || cn.equals("karakas")
|| cn.equals("karn, silver golem") || cn.equals("karn, silver golem")
|| cn.equals("karn, the great creator")
|| cn.equals("kataki, war's wage") || cn.equals("kataki, war's wage")
|| cn.equals("keldon firebombers") || cn.equals("keldon firebombers")
|| cn.equals("kiki-jiki, mirror breaker") || cn.equals("kiki-jiki, mirror breaker")
|| cn.equals("kinnan, bonder prodigy")
|| cn.equals("knowledge pool") || cn.equals("knowledge pool")
|| cn.equals("koma, cosmos serpent")
|| cn.equals("korvold, fae-cursed king")
|| cn.equals("kozilek, butcher of truth") || cn.equals("kozilek, butcher of truth")
|| cn.equals("krark-clan ironworks") || cn.equals("krark-clan ironworks")
|| cn.equals("krenko, mob boss") || cn.equals("krenko, mob boss")
@ -452,10 +454,10 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("maelstrom wanderer") || cn.equals("maelstrom wanderer")
|| cn.equals("magister sphinx") || cn.equals("magister sphinx")
|| cn.equals("malfegor") || cn.equals("malfegor")
|| cn.equals("malik, grim manipulator")
|| cn.equals("mana breach") || cn.equals("mana breach")
|| cn.equals("mana crypt") || cn.equals("mana crypt")
|| cn.equals("mana drain") || cn.equals("mana drain")
|| cn.equals("mana vault")
|| cn.equals("mana vortex") || cn.equals("mana vortex")
|| cn.equals("master of cruelties") || cn.equals("master of cruelties")
|| cn.equals("memnarch") || cn.equals("memnarch")
@ -468,6 +470,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("minion reflector") || cn.equals("minion reflector")
|| cn.equals("mycosynth lattice") || cn.equals("mycosynth lattice")
|| cn.equals("myr turbine") || cn.equals("myr turbine")
|| cn.equals("nadu, winged wisdom")
|| cn.equals("narset, enlightened master") || cn.equals("narset, enlightened master")
|| cn.equals("narset, parter of veils") || cn.equals("narset, parter of veils")
|| cn.equals("nath of the gilt-leaf") || cn.equals("nath of the gilt-leaf")
@ -487,8 +490,8 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("oloro, ageless ascetic") || cn.equals("oloro, ageless ascetic")
|| cn.equals("omniscience") || cn.equals("omniscience")
|| cn.equals("opalescence") || cn.equals("opalescence")
|| cn.equals("opposition agent")
|| cn.equals("oppression") || cn.equals("oppression")
|| cn.equals("orcish bowmasters")
|| cn.equals("ornithopter") || cn.equals("ornithopter")
|| cn.equals("overwhelming splendor") || cn.equals("overwhelming splendor")
|| cn.equals("palinchron") || cn.equals("palinchron")
@ -504,7 +507,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("purphoros, god of the forge") || cn.equals("purphoros, god of the forge")
|| cn.equals("ravages of war") || cn.equals("ravages of war")
|| cn.equals("reclamation sage") || cn.equals("reclamation sage")
|| cn.equals("rhystic study")
|| cn.equals("rick, steadfast leader") || cn.equals("rick, steadfast leader")
|| cn.equals("rings of brighthearth") || cn.equals("rings of brighthearth")
|| cn.equals("rising waters") || cn.equals("rising waters")
@ -515,12 +517,11 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("seedborn muse") || cn.equals("seedborn muse")
|| cn.equals("sen triplets") || cn.equals("sen triplets")
|| cn.equals("sensei's divining top") || cn.equals("sensei's divining top")
|| cn.equals("serra's sanctum") || cn.equals("sheoldred, the apocalypse")
|| cn.equals("sheoldred, whispering one") || cn.equals("sheoldred, whispering one")
|| cn.equals("sire of insanity") || cn.equals("sire of insanity")
|| cn.equals("skithiryx, the blight dragon") || cn.equals("skithiryx, the blight dragon")
|| cn.equals("smokestack") || cn.equals("smokestack")
|| cn.equals("smothering tithe")
|| cn.equals("sol ring") || cn.equals("sol ring")
|| cn.equals("sorin markov") || cn.equals("sorin markov")
|| cn.equals("splinter twin") || cn.equals("splinter twin")
@ -532,8 +533,6 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("storm cauldron") || cn.equals("storm cauldron")
|| cn.equals("strip mine") || cn.equals("strip mine")
|| cn.equals("sunder") || cn.equals("sunder")
|| cn.equals("survival of the fittest")
|| cn.equals("table view")
|| cn.equals("tainted aether") || cn.equals("tainted aether")
|| cn.equals("tangle wire") || cn.equals("tangle wire")
|| cn.equals("tectonic break") || cn.equals("tectonic break")
@ -543,21 +542,19 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("teferi, master of time") || cn.equals("teferi, master of time")
|| cn.equals("teferi, time raveler") || cn.equals("teferi, time raveler")
|| cn.equals("temporal manipulation") || cn.equals("temporal manipulation")
|| cn.equals("tergrid, god of fright")
|| cn.equals("text view")
|| cn.equals("tezzeret the seeker") || cn.equals("tezzeret the seeker")
|| cn.equals("thassa's oracle")
|| cn.equals("the chain veil") || cn.equals("the chain veil")
|| cn.equals("the tabernacle at pendrell vale")
|| cn.equals("thieves' auction") || cn.equals("thieves' auction")
|| cn.equals("thoughts of ruin") || cn.equals("thoughts of ruin")
|| cn.equals("thrasios, triton hero") || cn.equals("thrasios, triton hero")
|| cn.equals("time sieve")
|| cn.equals("time stretch") || cn.equals("time stretch")
|| cn.equals("time warp") || cn.equals("time warp")
|| cn.equals("tinker") || cn.equals("tinker")
|| cn.equals("tooth and nail") || cn.equals("tooth and nail")
|| cn.equals("torment of hailfire") || cn.equals("torment of hailfire")
|| cn.equals("torpor orb") || cn.equals("torpor orb")
|| cn.equals("toxrill, the corrosive")
|| cn.equals("training grounds") || cn.equals("training grounds")
|| cn.equals("treasure cruise") || cn.equals("treasure cruise")
|| cn.equals("triskelavus") || cn.equals("triskelavus")
@ -566,18 +563,20 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("turnabout") || cn.equals("turnabout")
|| cn.equals("ugin, the spirit dragon") || cn.equals("ugin, the spirit dragon")
|| cn.equals("ulamog, the ceaseless hunger") || cn.equals("ulamog, the ceaseless hunger")
|| cn.equals("ulamog, the defiler")
|| cn.equals("ulamog, the infinite gyre") || cn.equals("ulamog, the infinite gyre")
|| cn.equals("umbral mantle") || cn.equals("umbral mantle")
|| cn.equals("urabrask the hidden") || cn.equals("urabrask the hidden")
|| cn.equals("urza, lord high artificer")
|| cn.equals("uyo, silent prophet") || cn.equals("uyo, silent prophet")
|| cn.equals("void winnower") || cn.equals("void winnower")
|| cn.equals("voltaic key") || cn.equals("voltaic key")
|| cn.equals("vorinclex, voice of hunger") || cn.equals("vorinclex, monstrous raider")
|| cn.equals("wake of destruction") || cn.equals("wake of destruction")
|| cn.equals("warp world") || cn.equals("warp world")
|| cn.equals("winter moon")
|| cn.equals("winter orb") || cn.equals("winter orb")
|| cn.equals("workhorse") || cn.equals("workhorse")
|| cn.equals("worldfire")
|| cn.equals("worldgorger dragon") || cn.equals("worldgorger dragon")
|| cn.equals("worthy cause") || cn.equals("worthy cause")
|| cn.equals("xanathar, guild kingpin") || cn.equals("xanathar, guild kingpin")
@ -586,6 +585,111 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
|| cn.equals("zur the enchanter")) { || cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 12); 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; return thisMaxPower;
} }

View file

@ -474,7 +474,7 @@ public final class GuiDisplayUtil {
public static void refreshThemeSettings() { public static void refreshThemeSettings() {
// apply Nimbus's look and fill // apply Nimbus's look and fill
// possible settings: // 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 // enable nimbus
try { try {

View file

@ -50,6 +50,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
private CardRenderer cardRenderer; private CardRenderer cardRenderer;
private int updateArtImageStamp; private int updateArtImageStamp;
private final int cardRenderMode;
private static class ImageKey { private static class ImageKey {
final BufferedImage artImage; 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.isCanAttack() ? 1 : 0));
sb.append((char) (this.view.isCanBlock() ? 1 : 0)); sb.append((char) (this.view.isCanBlock() ? 1 : 0));
sb.append((char) (this.view.isFaceDown() ? 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) { if (this.view instanceof PermanentView) {
sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0)); sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0));
sb.append((char) (((PermanentView) this.view).getDamage())); 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, 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 // Call to super
super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender); super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender);
// Renderer // Renderer
cardRenderer = cardRendererFactory.create(getGameCard()); cardRenderMode = renderMode;
cardRenderer = cardRendererFactory.create(getGameCard(), cardRenderMode);
// Draw the parts // Draw the parts
initialDraw(); initialDraw();
@ -265,7 +267,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
// Update renderer // Update renderer
cardImage = null; cardImage = null;
cardRenderer = cardRendererFactory.create(getGameCard()); cardRenderer = cardRendererFactory.create(getGameCard(), cardRenderMode);
cardRenderer.setArtImage(artImage); cardRenderer.setArtImage(artImage);
// Repaint // Repaint

View file

@ -1,5 +1,8 @@
package org.mage.card.arcane; package org.mage.card.arcane;
import mage.cards.FrameStyle;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.CardRenderMode;
import mage.view.CardView; import mage.view.CardView;
/** /**
@ -11,10 +14,27 @@ public class CardRendererFactory {
} }
public CardRenderer create(CardView card) { public CardRenderer create(CardView card) {
return create(card, -1);
}
public CardRenderer create(CardView card, int renderModeOverride) {
if (card.isSplitCard()) { if (card.isSplitCard()) {
return new ModernSplitCardRenderer(card); return new ModernSplitCardRenderer(card);
} else if (shouldRenderRetro(card, renderModeOverride)) {
// TODO: implement split card renderer for retro cards
return new RetroCardRenderer(card);
} else { } else {
return new ModernCardRenderer(card); 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); public static final Color ERROR_COLOR = new Color(255, 0, 255);
static String SUB_TYPE_ADVENTURE = "Adventure"; static String SUB_TYPE_ADVENTURE = "Adventure";
static String SUB_TYPE_OMEN = "Omen";
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Layout metrics for modern border cards // Layout metrics for modern border cards
@ -168,8 +169,8 @@ public class ModernCardRenderer extends CardRenderer {
// Processed mana cost string // Processed mana cost string
protected String manaCostString; protected String manaCostString;
// Is an adventure // Is an adventure or omen
protected boolean isAdventure = false; protected boolean isCardWithSpellOption = false;
public ModernCardRenderer(CardView card) { public ModernCardRenderer(CardView card) {
// Pass off to parent // Pass off to parent
@ -179,12 +180,13 @@ public class ModernCardRenderer extends CardRenderer {
manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr()); manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr());
if (cardView.isSplitCard()) { 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() { protected boolean isCardWithSpellOption() {
return isAdventure; return isCardWithSpellOption;
} }
@Override @Override
@ -316,7 +318,8 @@ public class ModernCardRenderer extends CardRenderer {
} else if (isUnstableFullArtLand()) { } else if (isUnstableFullArtLand()) {
rect = new Rectangle2D.Float(.0f, .0f, 1.0f, 1.0f); rect = new Rectangle2D.Float(.0f, .0f, 1.0f, 1.0f);
} else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT || } 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; rect = cardView.getArtRect().rect;
} else if (cardView.getFrameStyle().isFullArt() || (cardView.isToken())) { } else if (cardView.getFrameStyle().isFullArt() || (cardView.isToken())) {
rect = new Rectangle2D.Float(.079f, .11f, .84f, .63f); rect = new Rectangle2D.Float(.079f, .11f, .84f, .63f);
@ -660,7 +663,7 @@ public class ModernCardRenderer extends CardRenderer {
drawRulesText(g, textboxKeywords, textboxRules, drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2, contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2,
contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false); contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false);
} else if (isAdventure) { } else if (isCardWithSpellOption) {
drawRulesText(g, textboxKeywords, textboxRules, drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2, contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2,
contentWidth / 2 - 8, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3, false); 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 boolean isAftermath = false;
private static String trimAdventure(String rule) { 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.substring(rule.lastIndexOf("&mdash;") + 8);
} }
return rule; return rule;
@ -71,7 +71,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr()); rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr());
leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr()); leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr());
if (isAdventure()) { if (isCardWithSpellOption()) {
List<String> trimmedRules = new ArrayList<>(); List<String> trimmedRules = new ArrayList<>();
for (String rule : view.getRightSplitRules()) { for (String rule : view.getRightSplitRules()) {
trimmedRules.add(trimAdventure(rule)); trimmedRules.add(trimAdventure(rule));
@ -95,7 +95,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
// they "rotate" in opposite directions making consquence and normal split cards // they "rotate" in opposite directions making consquence and normal split cards
// have the "right" vs "left" as the top half. // have the "right" vs "left" as the top half.
// Adventures are treated differently and not rotated at all. // Adventures are treated differently and not rotated at all.
if (isAdventure()) { if (isCardWithSpellOption()) {
manaCostString = leftHalf.manaCostString; manaCostString = leftHalf.manaCostString;
textboxKeywords = leftHalf.keywords; textboxKeywords = leftHalf.keywords;
textboxRules = leftHalf.rules; textboxRules = leftHalf.rules;
@ -159,7 +159,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
protected void drawBackground(Graphics2D g) { protected void drawBackground(Graphics2D g) {
if (cardView.isFaceDown()) { if (cardView.isFaceDown()) {
drawCardBackTexture(g); drawCardBackTexture(g);
} if (isAdventure()) { } if (isCardWithSpellOption()) {
super.drawBackground(g); super.drawBackground(g);
} else { } else {
{ // Left half background (top of the card) { // Left half background (top of the card)
@ -204,7 +204,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override @Override
protected void drawArt(Graphics2D g) { protected void drawArt(Graphics2D g) {
if (isAdventure) { if (isCardWithSpellOption) {
super.drawArt(g); super.drawArt(g);
} else if (artImage != null) { } else if (artImage != null) {
if (isAftermath()) { if (isAftermath()) {
@ -318,7 +318,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override @Override
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) {
if (isAdventure()) { if (isCardWithSpellOption()) {
super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox); super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox);
CardPanelAttributes adventureAttribs = new CardPanelAttributes( 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; index += 5;
++outputIndex; ++outputIndex;
} else { } 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('&'); build.append('&');
++index; ++index;
++outputIndex; ++outputIndex;

View file

@ -3,7 +3,7 @@ package org.mage.plugins.card;
import mage.cards.MageCard; import mage.cards.MageCard;
import mage.cards.MagePermanent; import mage.cards.MagePermanent;
import mage.cards.action.ActionCallback; import mage.cards.action.ActionCallback;
import mage.client.util.GUISizeHelper; import mage.client.util.*;
import mage.interfaces.plugin.CardPlugin; import mage.interfaces.plugin.CardPlugin;
import mage.view.CardView; import mage.view.CardView;
import mage.view.CounterView; import mage.view.CounterView;
@ -35,6 +35,8 @@ import java.util.List;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static mage.client.util.CardRenderMode.*;
/** /**
* {@link CardPlugin} implementation. * {@link CardPlugin} implementation.
* *
@ -102,16 +104,19 @@ public class CardPluginImpl implements CardPlugin {
* yet, so use old component based rendering for the split cards. * yet, so use old component based rendering for the split cards.
*/ */
private CardPanel makeCardPanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback, private CardPanel makeCardPanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback,
boolean isFoil, Dimension dimension, int renderMode, boolean needFullPermanentRender) { boolean isFoil, Dimension dimension, int renderModeId, boolean needFullPermanentRender) {
switch (renderMode) { CardRenderMode cardRenderMode = CardRenderMode.fromId(renderModeId);
case 0: switch (cardRenderMode) {
case MTGO:
case FORCED_M15:
case FORCED_RETRO:
return new CardPanelRenderModeMTGO(view, gameId, loadImage, callback, isFoil, dimension, return new CardPanelRenderModeMTGO(view, gameId, loadImage, callback, isFoil, dimension,
needFullPermanentRender); needFullPermanentRender, renderModeId);
case 1: case IMAGE:
return new CardPanelRenderModeImage(view, gameId, loadImage, callback, isFoil, dimension, return new CardPanelRenderModeImage(view, gameId, loadImage, callback, isFoil, dimension,
needFullPermanentRender); needFullPermanentRender);
default: 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/Hazard Trooper", "ZOutamG.jpeg");
singleLinks.put("SWS/Head Hunting", "7OT1bGZ.jpeg"); singleLinks.put("SWS/Head Hunting", "7OT1bGZ.jpeg");
singleLinks.put("SWS/Heavy Trooper", "HhZWs2N.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/Hungry Dragonsnake", "23v7RTm.jpeg");
singleLinks.put("SWS/Hunt to Extinction", "3eJyfzZ.jpeg"); singleLinks.put("SWS/Hunt to Extinction", "3eJyfzZ.jpeg");
singleLinks.put("SWS/Hutt Crime Lord", "NAzK7Hp.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 String watermark; // background watermark image for some cards
public void prepareCompatibleData() { public void prepareCompatibleData() {
// take images from main card
if (this.image_uris != null) { if (this.image_uris != null) {
this.imageSmall = this.image_uris.getOrDefault("small", ""); this.imageSmall = this.image_uris.getOrDefault("small", "");
this.imageNormal = this.image_uris.getOrDefault("normal", ""); this.imageNormal = this.image_uris.getOrDefault("normal", "");
@ -48,11 +49,12 @@ public class ScryfallApiCard {
this.image_uris = null; this.image_uris = null;
} }
// take first available images from one of the faces
if (this.card_faces != null) { if (this.card_faces != null) {
this.card_faces.forEach(ScryfallApiCardFace::prepareCompatibleData); 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 // - scryfall: Ondu Knotmaster // Throw a Line
// - xmage: Ondu Knotmaster // - xmage: Ondu Knotmaster
if (this.layout.equals("adventure")) { if (this.layout.equals("adventure")) {
@ -88,11 +90,50 @@ public class ScryfallApiCard {
// - scryfall: Command Tower // Command Tower // - 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 // - 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.layout.equals("reversible_card")) {
if (!this.card_faces.get(0).name.equals(this.card_faces.get(1).name)) { if (false) {
throw new IllegalArgumentException("Scryfall: unsupported data type, reversible_card has diff faces " // ignore
+ this.set + " - " + this.collector_number + " - " + this.name); } 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 // 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/ // - 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* // - xmage uses ascii alternative Chandra Nalaar - dd2 - 34*
this.collector_number = transformCardNumberFromScryfallToXmage(this.collector_number); this.collector_number = transformCardNumberFromScryfallToXmage(this.collector_number);
} }
public static String transformCardNumberFromXmageToScryfall(String cardNumber) { public static String transformCardNumberFromXmageToScryfall(String cardNumber) {

View file

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

View file

@ -504,7 +504,7 @@ public class ScryfallImageSource implements CardImageSource {
jsonReader.close(); jsonReader.close();
return bulkCardsDatabaseAll.size() > 0; return bulkCardsDatabaseAll.size() > 0;
} catch (Exception e) { } 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 { try {
// clean up // clean up
if (!SCRYFALL_BULK_FILES_DEBUG_READ_ONLY_MODE) { if (!SCRYFALL_BULK_FILES_DEBUG_READ_ONLY_MODE) {

View file

@ -145,7 +145,7 @@ public class ScryfallImageSupportCards {
add("PAL06"); // Arena League 2006 add("PAL06"); // Arena League 2006
//add("PMPS06"); // Magic Premiere Shop 2006 //add("PMPS06"); // Magic Premiere Shop 2006
add("PHUK"); // Hachette UK add("PHUK"); // Hachette UK
add("PDCI"); // DCI Promos add("DCI"); // DCI Promos
add("P06"); // Magic Player Rewards 2006 add("P06"); // Magic Player Rewards 2006
add("G06"); // Judge Gift Cards 2006 add("G06"); // Judge Gift Cards 2006
add("F06"); // Friday Night Magic 2006 add("F06"); // Friday Night Magic 2006
@ -190,7 +190,6 @@ public class ScryfallImageSupportCards {
add("P09"); // Magic Player Rewards 2009 add("P09"); // Magic Player Rewards 2009
add("G09"); // Judge Gift Cards 2009 add("G09"); // Judge Gift Cards 2009
add("F09"); // Friday Night Magic 2009 add("F09"); // Friday Night Magic 2009
add("PBOOK"); // Miscellaneous Book Promos
add("CON"); // Conflux add("CON"); // Conflux
add("DDC"); // Duel Decks: Divine vs. Demonic add("DDC"); // Duel Decks: Divine vs. Demonic
add("ARB"); // Alara Reborn add("ARB"); // Alara Reborn
@ -405,7 +404,7 @@ public class ScryfallImageSupportCards {
add("XANA"); // Arena New Player Experience Extras add("XANA"); // Arena New Player Experience Extras
add("OANA"); // Arena New Player Experience Cards add("OANA"); // Arena New Player Experience Cards
add("PS18"); // San Diego Comic-Con 2018 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("C18"); // Commander 2018
add("PGRN"); // Guilds of Ravnica Promos add("PGRN"); // Guilds of Ravnica Promos
add("PRWK"); // GRN Ravnica Weekend add("PRWK"); // GRN Ravnica Weekend
@ -440,7 +439,7 @@ public class ScryfallImageSupportCards {
add("ELD"); // Throne of Eldraine add("ELD"); // Throne of Eldraine
//add("PTG"); // Ponies: The Galloping //add("PTG"); // Ponies: The Galloping
add("CMB1"); // Mystery Booster Playtest Cards 2019 add("CMB1"); // Mystery Booster Playtest Cards 2019
//add("MB1"); // Mystery Booster add("MB1"); // Mystery Booster
add("GN2"); // Game Night 2019 add("GN2"); // Game Night 2019
add("HA1"); // Historic Anthology 1 add("HA1"); // Historic Anthology 1
//add("HHO"); // Happy Holidays //add("HHO"); // Happy Holidays
@ -457,7 +456,7 @@ public class ScryfallImageSupportCards {
//add("FMB1"); // Mystery Booster Retail Edition Foils //add("FMB1"); // Mystery Booster Retail Edition Foils
add("HA2"); // Historic Anthology 2 add("HA2"); // Historic Anthology 2
add("SLD"); // Secret Lair Drop add("SLD"); // Secret Lair Drop
add("PMEI"); // Magazine Inserts add("PMEI"); // Media and Collaboration Promos
add("SLU"); // Secret Lair: Ultimate Edition add("SLU"); // Secret Lair: Ultimate Edition
add("SS3"); // Signature Spellbook: Chandra add("SS3"); // Signature Spellbook: Chandra
add("HA3"); // Historic Anthology 3 add("HA3"); // Historic Anthology 3
@ -467,7 +466,7 @@ public class ScryfallImageSupportCards {
// add("DD3"); // Duel Decks Anthology // add("DD3"); // Duel Decks Anthology
// add("PZ1"); // Legendary Cube // add("PZ1"); // Legendary Cube
add("IKO"); // Ikoria: Lair of Behemoths add("IKO"); // Ikoria: Lair of Behemoths
add("C20"); // Commander 2020 Edition add("C20"); // Commander 2020
add("M21"); // Core Set 2021 add("M21"); // Core Set 2021
add("JMP"); // Jumpstart add("JMP"); // Jumpstart
add("PH19"); // 2019 Heroes of the Realm add("PH19"); // 2019 Heroes of the Realm
@ -480,6 +479,7 @@ public class ScryfallImageSupportCards {
add("KLR"); // Kaladesh Remastered add("KLR"); // Kaladesh Remastered
add("CMR"); // Commander Legends add("CMR"); // Commander Legends
add("CC1"); // Commander Collection: Green add("CC1"); // Commander Collection: Green
add("PJ21"); // Judge Gift Cards 2021
add("PL21"); // Year of the Ox 2021 add("PL21"); // Year of the Ox 2021
add("KHM"); // Kaldheim add("KHM"); // Kaldheim
add("KHC"); // Kaldheim Commander add("KHC"); // Kaldheim Commander
@ -488,9 +488,10 @@ public class ScryfallImageSupportCards {
add("STA"); // Strixhaven Mystical Archive add("STA"); // Strixhaven Mystical Archive
add("HA4"); // Historic Anthology 4 add("HA4"); // Historic Anthology 4
add("HA5"); // Historic Anthology 5 add("HA5"); // Historic Anthology 5
add("C21"); // Commander 2021 Edition add("C21"); // Commander 2021
add("MH2"); // Modern Horizons 2 add("MH2"); // Modern Horizons 2
add("H1R"); // Modern Horizons 1 Timeshifts add("H1R"); // Modern Horizons 1 Timeshifts
add("PW21"); // Wizards Play Network 2021
add("PLG21"); // Love Your LGS 2021 add("PLG21"); // Love Your LGS 2021
add("AFR"); // Adventures in the Forgotten Realms add("AFR"); // Adventures in the Forgotten Realms
add("AFC"); // Forgotten Realms Commander add("AFC"); // Forgotten Realms Commander
@ -500,12 +501,15 @@ public class ScryfallImageSupportCards {
add("VOW"); // Innistrad: Crimson Vow add("VOW"); // Innistrad: Crimson Vow
add("VOC"); // Crimson Vow Commander add("VOC"); // Crimson Vow Commander
add("YMID"); // Alchemy: Innistrad add("YMID"); // Alchemy: Innistrad
add("P22"); // Judge Gift Cards 2022
add("DBL"); // Innistrad: Double Feature add("DBL"); // Innistrad: Double Feature
add("CC2"); // Commander Collection: Black add("CC2"); // Commander Collection: Black
add("NEO"); // Kamigawa: Neon Dynasty add("NEO"); // Kamigawa: Neon Dynasty
add("YNEO"); // Alchemy: Kamigawa add("YNEO"); // Alchemy: Kamigawa
add("NEC"); // Neon Dynasty Commander add("NEC"); // Neon Dynasty Commander
add("PL22"); // Year of the Tiger 2022 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("SNC"); // Streets of New Capenna
add("NCC"); // New Capenna Commander add("NCC"); // New Capenna Commander
add("SLX"); // Universes Within add("SLX"); // Universes Within
@ -514,7 +518,7 @@ public class ScryfallImageSupportCards {
add("DMU"); // Dominaria United add("DMU"); // Dominaria United
add("DMC"); // Dominaria United Commander add("DMC"); // Dominaria United Commander
add("YDMU"); // Alchemy: Dominaria add("YDMU"); // Alchemy: Dominaria
add("40K"); // Warhammer 40,000 add("40K"); // Warhammer 40,000 Commander
add("UNF"); // Unfinity add("UNF"); // Unfinity
add("GN3"); // Game Night: Free-for-All add("GN3"); // Game Night: Free-for-All
add("BRO"); // The Brothers' War add("BRO"); // The Brothers' War
@ -523,17 +527,25 @@ public class ScryfallImageSupportCards {
add("BOT"); // Transformers add("BOT"); // Transformers
add("J22"); // Jumpstart 2022 add("J22"); // Jumpstart 2022
add("SCD"); // Starter Commander Decks 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("SLC"); // Secret Lair 30th Anniversary Countdown Kit
add("DMR"); // Dominaria Remastered add("DMR"); // Dominaria Remastered
add("ONE"); // Phyrexia: All Will Be One add("ONE"); // Phyrexia: All Will Be One
add("ONC"); // Phyrexia: All Will Be One Commander add("ONC"); // Phyrexia: All Will Be One Commander
add("PL23"); // Year of the Rabbit 2023 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("SLP"); // Secret Lair Showdown
add("MOM"); // March of the Machine add("MOM"); // March of the Machine
add("MOC"); // March of the Machine Commander add("MOC"); // March of the Machine Commander
add("MAT"); // March of the Machine: The Aftermath add("MAT"); // March of the Machine: The Aftermath
add("MUL"); // Multiverse Legends 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("LTR"); // The Lord of the Rings: Tales of Middle-Earth
add("LTC"); // Tales of Middle-Earth Commander add("LTC"); // Tales of Middle-Earth Commander
add("CMM"); // Commander Masters add("CMM"); // Commander Masters
@ -542,9 +554,10 @@ public class ScryfallImageSupportCards {
add("WOT"); // Wilds of Eldraine: Enchanting Tales add("WOT"); // Wilds of Eldraine: Enchanting Tales
add("WOC"); // Wilds of Eldraine Commander add("WOC"); // Wilds of Eldraine Commander
add("LCI"); // The Lost Caverns of Ixalan 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("REX"); // Jurassic World Collection
add("SPG"); // Special Guests add("SPG"); // Special Guests
add("PW24"); // Wizards Play Network 2024
add("RVR"); // Ravnica Remastered add("RVR"); // Ravnica Remastered
add("PIP"); // Fallout add("PIP"); // Fallout
add("MKM"); // Murders at Karlov Manor add("MKM"); // Murders at Karlov Manor
@ -563,6 +576,19 @@ public class ScryfallImageSupportCards {
add("DSK"); // Duskmourn: House of Horror add("DSK"); // Duskmourn: House of Horror
add("DSC"); // Duskmourn: House of Horror Commander add("DSC"); // Duskmourn: House of Horror Commander
add("FDN"); // Foundations 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 // Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
add("CALC"); // Custom Alchemized versions of existing cards add("CALC"); // Custom Alchemized versions of existing cards
@ -609,46 +635,85 @@ public class ScryfallImageSupportCards {
// SLD // SLD
// fake double faced cards // 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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'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/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/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/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/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/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/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/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/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/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/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/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/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/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/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"); put("SLD/Zombie Master/1460b", "https://api.scryfall.com/cards/sld/1460/en?format=image&face=back");
// normal cards // 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 put("SLD/Viscera Seer/99999VS", "https://api.scryfall.com/cards/sld/VS/"); // see issue 11157
// CALC - custom alchemy version of cards. // CALC - custom alchemy version of cards.
put("CALC/C-Pillar of the Paruns", "https://api.scryfall.com/cards/dis/176/"); 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 // 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) // 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"); 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/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/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"); 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()); putAll(TokenRepository.instance.prepareScryfallDownloadList());
// RIX // 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/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/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"); 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/Treasure", "https://api.scryfall.com/cards/trna/12/en?format=image");
put("RNA/Zombie", "https://api.scryfall.com/cards/trna/3/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/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/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"); 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/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"); 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/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/Construct", "https://api.scryfall.com/cards/tdom/14/en?format=image");
put("DOM/Demon", "https://api.scryfall.com/cards/tdom/7/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/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"); 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/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/Illusion", "https://api.scryfall.com/cards/txln/2/en?format=image");
put("XLN/Merfolk", "https://api.scryfall.com/cards/txln/3/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/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"); 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/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/Insect", "https://api.scryfall.com/cards/thou/12/en?format=image");
put("HOU/Snake", "https://api.scryfall.com/cards/thou/11/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/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/Cat", "https://api.scryfall.com/cards/takh/16/en?format=image");
put("AKH/Drake", "https://api.scryfall.com/cards/takh/18/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/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/Wurm", "https://api.scryfall.com/cards/takh/24/en?format=image");
put("AKH/Zombie", "https://api.scryfall.com/cards/takh/20/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/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/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/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"); 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/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/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"); 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/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"); 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/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", "https://api.scryfall.com/cards/temn/7/en?format=image");
put("EMN/Human Wizard", "https://api.scryfall.com/cards/temn/2/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/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"); 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/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/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"); 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/Wolf", "https://api.scryfall.com/cards/tsoi/9/en?format=image");
put("SOI/Zombie", "https://api.scryfall.com/cards/tsoi/5/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/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/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"); 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/Plant", "https://api.scryfall.com/cards/togw/11/en?format=image");
put("OGW/Zombie", "https://api.scryfall.com/cards/togw/8/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/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", "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"); 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/Worm", "https://api.scryfall.com/cards/tc18/18/en?format=image");
put("C18/Zombie", "https://api.scryfall.com/cards/tc18/9/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/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/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"); 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/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/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/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/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/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"); 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/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/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/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/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/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"); 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"); put("NEC/Thopter", "https://api.scryfall.com/cards/tnec/12/en?format=image");
// SLD // 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/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/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/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/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/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/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/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/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/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/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 // 2XM
put("2XM/Angel", "https://api.scryfall.com/cards/t2xm/3/en?format=image"); 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/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/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/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/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/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"); 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/Spirit", "https://api.scryfall.com/cards/twoc/17/en?format=image");
put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/3/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 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 // 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 // LCI
put("LCI/Angel", "https://api.scryfall.com/cards/tlci/2/en?format=image"); 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"); put("BLB/Wall", "https://api.scryfall.com/cards/tblb/4/en?format=image");
// BLC // 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/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 // generate supported sets
supportedSets.clear(); supportedSets.clear();

View file

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

View file

@ -725,8 +725,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
try { try {
TVFS.umount(); TVFS.umount();
} catch (FsSyncException e) { } catch (FsSyncException e) {
logger.fatal("Couldn't unmount zip files " + e, e); logger.error("Couldn't unmount zip files " + e, e);
MageFrame.getInstance().showErrorDialog("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); logger.warn(err);
} }
} }
} catch (AccessDeniedException e) { } catch (AccessDeniedException e) {
incErrorCount(); incErrorCount();
logger.error("Can't access to files: " + card.getName() + "(" + card.getSet() + "). Try rebooting your system to remove the file lock."); logger.error("Can't access to files: " + card.getName() + "(" + card.getSet() + "). Try rebooting your system to remove the file lock.");
} catch (Exception e) { } catch (Exception e) {
incErrorCount(); incErrorCount();
logger.error("Unknown error: " + e.getMessage(), e); String sampleUrl = (urls == null ? "null" : urls.getDownloadList().stream().findFirst().orElse(null));
} finally { logger.error("Unknown error: " + e.getMessage() + ", sample url: " + sampleUrl, e);
} }
synchronized (sync) { synchronized (sync) {

View file

@ -185,7 +185,7 @@ public final class CardImageUtils {
try { try {
TVFS.umount(); TVFS.umount();
} catch (FsSyncException e) { } 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) // 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")) .anyMatch(c -> c.getCardNumber().equals("001"))
); );
urls = imageSource.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0)); 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 // added same tests for small images
@ -74,6 +74,6 @@ public class ScryfallImagesDownloadTest {
.anyMatch(c -> c.getCardNumber().equals("001")) .anyMatch(c -> c.getCardNumber().equals("001"))
); );
urls = imageSourceSmall.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0)); 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 mage.client.remote.XmageURLConnection;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -12,24 +13,40 @@ import java.io.InputStream;
/** /**
* @author JayDi85 * @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 { public class DownloaderTest {
@Test @Test
public void test_DownloadText_ByHttp() { 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>")); Assert.assertTrue("must have text data", s.contains("<head>"));
} }
@Test @Test
public void test_DownloadText_ByHttps() { 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>")); 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 @Test
public void test_DownloadFile_ByHttp() throws IOException { public void test_DownloadFile_ByHttp() throws IOException {
// use any public image here // 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); Assert.assertNotNull(stream);
BufferedImage image = ImageIO.read(stream); BufferedImage image = ImageIO.read(stream);
Assert.assertNotNull(stream); Assert.assertNotNull(stream);
@ -39,10 +56,28 @@ public class DownloaderTest {
@Test @Test
public void test_DownloadFile_ByHttps() throws IOException { public void test_DownloadFile_ByHttps() throws IOException {
// use any public image here // 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); Assert.assertNotNull(stream);
BufferedImage image = ImageIO.read(stream); BufferedImage image = ImageIO.read(stream);
Assert.assertNotNull(stream); Assert.assertNotNull(stream);
Assert.assertTrue("must have image data", image.getWidth() > 0); 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> <parent>
<groupId>org.mage</groupId> <groupId>org.mage</groupId>
<artifactId>mage-root</artifactId> <artifactId>mage-root</artifactId>
<version>1.4.54</version> <version>1.4.57</version>
</parent> </parent>
<artifactId>mage-common</artifactId> <artifactId>mage-common</artifactId>

View file

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

View file

@ -99,4 +99,7 @@ public class ClientCallback implements Serializable {
return messageId; 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.interfaces.callback.ClientCallback;
import mage.players.PlayerType; import mage.players.PlayerType;
import mage.players.net.UserData; import mage.players.net.UserData;
import mage.utils.CompressUtil;
import mage.util.ThreadUtils; import mage.util.ThreadUtils;
import mage.utils.CompressUtil;
import mage.view.*; import mage.view.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.jboss.remoting.*; import org.jboss.remoting.*;
import org.jboss.remoting.callback.Callback; import org.jboss.remoting.callback.Callback;
import org.jboss.remoting.callback.HandleCallbackException;
import org.jboss.remoting.callback.InvokerCallbackHandler; import org.jboss.remoting.callback.InvokerCallbackHandler;
import org.jboss.remoting.transport.bisocket.Bisocket; import org.jboss.remoting.transport.bisocket.Bisocket;
import org.jboss.remoting.transport.socket.SocketWrapper; import org.jboss.remoting.transport.socket.SocketWrapper;
@ -31,6 +30,7 @@ import java.lang.reflect.UndeclaredThrowableException;
import java.net.*; import java.net.*;
import java.util.*; import java.util.*;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit; 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_PERIOD_SECS = 4;
private static final int SESSION_VALIDATOR_PING_TIMEOUT_SECS = 3; 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 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) 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) { 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(); serverState = server.getServerState();
if (serverState == null) { if (serverState == null) {
throw new MageVersionException(client.getVersion(), null); throw new MageVersionException(client.getVersion(), null);
@ -597,14 +600,43 @@ public class SessionImpl implements Session {
class CallbackHandler implements InvokerCallbackHandler { class CallbackHandler implements InvokerCallbackHandler {
final CopyOnWriteArrayList<ClientCallback> waitingCallbacks = new CopyOnWriteArrayList<>();
@Override @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 { try {
client.onCallback((ClientCallback) callback.getCallbackObject()); executingCallbacks.forEach(client::onCallback);
} catch (Exception ex) { } catch (Exception ex) {
logger.error("handleCallback error", 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 // * 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_MAJOR = 1;
public static final int MAGE_VERSION_MINOR = 4; public static final int MAGE_VERSION_MINOR = 4;
public static final int MAGE_VERSION_RELEASE = 54; public static final int MAGE_VERSION_RELEASE = 57;
public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas public static final String MAGE_VERSION_RELEASE_INFO = "V2"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas
// strict mode // strict mode
// Each update requires a strict version // Each update requires a strict version

View file

@ -1,8 +1,6 @@
package mage.utils; package mage.utils;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
@ -24,9 +22,13 @@ import mage.game.command.Plane;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token; import mage.game.permanent.token.Token;
import mage.players.Player; import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPlayer;
import mage.target.common.TargetOpponent;
import mage.util.CardUtil; import mage.util.CardUtil;
import mage.util.MultiAmountMessage; import mage.util.MultiAmountMessage;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import mage.utils.testers.TestableDialogsRunner;
import java.io.File; import java.io.File;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -66,26 +68,31 @@ public final class SystemUtil {
// [@mana add] -> MANA ADD // [@mana add] -> MANA ADD
private static final String COMMAND_CARDS_ADD_TO_HAND = "@card add to hand"; 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_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_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_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_HAND = "@show opponent hand";
private static final String COMMAND_SHOW_OPPONENT_LIBRARY = "@show opponent library"; 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_HAND = "@show my hand";
private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library"; 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 Map<String, String> supportedCommands = new HashMap<>();
private static final TestableDialogsRunner testableDialogsRunner = new TestableDialogsRunner(); // for tests
static { static {
// special commands names in choose dialog // special commands names in choose dialog
supportedCommands.put(COMMAND_CARDS_ADD_TO_HAND, "CARDS: ADD TO HAND"); supportedCommands.put(COMMAND_CARDS_ADD_TO_HAND, "CARDS: ADD TO HAND");
supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD"); supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD");
supportedCommands.put(COMMAND_LANDS_ADD_TO_BATTLEFIELD, "LANDS: ADD TO BATTLEFIELD"); 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_RUN_CUSTOM_CODE, "RUN CUSTOM CODE");
supportedCommands.put(COMMAND_SHOW_OPPONENT_HAND, "SHOW OPPONENT HAND"); supportedCommands.put(COMMAND_SHOW_OPPONENT_HAND, "SHOW OPPONENT HAND");
supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY"); supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY");
supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND"); supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND");
supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY"); 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] private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card]
@ -255,13 +262,18 @@ public final class SystemUtil {
* *
* @param game * @param game
* @param commandsFilePath file path with commands in init.txt format * @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) { public static void executeCheatCommands(Game game, String commandsFilePath, Player feedbackPlayer) {
// fake test ability for triggers and events // 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()); 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<>(); List<String> errorsList = new ArrayList<>();
try { try {
@ -301,6 +313,9 @@ public final class SystemUtil {
// add default commands // add default commands
initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD)); initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD));
initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND)); 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 // collect all commands
CommandGroup currentGroup = null; CommandGroup currentGroup = null;
@ -386,7 +401,7 @@ public final class SystemUtil {
// 3. system commands // 3. system commands
if (runGroup.isSpecialCommand) { 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; String info;
switch (runGroup.name) { switch (runGroup.name) {
@ -419,53 +434,6 @@ public final class SystemUtil {
break; 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: { case COMMAND_CARDS_ADD_TO_HAND: {
// card // card
@ -480,7 +448,7 @@ public final class SystemUtil {
cardName = cardChoice.getChoice(); cardName = cardChoice.getChoice();
// amount // 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) { if (cardAmount == 0) {
break; break;
} }
@ -544,6 +512,47 @@ public final class SystemUtil {
break; 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: { default: {
String mes = String.format("Unknown system command: %s", runGroup.name); String mes = String.format("Unknown system command: %s", runGroup.name);
errorsList.add(mes); errorsList.add(mes);
@ -551,7 +560,6 @@ public final class SystemUtil {
break; break;
} }
} }
sendCheatCommandsFeedback(game, feedbackPlayer, errorsList);
return; 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