mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
deck improves:
* gui: removed public deck hash info; * gui: improved xmage dck-file - now it correctly load a card's amount (related to files from third party services); * server: fixed wrong cheating warning on deck construction (closes #11877); * refactor: removed outdated hash code and calculations; * other: added docs, added multiple deck hash tests;
This commit is contained in:
parent
889c1125e8
commit
7817a5cac6
32 changed files with 551 additions and 247 deletions
|
|
@ -87,7 +87,6 @@ import java.util.prefs.Preferences;
|
||||||
public class MageFrame extends javax.swing.JFrame implements MageClient {
|
public class MageFrame extends javax.swing.JFrame implements MageClient {
|
||||||
|
|
||||||
private static final String TITLE_NAME = "XMage";
|
private static final String TITLE_NAME = "XMage";
|
||||||
private static final Logger logger = Logger.getLogger(MageFrame.class);
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(MageFrame.class);
|
private static final Logger LOGGER = Logger.getLogger(MageFrame.class);
|
||||||
private static final String LITE_MODE_ARG = "-lite";
|
private static final String LITE_MODE_ARG = "-lite";
|
||||||
|
|
@ -398,7 +397,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bootstrapSetsAndFormats() {
|
private void bootstrapSetsAndFormats() {
|
||||||
logger.info("Loading sets and formats...");
|
LOGGER.info("Loading sets and formats...");
|
||||||
ConstructedFormats.ensureLists();
|
ConstructedFormats.ensureLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1460,7 +1459,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
||||||
try {
|
try {
|
||||||
instance = new MageFrame();
|
instance = new MageFrame();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.fatal("Critical error on start up, app will be closed: " + e.getMessage(), e);
|
LOGGER.fatal("Critical error on start up, app will be closed: " + e.getMessage(), e);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1861,9 +1861,9 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
|
||||||
List<CardView> gridStack = new ArrayList<>();
|
List<CardView> gridStack = new ArrayList<>();
|
||||||
gridRow.add(gridStack);
|
gridRow.add(gridStack);
|
||||||
for (DeckCardInfo info : stack) {
|
for (DeckCardInfo info : stack) {
|
||||||
if (trackedCards.containsKey(info.getSetCode()) && trackedCards.get(info.getSetCode()).containsKey(info.getCardNum())) {
|
if (trackedCards.containsKey(info.getSetCode()) && trackedCards.get(info.getSetCode()).containsKey(info.getCardNumber())) {
|
||||||
List<CardView> candidates
|
List<CardView> candidates
|
||||||
= trackedCards.get(info.getSetCode()).get(info.getCardNum());
|
= trackedCards.get(info.getSetCode()).get(info.getCardNumber());
|
||||||
if (!candidates.isEmpty()) {
|
if (!candidates.isEmpty()) {
|
||||||
gridStack.add(candidates.remove(0));
|
gridStack.add(candidates.remove(0));
|
||||||
thisMaxStackSize = Math.max(thisMaxStackSize, gridStack.size());
|
thisMaxStackSize = Math.max(thisMaxStackSize, gridStack.size());
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@ public class DeckGeneratorDialog {
|
||||||
tmp.getParentFile().mkdirs();
|
tmp.getParentFile().mkdirs();
|
||||||
tmp.createNewFile();
|
tmp.createNewFile();
|
||||||
deck.setName(deckName);
|
deck.setName(deckName);
|
||||||
XMAGE.getExporter().writeDeck(tmp.getAbsolutePath(), deck.getDeckCardLists());
|
XMAGE.getExporter().writeDeck(tmp.getAbsolutePath(), deck.prepareCardsOnlyDeck());
|
||||||
cleanUp();
|
cleanUp();
|
||||||
return tmp.getAbsolutePath();
|
return tmp.getAbsolutePath();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -794,18 +794,14 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
dialog.showDialog();
|
dialog.showDialog();
|
||||||
|
|
||||||
if (!dialog.getTmpPath().isEmpty()) {
|
if (!dialog.getTmpPath().isEmpty()) {
|
||||||
Deck deckToAppend = null;
|
|
||||||
StringBuilder errorMessages = new StringBuilder();
|
StringBuilder errorMessages = new StringBuilder();
|
||||||
|
|
||||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
try {
|
try {
|
||||||
deckToAppend = Deck.load(DeckImporter.importDeckFromFile(dialog.getTmpPath(), errorMessages, false), true, true);
|
Deck deckToAppend = Deck.load(DeckImporter.importDeckFromFile(dialog.getTmpPath(), errorMessages, false), true, true);
|
||||||
processAndShowImportErrors(errorMessages);
|
processAndShowImportErrors(errorMessages);
|
||||||
|
this.deck = Deck.append(deckToAppend, this.deck);
|
||||||
if (deckToAppend != null) {
|
|
||||||
deck = Deck.append(deckToAppend, deck);
|
|
||||||
refreshDeck();
|
refreshDeck();
|
||||||
}
|
|
||||||
} catch (GameException e1) {
|
} catch (GameException e1) {
|
||||||
JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -864,7 +860,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
try {
|
try {
|
||||||
DeckFormats.writeDeck(needFileName, deck.getDeckCardLists());
|
DeckFormats.writeDeck(needFileName, deck.prepareCardsOnlyDeck());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MageFrame.getPreferences().put("lastExportFolder", file.getCanonicalPath());
|
MageFrame.getPreferences().put("lastExportFolder", file.getCanonicalPath());
|
||||||
|
|
@ -1364,7 +1360,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
useDeckInfo = true;
|
useDeckInfo = true;
|
||||||
}
|
}
|
||||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
DeckCardLists cardLists = deck.getDeckCardLists();
|
DeckCardLists cardLists = deck.prepareCardsOnlyDeck();
|
||||||
cardLists.setCardLayout(deckArea.getCardLayout());
|
cardLists.setCardLayout(deckArea.getCardLayout());
|
||||||
cardLists.setSideboardLayout(deckArea.getSideboardLayout());
|
cardLists.setSideboardLayout(deckArea.getSideboardLayout());
|
||||||
if (!useDeckInfo) {
|
if (!useDeckInfo) {
|
||||||
|
|
@ -1444,12 +1440,14 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
if (mode == DeckEditorMode.SIDEBOARDING
|
if (mode == DeckEditorMode.SIDEBOARDING
|
||||||
|| mode == DeckEditorMode.LIMITED_BUILDING
|
|| mode == DeckEditorMode.LIMITED_BUILDING
|
||||||
|| mode == DeckEditorMode.LIMITED_SIDEBOARD_BUILDING) {
|
|| mode == DeckEditorMode.LIMITED_SIDEBOARD_BUILDING) {
|
||||||
|
// in game mode - move all to sideboard
|
||||||
for (Card card : deck.getCards()) {
|
for (Card card : deck.getCards()) {
|
||||||
deck.getSideboard().add(card);
|
deck.getSideboard().add(card);
|
||||||
}
|
}
|
||||||
deck.getCards().clear();
|
deck.getCards().clear();
|
||||||
cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard);
|
cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard);
|
||||||
} else {
|
} else {
|
||||||
|
// in deck editor mode - clear all cards
|
||||||
deck = new Deck();
|
deck = new Deck();
|
||||||
}
|
}
|
||||||
refreshDeck();
|
refreshDeck();
|
||||||
|
|
@ -1488,7 +1486,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
updateDeckTask.cancel(true);
|
updateDeckTask.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SessionHandler.submitDeck(mode, tableId, deck.getDeckCardLists())) {
|
if (SessionHandler.submitDeck(mode, tableId, deck.prepareCardsOnlyDeck())) {
|
||||||
removeDeckEditor();
|
removeDeckEditor();
|
||||||
}
|
}
|
||||||
}//GEN-LAST:event_btnSubmitActionPerformed
|
}//GEN-LAST:event_btnSubmitActionPerformed
|
||||||
|
|
@ -1504,7 +1502,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
updateDeckTask.cancel(true);
|
updateDeckTask.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SessionHandler.submitDeck(mode, tableId, deck.getDeckCardLists())) {
|
if (SessionHandler.submitDeck(mode, tableId, deck.prepareCardsOnlyDeck())) {
|
||||||
SwingUtilities.invokeLater(this::removeDeckEditor);
|
SwingUtilities.invokeLater(this::removeDeckEditor);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -1617,7 +1615,7 @@ class UpdateDeckTask extends SwingWorker<Void, Void> {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() throws Exception {
|
protected Void doInBackground() throws Exception {
|
||||||
while (!isCancelled()) {
|
while (!isCancelled()) {
|
||||||
SessionHandler.updateDeck(tableId, deck.getDeckCardLists());
|
SessionHandler.updateDeck(tableId, deck.prepareCardsOnlyDeck());
|
||||||
TimeUnit.SECONDS.sleep(5);
|
TimeUnit.SECONDS.sleep(5);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ public class DeckExportClipboardDialog extends MageDialog {
|
||||||
DeckExporter exporter = formats.get(formatIndex).getExporter();
|
DeckExporter exporter = formats.get(formatIndex).getExporter();
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
exporter.writeDeck(baos, deck.getDeckCardLists());
|
exporter.writeDeck(baos, deck.prepareCardsOnlyDeck());
|
||||||
editData.setText(baos.toString());
|
editData.setText(baos.toString());
|
||||||
editData.setCaretPosition(0);
|
editData.setCaretPosition(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ public class DeckLegalityPanel extends javax.swing.JPanel {
|
||||||
} else {
|
} else {
|
||||||
// contains mock cards, e.g. it's a Deck Editor
|
// contains mock cards, e.g. it's a Deck Editor
|
||||||
try {
|
try {
|
||||||
deckToValidate = Deck.load(deck.getDeckCardLists(), true, false);
|
deckToValidate = Deck.load(deck.prepareCardsOnlyDeck(), true, false);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Can't load real deck cards for validate: " + ex.getMessage(), ex);
|
logger.error("Can't load real deck cards for validate: " + ex.getMessage(), ex);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -557,7 +557,7 @@ public class CustomOptionsDialog extends MageDialog {
|
||||||
}
|
}
|
||||||
if (perPlayerEmblemDeck != null) {
|
if (perPlayerEmblemDeck != null) {
|
||||||
perPlayerEmblemDeck.clearLayouts();
|
perPlayerEmblemDeck.clearLayouts();
|
||||||
options.setPerPlayerEmblemCards(perPlayerEmblemDeck.getDeckCardLists().getCards());
|
options.setPerPlayerEmblemCards(perPlayerEmblemDeck.prepareCardsOnlyDeck().getCards());
|
||||||
} else {
|
} else {
|
||||||
options.setPerPlayerEmblemCards(Collections.emptySet());
|
options.setPerPlayerEmblemCards(Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
@ -571,7 +571,7 @@ public class CustomOptionsDialog extends MageDialog {
|
||||||
}
|
}
|
||||||
if (startingPlayerEmblemDeck != null) {
|
if (startingPlayerEmblemDeck != null) {
|
||||||
startingPlayerEmblemDeck.clearLayouts();
|
startingPlayerEmblemDeck.clearLayouts();
|
||||||
options.setGlobalEmblemCards(startingPlayerEmblemDeck.getDeckCardLists().getCards());
|
options.setGlobalEmblemCards(startingPlayerEmblemDeck.prepareCardsOnlyDeck().getCards());
|
||||||
} else {
|
} else {
|
||||||
options.setGlobalEmblemCards(Collections.emptySet());
|
options.setGlobalEmblemCards(Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,6 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
String countryName = CountryUtil.getCountryName(flagName);
|
String countryName = CountryUtil.getCountryName(flagName);
|
||||||
basicTooltipText = "<HTML>Name: " + player.getName()
|
basicTooltipText = "<HTML>Name: " + player.getName()
|
||||||
+ "<br/>Flag: " + (countryName == null ? "Unknown" : countryName)
|
+ "<br/>Flag: " + (countryName == null ? "Unknown" : countryName)
|
||||||
+ "<br/>Deck hash code: " + player.getDeckHashCode()
|
|
||||||
+ "<br/>This match wins: " + player.getWins() + " of " + player.getWinsNeeded() + " (to win the match)";
|
+ "<br/>This match wins: " + player.getWins() + " of " + player.getWinsNeeded() + " (to win the match)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1297,20 +1297,6 @@ public class SessionImpl implements Session {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public boolean startChallenge(UUID roomId, UUID tableId, UUID challengeId) {
|
|
||||||
// try {
|
|
||||||
// if (isConnected()) {
|
|
||||||
// server.startChallenge(sessionId, roomId, tableId, challengeId);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// } catch (MageException ex) {
|
|
||||||
// handleMageException(ex);
|
|
||||||
// } catch (Throwable t) {
|
|
||||||
// handleThrowable(t);
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
@Override
|
@Override
|
||||||
public boolean submitDeck(UUID tableId, DeckCardLists deck) {
|
public boolean submitDeck(UUID tableId, DeckCardLists deck) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ public class PlayerView implements Serializable {
|
||||||
private final Counters counters;
|
private final Counters counters;
|
||||||
private final int wins;
|
private final int wins;
|
||||||
private final int winsNeeded;
|
private final int winsNeeded;
|
||||||
private final long deckHashCode;
|
|
||||||
private final int libraryCount;
|
private final int libraryCount;
|
||||||
private final int handCount;
|
private final int handCount;
|
||||||
private final boolean isActive;
|
private final boolean isActive;
|
||||||
|
|
@ -68,8 +67,6 @@ public class PlayerView implements Serializable {
|
||||||
this.counters = player.getCounters();
|
this.counters = player.getCounters();
|
||||||
this.wins = player.getMatchPlayer().getWins();
|
this.wins = player.getMatchPlayer().getWins();
|
||||||
this.winsNeeded = player.getMatchPlayer().getWinsNeeded();
|
this.winsNeeded = player.getMatchPlayer().getWinsNeeded();
|
||||||
// If match ended immediately before, deck can be set to null so check is necessarry here
|
|
||||||
this.deckHashCode = player.getMatchPlayer().getDeck() != null ? player.getMatchPlayer().getDeck().getDeckHashCode() : 0;
|
|
||||||
this.libraryCount = player.getLibrary().size();
|
this.libraryCount = player.getLibrary().size();
|
||||||
this.handCount = player.getHand().size();
|
this.handCount = player.getHand().size();
|
||||||
this.manaPool = new ManaPoolView(player.getManaPool());
|
this.manaPool = new ManaPoolView(player.getManaPool());
|
||||||
|
|
@ -206,10 +203,6 @@ public class PlayerView implements Serializable {
|
||||||
return winsNeeded;
|
return winsNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDeckHashCode() {
|
|
||||||
return deckHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHandCount() {
|
public int getHandCount() {
|
||||||
return this.handCount;
|
return this.handCount;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
private static final int MAX_SIMULATED_NODES_PER_CALC = 5000;
|
private static final int MAX_SIMULATED_NODES_PER_CALC = 5000;
|
||||||
|
|
||||||
// same params as Executors.newFixedThreadPool
|
// same params as Executors.newFixedThreadPool
|
||||||
// no needs erorrs check in afterExecute here cause that pool used for FutureTask with result check already
|
// no needs errors check in afterExecute here cause that pool used for FutureTask with result check already
|
||||||
private static final ExecutorService threadPoolSimulations = new ThreadPoolExecutor(
|
private static final ExecutorService threadPoolSimulations = new ThreadPoolExecutor(
|
||||||
COMPUTER_MAX_THREADS_FOR_SIMULATIONS,
|
COMPUTER_MAX_THREADS_FOR_SIMULATIONS,
|
||||||
COMPUTER_MAX_THREADS_FOR_SIMULATIONS,
|
COMPUTER_MAX_THREADS_FOR_SIMULATIONS,
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,13 @@ public class CubeFromDeck extends DraftCube {
|
||||||
public CubeFromDeck(Deck cubeFromDeck) {
|
public CubeFromDeck(Deck cubeFromDeck) {
|
||||||
super("Cube From Deck");
|
super("Cube From Deck");
|
||||||
|
|
||||||
DeckCardLists cards = null;
|
if (cubeFromDeck == null) {
|
||||||
if (cubeFromDeck != null) {
|
return;
|
||||||
cards = cubeFromDeck.getDeckCardLists();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cards != null) {
|
DeckCardLists cards = cubeFromDeck.prepareCardsOnlyDeck();
|
||||||
for (DeckCardInfo card : cards.getCards()) {
|
for (DeckCardInfo card : cards.getCards()) {
|
||||||
cubeCards.add(new CardIdentity(card.getCardName(), card.getSetCode(), card.getCardNum()));
|
cubeCards.add(new CardIdentity(card.getCardName(), card.getSetCode(), card.getCardNumber()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,35 +236,51 @@ public class TableController {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and join real player
|
||||||
|
*/
|
||||||
public synchronized boolean joinTable(UUID userId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws MageException {
|
public synchronized boolean joinTable(UUID userId, String name, PlayerType playerType, int skill, DeckCardLists deckList, String password) throws MageException {
|
||||||
|
// user - must exist
|
||||||
Optional<User> _user = managerFactory.userManager().getUser(userId);
|
Optional<User> _user = managerFactory.userManager().getUser(userId);
|
||||||
if (!_user.isPresent()) {
|
if (!_user.isPresent()) {
|
||||||
logger.error("Join Table: can't find user to join " + name + " Id = " + userId);
|
logger.error("Join Table: can't find user to join " + name + " Id = " + userId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// table - already joined
|
||||||
User user = _user.get();
|
User user = _user.get();
|
||||||
if (userPlayerMap.containsKey(userId) && playerType == PlayerType.HUMAN) {
|
if (userPlayerMap.containsKey(userId) && playerType == PlayerType.HUMAN) {
|
||||||
user.showUserMessage("Join Table", new StringBuilder("You can join a table only one time.").toString());
|
user.showUserMessage("Join Table", new StringBuilder("You can join a table only one time.").toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// table - already started
|
||||||
if (table.getState() != TableState.WAITING) {
|
if (table.getState() != TableState.WAITING) {
|
||||||
user.showUserMessage("Join Table", "No available seats.");
|
user.showUserMessage("Join Table", "No available seats.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// check password
|
|
||||||
|
// table - wrong password
|
||||||
if (!table.getMatch().getOptions().getPassword().isEmpty() && playerType == PlayerType.HUMAN) {
|
if (!table.getMatch().getOptions().getPassword().isEmpty() && playerType == PlayerType.HUMAN) {
|
||||||
if (!table.getMatch().getOptions().getPassword().equals(password)) {
|
if (!table.getMatch().getOptions().getPassword().equals(password)) {
|
||||||
user.showUserMessage("Join Table", "Wrong password.");
|
user.showUserMessage("Join Table", "Wrong password.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// table - no more free seats
|
||||||
Seat seat = table.getNextAvailableSeat(playerType);
|
Seat seat = table.getNextAvailableSeat(playerType);
|
||||||
if (seat == null) {
|
if (seat == null) {
|
||||||
user.showUserMessage("Join Table", "No available seats.");
|
user.showUserMessage("Join Table", "No available seats.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deck - try to load real deck (any unknown cards will raise exception error here)
|
||||||
|
// P.S. Quick start button can raise error here on memory problems, but it's ok
|
||||||
|
// (real table creating is unaffected and will not create empty table)
|
||||||
Deck deck = Deck.load(deckList, false, false);
|
Deck deck = Deck.load(deckList, false, false);
|
||||||
|
|
||||||
|
// deck - validate
|
||||||
if (!Main.isTestMode() && !table.getValidator().validate(deck)) {
|
if (!Main.isTestMode() && !table.getValidator().validate(deck)) {
|
||||||
StringBuilder sb = new StringBuilder("You (").append(name).append(") have an invalid deck for the selected ").append(table.getValidator().getName()).append(" Format. \n\n");
|
StringBuilder sb = new StringBuilder("You (").append(name).append(") have an invalid deck for the selected ").append(table.getValidator().getName()).append(" Format. \n\n");
|
||||||
List<DeckValidatorError> errorsList = table.getValidator().getErrorsListSorted();
|
List<DeckValidatorError> errorsList = table.getValidator().getErrorsListSorted();
|
||||||
|
|
@ -273,13 +289,16 @@ public class TableController {
|
||||||
});
|
});
|
||||||
sb.append("\n\nSelect a deck that is appropriate for the selected format and try again!");
|
sb.append("\n\nSelect a deck that is appropriate for the selected format and try again!");
|
||||||
user.showUserMessage("Join Table", sb.toString());
|
user.showUserMessage("Join Table", sb.toString());
|
||||||
|
|
||||||
|
// owner must create table with valid deck only
|
||||||
if (isOwner(userId)) {
|
if (isOwner(userId)) {
|
||||||
logger.debug("New table removed because owner submitted invalid deck tableId " + table.getId());
|
logger.error("New table removed because owner submitted invalid deck tableId " + table.getId());
|
||||||
managerFactory.tableManager().removeTable(table.getId());
|
managerFactory.tableManager().removeTable(table.getId());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Check quit ratio.
|
|
||||||
|
// user - restrict by quit ratio
|
||||||
int quitRatio = table.getMatch().getOptions().getQuitRatio();
|
int quitRatio = table.getMatch().getOptions().getQuitRatio();
|
||||||
if (quitRatio < user.getMatchQuitRatio()) {
|
if (quitRatio < user.getMatchQuitRatio()) {
|
||||||
String message = new StringBuilder("Your quit ratio ").append(user.getMatchQuitRatio())
|
String message = new StringBuilder("Your quit ratio ").append(user.getMatchQuitRatio())
|
||||||
|
|
@ -288,7 +307,7 @@ public class TableController {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check minimum rating.
|
// user - restrict by rating
|
||||||
int minimumRating = table.getMatch().getOptions().getMinimumRating();
|
int minimumRating = table.getMatch().getOptions().getMinimumRating();
|
||||||
int userRating;
|
int userRating;
|
||||||
if (table.getMatch().getOptions().isLimited()) {
|
if (table.getMatch().getOptions().isLimited()) {
|
||||||
|
|
@ -303,7 +322,7 @@ public class TableController {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check power level for table (currently only used for EDH/Commander table)
|
// user - restrict by deck power level and cards colors (see edh power level for details)
|
||||||
int edhPowerLevel = table.getMatch().getOptions().getEdhPowerLevel();
|
int edhPowerLevel = table.getMatch().getOptions().getEdhPowerLevel();
|
||||||
if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase(Locale.ENGLISH).equals("commander")) {
|
if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase(Locale.ENGLISH).equals("commander")) {
|
||||||
int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck);
|
int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck);
|
||||||
|
|
@ -349,6 +368,7 @@ public class TableController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// player - try to create (human, ai, etc)
|
||||||
Optional<Player> playerOpt = createPlayer(name, seat.getPlayerType(), skill);
|
Optional<Player> playerOpt = createPlayer(name, seat.getPlayerType(), skill);
|
||||||
if (!playerOpt.isPresent()) {
|
if (!playerOpt.isPresent()) {
|
||||||
String message = "Could not create player " + name + " of type " + seat.getPlayerType();
|
String message = "Could not create player " + name + " of type " + seat.getPlayerType();
|
||||||
|
|
@ -356,14 +376,18 @@ public class TableController {
|
||||||
user.showUserMessage("Join Table", message);
|
user.showUserMessage("Join Table", message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// player - restrict by player type, e.g. draft bot can join to tourney only
|
||||||
Player player = playerOpt.get();
|
Player player = playerOpt.get();
|
||||||
if (!player.canJoinTable(table)) {
|
if (!player.canJoinTable(table)) {
|
||||||
user.showUserMessage("Join Table", "A " + seat.getPlayerType() + " player can't join this table.");
|
user.showUserMessage("Join Table", "A " + seat.getPlayerType() + " player can't join this table.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all fine, player can be added
|
||||||
match.addPlayer(player, deck);
|
match.addPlayer(player, deck);
|
||||||
table.joinTable(player, seat);
|
table.joinTable(player, seat);
|
||||||
logger.trace(player.getName() + " joined tableId: " + table.getId());
|
|
||||||
//only inform human players and add them to sessionPlayerMap
|
//only inform human players and add them to sessionPlayerMap
|
||||||
if (seat.getPlayer().isHuman()) {
|
if (seat.getPlayer().isHuman()) {
|
||||||
seat.getPlayer().setUserData(user.getUserData());
|
seat.getPlayer().setUserData(user.getUserData());
|
||||||
|
|
@ -373,6 +397,7 @@ public class TableController {
|
||||||
user.ccJoinedTable(table.getRoomId(), table.getId(), false);
|
user.ccJoinedTable(table.getRoomId(), table.getId(), false);
|
||||||
userPlayerMap.put(userId, player.getId());
|
userPlayerMap.put(userId, player.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -391,8 +416,22 @@ public class TableController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit deck on sideboarding/construction (final deck)
|
||||||
|
*/
|
||||||
public synchronized boolean submitDeck(UUID userId, DeckCardLists deckList) throws MageException {
|
public synchronized boolean submitDeck(UUID userId, DeckCardLists deckList) throws MageException {
|
||||||
UUID playerId = userPlayerMap.get(userId);
|
UUID playerId = userPlayerMap.get(userId);
|
||||||
|
|
||||||
|
// player - update tourney's player status
|
||||||
|
// TODO: need research, why it here instead real time check?
|
||||||
|
if (table.isTournamentSubTable()) {
|
||||||
|
TournamentPlayer tournamentPlayer = table.getTournament().getPlayer(playerId);
|
||||||
|
if (tournamentPlayer != null) {
|
||||||
|
tournamentPlayer.setStateInfo(""); // reset sideboarding state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// player - already quit
|
||||||
if (table.isTournament()) {
|
if (table.isTournament()) {
|
||||||
TournamentPlayer player = tournament.getPlayer(playerId);
|
TournamentPlayer player = tournament.getPlayer(playerId);
|
||||||
if (player == null || player.hasQuit()) {
|
if (player == null || player.hasQuit()) {
|
||||||
|
|
@ -403,23 +442,26 @@ public class TableController {
|
||||||
if (mPlayer == null || mPlayer.hasQuit()) {
|
if (mPlayer == null || mPlayer.hasQuit()) {
|
||||||
return true; // so the construct panel closes after submit
|
return true; // so the construct panel closes after submit
|
||||||
}
|
}
|
||||||
if (table.isTournamentSubTable()) {
|
|
||||||
TournamentPlayer tournamentPlayer = table.getTournament().getPlayer(mPlayer.getPlayer().getId());
|
|
||||||
if (tournamentPlayer != null) {
|
|
||||||
tournamentPlayer.setStateInfo(""); // reset sideboarding state
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// tourney - too late for submit
|
||||||
if (table.getState() != TableState.SIDEBOARDING && table.getState() != TableState.CONSTRUCTING) {
|
if (table.getState() != TableState.SIDEBOARDING
|
||||||
|
&& table.getState() != TableState.CONSTRUCTING) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deck - try to load real deck (any unknown cards will raise exception error here)
|
||||||
Deck deck = Deck.load(deckList, false, false);
|
Deck deck = Deck.load(deckList, false, false);
|
||||||
|
|
||||||
|
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
||||||
if (table.getState() == TableState.SIDEBOARDING && table.getMatch() != null) {
|
if (table.getState() == TableState.SIDEBOARDING && table.getMatch() != null) {
|
||||||
MatchPlayer mPlayer = table.getMatch().getPlayer(playerId);
|
MatchPlayer mPlayer = table.getMatch().getPlayer(playerId);
|
||||||
if (mPlayer != null) {
|
if (mPlayer != null) {
|
||||||
deck.setName(mPlayer.getDeck().getName());
|
deck.setName(mPlayer.getDeck().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deck - validate
|
||||||
if (!Main.isTestMode() && !table.getValidator().validate(deck)) {
|
if (!Main.isTestMode() && !table.getValidator().validate(deck)) {
|
||||||
Optional<User> _user = managerFactory.userManager().getUser(userId);
|
Optional<User> _user = managerFactory.userManager().getUser(userId);
|
||||||
if (!_user.isPresent()) {
|
if (!_user.isPresent()) {
|
||||||
|
|
@ -434,21 +476,20 @@ public class TableController {
|
||||||
_user.get().showUserMessage("Submit deck", sb.toString());
|
_user.get().showUserMessage("Submit deck", sb.toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitDeck(userId, playerId, deck);
|
submitDeck(userId, playerId, deck);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDeck(UUID userId, DeckCardLists deckList) throws MageException {
|
public void updateDeck(UUID userId, DeckCardLists deckList) throws MageException {
|
||||||
boolean validDeck;
|
|
||||||
UUID playerId = userPlayerMap.get(userId);
|
UUID playerId = userPlayerMap.get(userId);
|
||||||
if (table.getState() != TableState.SIDEBOARDING && table.getState() != TableState.CONSTRUCTING) {
|
// TODO: need refactor, many duplicated code like state check
|
||||||
|
if (table.getState() != TableState.SIDEBOARDING
|
||||||
|
&& table.getState() != TableState.CONSTRUCTING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Deck deck = Deck.load(deckList, false, false);
|
Deck deck = Deck.load(deckList, false, false);
|
||||||
validDeck = updateDeck(userId, playerId, deck);
|
updateDeck(userId, playerId, deck);
|
||||||
if (!validDeck && getTableState() == TableState.SIDEBOARDING) {
|
|
||||||
logger.warn(" userId: " + userId + " - Modified deck card list!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitDeck(UUID userId, UUID playerId, Deck deck) {
|
private void submitDeck(UUID userId, UUID playerId, Deck deck) {
|
||||||
|
|
@ -461,20 +502,21 @@ public class TableController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean updateDeck(UUID userId, UUID playerId, Deck deck) {
|
private void updateDeck(UUID userId, UUID playerId, Deck deck) {
|
||||||
boolean validDeck = true;
|
|
||||||
if (table.isTournament()) {
|
if (table.isTournament()) {
|
||||||
if (tournament != null) {
|
if (tournament != null) {
|
||||||
validDeck = managerFactory.tournamentManager().updateDeck(tournament.getId(), playerId, deck);
|
// TODO: is it possible to update from direct call command in game?!
|
||||||
|
managerFactory.tournamentManager().updateDeck(tournament.getId(), playerId, deck);
|
||||||
} else {
|
} else {
|
||||||
logger.fatal("Tournament == null table: " + table.getId() + " userId: " + userId);
|
logger.fatal("Tournament == null table: " + table.getId() + " userId: " + userId);
|
||||||
}
|
}
|
||||||
} else if (TableState.SIDEBOARDING == table.getState()) {
|
} else if (table.getState() == TableState.SIDEBOARDING) {
|
||||||
validDeck = match.updateDeck(playerId, deck);
|
match.updateDeck(playerId, deck);
|
||||||
} else {
|
} else {
|
||||||
// deck was meanwhile submitted so the autoupdate can be ignored
|
// deck was meanwhile submitted so the autoupdate can be ignored
|
||||||
|
// TODO: need research
|
||||||
|
logger.warn("wtf, why it submitting?!");
|
||||||
}
|
}
|
||||||
return validDeck;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean watchTable(UUID userId) {
|
public boolean watchTable(UUID userId) {
|
||||||
|
|
@ -538,6 +580,7 @@ public class TableController {
|
||||||
} else {
|
} else {
|
||||||
UUID playerId = userPlayerMap.get(userId);
|
UUID playerId = userPlayerMap.get(userId);
|
||||||
if (playerId != null) {
|
if (playerId != null) {
|
||||||
|
// TODO: wtf, need research and document all that checks, can be never used code
|
||||||
if (table.getState() == TableState.WAITING || table.getState() == TableState.READY_TO_START) {
|
if (table.getState() == TableState.WAITING || table.getState() == TableState.READY_TO_START) {
|
||||||
table.leaveNotStartedTable(playerId);
|
table.leaveNotStartedTable(playerId);
|
||||||
if (table.isTournament()) {
|
if (table.isTournament()) {
|
||||||
|
|
@ -884,7 +927,7 @@ public class TableController {
|
||||||
private void autoSideboard() {
|
private void autoSideboard() {
|
||||||
for (MatchPlayer player : match.getPlayers()) {
|
for (MatchPlayer player : match.getPlayers()) {
|
||||||
if (!player.isDoneSideboarding()) {
|
if (!player.isDoneSideboarding()) {
|
||||||
match.submitDeck(player.getPlayer().getId(), player.generateDeck(table.getValidator()));
|
match.submitDeck(player.getPlayer().getId(), player.autoCompleteDeck(table.getValidator()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public interface TournamentManager {
|
||||||
|
|
||||||
void submitDeck(UUID tournamentId, UUID playerId, Deck deck);
|
void submitDeck(UUID tournamentId, UUID playerId, Deck deck);
|
||||||
|
|
||||||
boolean updateDeck(UUID tournamentId, UUID playerId, Deck deck);
|
void updateDeck(UUID tournamentId, UUID playerId, Deck deck);
|
||||||
|
|
||||||
TournamentView getTournamentView(UUID tournamentId);
|
TournamentView getTournamentView(UUID tournamentId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -324,11 +324,13 @@ public class TournamentController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck) {
|
||||||
if (tournamentSessions.containsKey(playerId)) {
|
TournamentSession session = tournamentSessions.getOrDefault(playerId, null);
|
||||||
return tournamentSessions.get(playerId).updateDeck(deck);
|
if (session == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
session.updateDeck(deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void timeout(UUID userId) {
|
public void timeout(UUID userId) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
*/
|
*/
|
||||||
public class TournamentManagerImpl implements TournamentManager {
|
public class TournamentManagerImpl implements TournamentManager {
|
||||||
|
|
||||||
|
// TODO: must check all usage of controllers.get and insert null check and error log
|
||||||
private final ManagerFactory managerFactory;
|
private final ManagerFactory managerFactory;
|
||||||
private final ConcurrentMap<UUID, TournamentController> controllers = new ConcurrentHashMap<>();
|
private final ConcurrentMap<UUID, TournamentController> controllers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
@ -61,8 +62,8 @@ public class TournamentManagerImpl implements TournamentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateDeck(UUID tournamentId, UUID playerId, Deck deck) {
|
public void updateDeck(UUID tournamentId, UUID playerId, Deck deck) {
|
||||||
return controllers.get(tournamentId).updateDeck(playerId, deck);
|
controllers.get(tournamentId).updateDeck(playerId, deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ public class TournamentSession {
|
||||||
tournament.submitDeck(playerId, deck);
|
tournament.submitDeck(playerId, deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateDeck(Deck deck) {
|
public void updateDeck(Deck deck) {
|
||||||
return tournament.updateDeck(playerId, deck);
|
tournament.updateDeck(playerId, deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKilled() {
|
public void setKilled() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
package org.mage.test.serverside.deck;
|
||||||
|
|
||||||
|
import mage.cards.decks.Deck;
|
||||||
|
import mage.cards.decks.DeckCardInfo;
|
||||||
|
import mage.cards.decks.DeckCardLayout;
|
||||||
|
import mage.cards.decks.DeckCardLists;
|
||||||
|
import mage.game.GameException;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.MageTestPlayerBase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class DeckHashTest extends MageTestPlayerBase {
|
||||||
|
|
||||||
|
private static final DeckCardInfo BAD_CARD = new DeckCardInfo("unknown card", "123", "xxx");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_1 = new DeckCardInfo("Lightning Bolt", "141", "CLU");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_1_ANOTHER_SET = new DeckCardInfo("Lightning Bolt", "146", "M10");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_1_x3 = new DeckCardInfo("Lightning Bolt", "141", "CLU", 3);
|
||||||
|
private static final DeckCardInfo GOOD_CARD_2 = new DeckCardInfo("Bear's Companion", "182", "2x2");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_3 = new DeckCardInfo("Amplifire", "92", "RNA");
|
||||||
|
|
||||||
|
private void assertDecks(String check, boolean mustBeSame, Deck deck1, Deck deck2) {
|
||||||
|
Assert.assertEquals(
|
||||||
|
check + " - " + (mustBeSame ? "hash code must be same" : "hash code must be different"),
|
||||||
|
mustBeSame,
|
||||||
|
deck1.getDeckHash() == deck2.getDeckHash()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Deck prepareCardsDeck(List<DeckCardInfo> mainCards, List<DeckCardInfo> sideboardCards) {
|
||||||
|
DeckCardLists deckSource = new DeckCardLists();
|
||||||
|
deckSource.getCards().addAll(mainCards);
|
||||||
|
deckSource.getSideboard().addAll(sideboardCards);
|
||||||
|
try {
|
||||||
|
return Deck.load(deckSource, true);
|
||||||
|
} catch (GameException e) {
|
||||||
|
throw new IllegalArgumentException("Must not catch exception, but found " + e, e);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Assert.fail("wtf");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Deck prepareEmptyDeck(String name, String author, boolean addMainLayout, boolean addSideboardLayout) {
|
||||||
|
DeckCardLists deckSource = new DeckCardLists();
|
||||||
|
deckSource.setName(name);
|
||||||
|
deckSource.setAuthor(author);
|
||||||
|
if (addMainLayout) {
|
||||||
|
deckSource.setCardLayout(new DeckCardLayout(new ArrayList<>(), "xxx"));
|
||||||
|
}
|
||||||
|
if (addSideboardLayout) {
|
||||||
|
deckSource.setCardLayout(new DeckCardLayout(new ArrayList<>(), "xxx"));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Deck.load(deckSource, true);
|
||||||
|
} catch (GameException e) {
|
||||||
|
throw new IllegalArgumentException("Must not catch exception, but found " + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_MustIgnoreNonCardsData() {
|
||||||
|
// additional info must be ignored for deck hash
|
||||||
|
Deck emptyDeck = new Deck();
|
||||||
|
assertDecks("empty with empty", true, emptyDeck, emptyDeck);
|
||||||
|
assertDecks("empty with new empty", true, emptyDeck, new Deck());
|
||||||
|
|
||||||
|
assertDecks("empty with name", true, emptyDeck, prepareEmptyDeck("name", "", false, false));
|
||||||
|
assertDecks("empty with author", true, emptyDeck, prepareEmptyDeck("", "author", false, false));
|
||||||
|
assertDecks("empty with layout main", true, emptyDeck, prepareEmptyDeck("", "", true, false));
|
||||||
|
assertDecks("empty with layout sideboard", true, emptyDeck, prepareEmptyDeck("", "", false, true));
|
||||||
|
assertDecks("empty with all", true, emptyDeck, prepareEmptyDeck("name", "author", true, true));
|
||||||
|
|
||||||
|
assertDecks("empty with non empty", false, emptyDeck, prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_MustUseCardNameAndAmountOnly() {
|
||||||
|
// card names and amount must be important
|
||||||
|
// set and card number must be ignored
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"empty",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same names",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same names but diff sets",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1_ANOTHER_SET), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff names",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_2), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same main, same side",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList(GOOD_CARD_3)),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList(GOOD_CARD_3))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff main, same side",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList(GOOD_CARD_3)),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_2), Arrays.asList(GOOD_CARD_3))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same main, diff side",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList(GOOD_CARD_2)),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList(GOOD_CARD_3))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same names, but diff amount - main",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1_x3), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same names, but diff amount - side",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList(GOOD_CARD_1)),
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList(GOOD_CARD_1_x3))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_MustIgnoreCardsOrder() {
|
||||||
|
// deck hash must use sorted list, so deck order must be ignored
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff order - main",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_1, GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1_x3), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff order - side",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList(GOOD_CARD_1, GOOD_CARD_1, GOOD_CARD_1)),
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList(GOOD_CARD_1_x3))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff order - diff names",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_2, GOOD_CARD_3), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_3, GOOD_CARD_1, GOOD_CARD_2), Arrays.asList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_MustIgnoreUnknownCards() {
|
||||||
|
// hash must use only real cards and ignore missing
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"empty and unknown",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"unknown and unknown",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"unknown and x2 unknown",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD, BAD_CARD), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"card and unknown",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"card + unknown and unknown",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD, GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD), Arrays.asList())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"card + unknown and card + unknown",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(BAD_CARD, GOOD_CARD_1), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, BAD_CARD), Arrays.asList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_MustRaiseErrorOnTooManyCards() {
|
||||||
|
// good amount
|
||||||
|
Assert.assertEquals(90, new DeckCardInfo(GOOD_CARD_1.getCardName(), GOOD_CARD_1.getCardNumber(), GOOD_CARD_1.getSetCode(), 90).getAmount());
|
||||||
|
|
||||||
|
// bad amount
|
||||||
|
try {
|
||||||
|
new DeckCardInfo(GOOD_CARD_1.getCardName(), GOOD_CARD_1.getCardNumber(), GOOD_CARD_1.getSetCode(), 100);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e.getMessage().contains("Found too big amount")) {
|
||||||
|
// good
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.fail("must raise exception on too big amount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,8 +85,8 @@ public class TournamentStub implements Tournament {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck) {
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,34 @@
|
||||||
package mage.cards.decks;
|
package mage.cards.decks;
|
||||||
|
|
||||||
|
import mage.MageObject;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.repository.CardInfo;
|
import mage.cards.repository.CardInfo;
|
||||||
import mage.cards.repository.CardRepository;
|
import mage.cards.repository.CardRepository;
|
||||||
import mage.game.GameException;
|
import mage.game.GameException;
|
||||||
import mage.util.Copyable;
|
import mage.util.Copyable;
|
||||||
import mage.util.DeckUtil;
|
import mage.util.DeckUtil;
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server side deck with workable cards, also can be used in GUI
|
||||||
|
* <p>
|
||||||
|
* If you need text only deck then look at DeckCardLists
|
||||||
|
*/
|
||||||
public class Deck implements Serializable, Copyable<Deck> {
|
public class Deck implements Serializable, Copyable<Deck> {
|
||||||
|
|
||||||
static final int MAX_CARDS_PER_DECK = 2000;
|
static final int MAX_CARDS_PER_DECK = 2000;
|
||||||
|
|
||||||
private String name;
|
private String name; // TODO: must rework somehow - tiny leaders use deck name to find commander card and hide it for a user
|
||||||
private final Set<Card> cards = new LinkedHashSet<>();
|
private final Set<Card> cards = new LinkedHashSet<>();
|
||||||
private final Set<Card> sideboard = new LinkedHashSet<>();
|
private final Set<Card> sideboard = new LinkedHashSet<>();
|
||||||
private DeckCardLayout cardsLayout;
|
private DeckCardLayout cardsLayout; // client side only
|
||||||
private DeckCardLayout sideboardLayout;
|
private DeckCardLayout sideboardLayout; // client side only
|
||||||
private long deckHashCode = 0;
|
|
||||||
private long deckCompleteHashCode = 0;
|
|
||||||
|
|
||||||
public Deck() {
|
public Deck() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -34,8 +40,6 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
this.sideboard.addAll(deck.sideboard.stream().map(Card::copy).collect(Collectors.toList()));
|
this.sideboard.addAll(deck.sideboard.stream().map(Card::copy).collect(Collectors.toList()));
|
||||||
this.cardsLayout = deck.cardsLayout == null ? null : deck.cardsLayout.copy();
|
this.cardsLayout = deck.cardsLayout == null ? null : deck.cardsLayout.copy();
|
||||||
this.sideboardLayout = deck.sideboardLayout == null ? null : deck.sideboardLayout.copy();
|
this.sideboardLayout = deck.sideboardLayout == null ? null : deck.sideboardLayout.copy();
|
||||||
this.deckHashCode = deck.deckHashCode;
|
|
||||||
this.deckCompleteHashCode = deck.deckCompleteHashCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Deck load(DeckCardLists deckCardLists) throws GameException {
|
public static Deck load(DeckCardLists deckCardLists) throws GameException {
|
||||||
|
|
@ -46,27 +50,15 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
return Deck.load(deckCardLists, ignoreErrors, true);
|
return Deck.load(deckCardLists, ignoreErrors, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Deck append(Deck deckToAppend, Deck currentDeck) throws GameException {
|
public static Deck append(Deck sourceDeck, Deck currentDeck) throws GameException {
|
||||||
List<String> deckCardNames = new ArrayList<>();
|
Deck newDeck = currentDeck.copy();
|
||||||
|
sourceDeck.getCards().forEach(card -> {
|
||||||
for (Card card : deckToAppend.getCards()) {
|
newDeck.cards.add(card.copy());
|
||||||
if (card != null) {
|
});
|
||||||
currentDeck.cards.add(card);
|
sourceDeck.getSideboard().forEach(card -> {
|
||||||
deckCardNames.add(card.getName());
|
newDeck.sideboard.add(card.copy());
|
||||||
}
|
});
|
||||||
}
|
return newDeck;
|
||||||
List<String> sbCardNames = new ArrayList<>();
|
|
||||||
for (Card card : deckToAppend.getSideboard()) {
|
|
||||||
if (card != null) {
|
|
||||||
currentDeck.sideboard.add(card);
|
|
||||||
deckCardNames.add(card.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(deckCardNames);
|
|
||||||
Collections.sort(sbCardNames);
|
|
||||||
String deckString = deckCardNames.toString() + sbCardNames.toString();
|
|
||||||
currentDeck.setDeckHashCode(DeckUtil.fixedHash(deckString));
|
|
||||||
return currentDeck;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException {
|
public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException {
|
||||||
|
|
@ -87,49 +79,45 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
deck.setName(deckCardLists.getName());
|
deck.setName(deckCardLists.getName());
|
||||||
deck.cardsLayout = deckCardLists.getCardLayout() == null ? null : deckCardLists.getCardLayout().copy();
|
deck.cardsLayout = deckCardLists.getCardLayout() == null ? null : deckCardLists.getCardLayout().copy();
|
||||||
deck.sideboardLayout = deckCardLists.getSideboardLayout() == null ? null : deckCardLists.getSideboardLayout().copy();
|
deck.sideboardLayout = deckCardLists.getSideboardLayout() == null ? null : deckCardLists.getSideboardLayout().copy();
|
||||||
List<String> deckCardNames = new ArrayList<>();
|
|
||||||
int totalCards = 0;
|
|
||||||
for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) {
|
|
||||||
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
|
||||||
if (card != null) {
|
|
||||||
if (totalCards > MAX_CARDS_PER_DECK) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
deck.cards.add(card);
|
|
||||||
deckCardNames.add(card.getName());
|
|
||||||
totalCards++;
|
|
||||||
|
|
||||||
} else if (!ignoreErrors) {
|
// load only real cards
|
||||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
int totalCards = 0;
|
||||||
|
|
||||||
|
// main
|
||||||
|
main:
|
||||||
|
for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) {
|
||||||
|
for (int i = 1; i <= deckCardInfo.getAmount(); i++) {
|
||||||
|
if (totalCards > MAX_CARDS_PER_DECK) {
|
||||||
|
break main;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
List<String> sbCardNames = new ArrayList<>();
|
|
||||||
for (DeckCardInfo deckCardInfo : deckCardLists.getSideboard()) {
|
|
||||||
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
if (totalCards > MAX_CARDS_PER_DECK) {
|
deck.cards.add(card);
|
||||||
break;
|
|
||||||
}
|
|
||||||
deck.sideboard.add(card);
|
|
||||||
sbCardNames.add(card.getName());
|
|
||||||
totalCards++;
|
totalCards++;
|
||||||
} else if (!ignoreErrors) {
|
} else if (!ignoreErrors) {
|
||||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Collections.sort(deckCardNames);
|
|
||||||
Collections.sort(sbCardNames);
|
|
||||||
String deckString = deckCardNames.toString() + sbCardNames.toString();
|
|
||||||
deck.setDeckHashCode(DeckUtil.fixedHash(deckString));
|
|
||||||
if (sbCardNames.isEmpty()) {
|
|
||||||
deck.setDeckCompleteHashCode(deck.getDeckHashCode());
|
|
||||||
} else {
|
|
||||||
List<String> deckAllCardNames = new ArrayList<>();
|
|
||||||
deckAllCardNames.addAll(deckCardNames);
|
|
||||||
deckAllCardNames.addAll(sbCardNames);
|
|
||||||
Collections.sort(deckAllCardNames);
|
|
||||||
deck.setDeckCompleteHashCode(DeckUtil.fixedHash(deckAllCardNames.toString()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sideboard
|
||||||
|
side:
|
||||||
|
for (DeckCardInfo deckCardInfo : deckCardLists.getSideboard()) {
|
||||||
|
for (int i = 1; i <= deckCardInfo.getAmount(); i++) {
|
||||||
|
if (totalCards > MAX_CARDS_PER_DECK) {
|
||||||
|
break side;
|
||||||
|
}
|
||||||
|
Card card = createCard(deckCardInfo, mockCards, cardInfoCache);
|
||||||
|
if (card != null) {
|
||||||
|
deck.sideboard.add(card);
|
||||||
|
totalCards++;
|
||||||
|
} else if (!ignoreErrors) {
|
||||||
|
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return deck;
|
return deck;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +137,7 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
String cardError = String.format("Card not found - %s - %s - %s in deck %s.",
|
String cardError = String.format("Card not found - %s - %s - %s in deck %s.",
|
||||||
deckCardInfo.getCardName(),
|
deckCardInfo.getCardName(),
|
||||||
deckCardInfo.getSetCode(),
|
deckCardInfo.getSetCode(),
|
||||||
deckCardInfo.getCardNum(),
|
deckCardInfo.getCardNumber(),
|
||||||
deckName
|
deckName
|
||||||
);
|
);
|
||||||
cardError += "\n\nPossible reasons:";
|
cardError += "\n\nPossible reasons:";
|
||||||
|
|
@ -168,15 +156,15 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
CardInfo cardInfo;
|
CardInfo cardInfo;
|
||||||
if (cardInfoCache != null) {
|
if (cardInfoCache != null) {
|
||||||
// from cache
|
// from cache
|
||||||
String key = String.format("%s_%s", deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
String key = String.format("%s_%s", deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||||
cardInfo = cardInfoCache.getOrDefault(key, null);
|
cardInfo = cardInfoCache.getOrDefault(key, null);
|
||||||
if (cardInfo == null) {
|
if (cardInfo == null) {
|
||||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||||
cardInfoCache.put(key, cardInfo);
|
cardInfoCache.put(key, cardInfo);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// from db
|
// from db
|
||||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cardInfo == null) {
|
if (cardInfo == null) {
|
||||||
|
|
@ -190,13 +178,18 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeckCardLists getDeckCardLists() {
|
/**
|
||||||
|
* Prepare new deck with cards only without layout and other client side settings.
|
||||||
|
* Use it for any export and server's deck update/submit
|
||||||
|
*/
|
||||||
|
public DeckCardLists prepareCardsOnlyDeck() {
|
||||||
DeckCardLists deckCardLists = new DeckCardLists();
|
DeckCardLists deckCardLists = new DeckCardLists();
|
||||||
|
|
||||||
deckCardLists.setName(name);
|
deckCardLists.setName(name);
|
||||||
|
|
||||||
for (Card card : cards) {
|
for (Card card : cards) {
|
||||||
deckCardLists.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
deckCardLists.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card card : sideboard) {
|
for (Card card : sideboard) {
|
||||||
deckCardLists.getSideboard().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
deckCardLists.getSideboard().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +220,7 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
return cards;
|
return cards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: delete and replace by getCards()
|
||||||
public Set<Card> getMaindeckCards() {
|
public Set<Card> getMaindeckCards() {
|
||||||
return cards
|
return cards
|
||||||
.stream()
|
.stream()
|
||||||
|
|
@ -262,22 +256,6 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
return sideboardLayout;
|
return sideboardLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDeckHashCode() {
|
|
||||||
return deckHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDeckHashCode(long deckHashCode) {
|
|
||||||
this.deckHashCode = deckHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDeckCompleteHashCode() {
|
|
||||||
return deckCompleteHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDeckCompleteHashCode(long deckHashCode) {
|
|
||||||
this.deckCompleteHashCode = deckHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLayouts() {
|
public void clearLayouts() {
|
||||||
this.cardsLayout = null;
|
this.cardsLayout = null;
|
||||||
this.sideboardLayout = null;
|
this.sideboardLayout = null;
|
||||||
|
|
@ -287,4 +265,11 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
public Deck copy() {
|
public Deck copy() {
|
||||||
return new Deck(this);
|
return new Deck(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getDeckHash() {
|
||||||
|
return DeckUtil.getDeckHash(
|
||||||
|
this.cards.stream().map(MageObject::getName).collect(Collectors.toList()),
|
||||||
|
this.sideboard.stream().map(MageObject::getName).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
package mage.cards.decks;
|
package mage.cards.decks;
|
||||||
|
|
||||||
import mage.util.Copyable;
|
import mage.util.Copyable;
|
||||||
|
|
@ -7,14 +5,18 @@ import mage.util.Copyable;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Client side: card record in deck file
|
||||||
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
||||||
|
|
||||||
|
static final int MAX_AMOUNT_PER_CARD = 99;
|
||||||
|
|
||||||
private String cardName;
|
private String cardName;
|
||||||
private String setCode;
|
private String setCode;
|
||||||
private String cardNum;
|
private String cardNumber;
|
||||||
private int quantity;
|
private int amount;
|
||||||
|
|
||||||
public DeckCardInfo() {
|
public DeckCardInfo() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -23,19 +25,29 @@ public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
||||||
protected DeckCardInfo(final DeckCardInfo info) {
|
protected DeckCardInfo(final DeckCardInfo info) {
|
||||||
this.cardName = info.cardName;
|
this.cardName = info.cardName;
|
||||||
this.setCode = info.setCode;
|
this.setCode = info.setCode;
|
||||||
this.cardNum = info.cardNum;
|
this.cardNumber = info.cardNumber;
|
||||||
this.quantity = info.quantity;
|
this.amount = info.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeckCardInfo(String cardName, String cardNum, String setCode) {
|
public DeckCardInfo(String cardName, String cardNumber, String setCode) {
|
||||||
this(cardName, cardNum, setCode, 1);
|
this(cardName, cardNumber, setCode, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeckCardInfo(String cardName, String cardNum, String setCode, int quantity) {
|
public static void makeSureCardAmountFine(int amount, String cardName) {
|
||||||
|
// runtime check
|
||||||
|
if (amount > MAX_AMOUNT_PER_CARD) {
|
||||||
|
// xmage uses 1 for amount all around, but keep that protection anyway
|
||||||
|
throw new IllegalArgumentException("Found too big amount for a deck's card: " + cardName + " - " + amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeckCardInfo(String cardName, String cardNumber, String setCode, int amount) {
|
||||||
|
makeSureCardAmountFine(amount, cardName);
|
||||||
|
|
||||||
this.cardName = cardName;
|
this.cardName = cardName;
|
||||||
this.cardNum = cardNum;
|
this.cardNumber = cardNumber;
|
||||||
this.setCode = setCode;
|
this.setCode = setCode;
|
||||||
this.quantity = quantity;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCardName() {
|
public String getCardName() {
|
||||||
|
|
@ -46,21 +58,16 @@ public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
||||||
return setCode;
|
return setCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCardNum() {
|
public String getCardNumber() {
|
||||||
return cardNum;
|
return cardNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getQuantity() {
|
public int getAmount() {
|
||||||
return quantity;
|
return amount;
|
||||||
}
|
|
||||||
|
|
||||||
public DeckCardInfo increaseQuantity() {
|
|
||||||
quantity++;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCardKey() {
|
public String getCardKey() {
|
||||||
return setCode + cardNum;
|
return setCode + cardNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ import mage.util.Copyable;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Client side deck with text only.
|
||||||
|
* <p>
|
||||||
|
* Can contain restricted, un-implemented or unknown cards
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
|
|
@ -46,12 +48,15 @@ public class DeckCardLists implements Serializable, Copyable<DeckCardLists> {
|
||||||
public DeckCardLayout getCardLayout() {
|
public DeckCardLayout getCardLayout() {
|
||||||
return cardLayout;
|
return cardLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCardLayout(DeckCardLayout layout) {
|
public void setCardLayout(DeckCardLayout layout) {
|
||||||
this.cardLayout = layout;
|
this.cardLayout = layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeckCardLayout getSideboardLayout() {
|
public DeckCardLayout getSideboardLayout() {
|
||||||
return sideboardLayout;
|
return sideboardLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSideboardLayout(DeckCardLayout layout) {
|
public void setSideboardLayout(DeckCardLayout layout) {
|
||||||
this.sideboardLayout = layout;
|
this.sideboardLayout = layout;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ public enum DeckFormats {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: need refactor and remove all that methods to 1-3 versions only
|
||||||
|
|
||||||
public static void writeDeck(String file, DeckCardLists deck) throws IOException {
|
public static void writeDeck(String file, DeckCardLists deck) throws IOException {
|
||||||
writeDeck(new File(file), deck);
|
writeDeck(new File(file), deck);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,9 @@ public enum ExpansionRepository {
|
||||||
instanceInitialized = true;
|
instanceInitialized = true;
|
||||||
|
|
||||||
eventSource.fireRepositoryDbLoaded();
|
eventSource.fireRepositoryDbLoaded();
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException e) {
|
||||||
ex.printStackTrace();
|
// TODO: add app close?
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import mage.cards.repository.CardRepository;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.command.Emblem;
|
import mage.game.command.Emblem;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -50,7 +49,7 @@ public final class EmblemOfCard extends Emblem {
|
||||||
public static Card cardFromDeckInfo(DeckCardInfo info) {
|
public static Card cardFromDeckInfo(DeckCardInfo info) {
|
||||||
return lookupCard(
|
return lookupCard(
|
||||||
info.getCardName(),
|
info.getCardName(),
|
||||||
info.getCardNum(),
|
info.getCardNumber(),
|
||||||
info.getSetCode(),
|
info.getSetCode(),
|
||||||
"DeckCardInfo"
|
"DeckCardInfo"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ public interface Match {
|
||||||
|
|
||||||
void submitDeck(UUID playerId, Deck deck);
|
void submitDeck(UUID playerId, Deck deck);
|
||||||
|
|
||||||
boolean updateDeck(UUID playerId, Deck deck);
|
void updateDeck(UUID playerId, Deck deck);
|
||||||
|
|
||||||
void startMatch();
|
void startMatch();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -379,9 +379,8 @@ public abstract class MatchImpl implements Match {
|
||||||
public void submitDeck(UUID playerId, Deck deck) {
|
public void submitDeck(UUID playerId, Deck deck) {
|
||||||
MatchPlayer player = getPlayer(playerId);
|
MatchPlayer player = getPlayer(playerId);
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
// make sure the deck name (needed for Tiny Leaders) won't get lost by sideboarding
|
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
||||||
deck.setName(player.getDeck().getName());
|
deck.setName(player.getDeck().getName());
|
||||||
deck.setDeckHashCode(player.getDeck().getDeckHashCode());
|
|
||||||
player.submitDeck(deck);
|
player.submitDeck(deck);
|
||||||
}
|
}
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
|
@ -390,21 +389,15 @@ public abstract class MatchImpl implements Match {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck) {
|
||||||
boolean validDeck = true;
|
// used for auto-save deck
|
||||||
MatchPlayer player = getPlayer(playerId);
|
MatchPlayer player = getPlayer(playerId);
|
||||||
if (player != null) {
|
if (player == null) {
|
||||||
// Check if the cards included in the deck are the same as in the original deck
|
return;
|
||||||
validDeck = (player.getDeck().getDeckCompleteHashCode() == deck.getDeckCompleteHashCode());
|
|
||||||
if (validDeck == false) {
|
|
||||||
// clear the deck so the player cheating looses the game
|
|
||||||
deck.getCards().clear();
|
|
||||||
deck.getSideboard().clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
player.updateDeck(deck);
|
player.updateDeck(deck);
|
||||||
}
|
}
|
||||||
return validDeck;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String createGameStartMessage() {
|
protected String createGameStartMessage() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
@ -416,9 +409,6 @@ public abstract class MatchImpl implements Match {
|
||||||
sb.append(" QUITTED");
|
sb.append(" QUITTED");
|
||||||
}
|
}
|
||||||
sb.append("<br/>");
|
sb.append("<br/>");
|
||||||
if (mp.getDeck() != null) {
|
|
||||||
sb.append("DeckHash: ").append(mp.getDeck().getDeckHashCode()).append("<br/>");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (getDraws() > 0) {
|
if (getDraws() > 0) {
|
||||||
sb.append(" Draws: ").append(getDraws()).append("<br/>");
|
sb.append(" Draws: ").append(getDraws()).append("<br/>");
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import mage.cards.Card;
|
||||||
import mage.cards.decks.Deck;
|
import mage.cards.decks.Deck;
|
||||||
import mage.cards.decks.DeckValidator;
|
import mage.cards.decks.DeckValidator;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|
@ -12,13 +13,15 @@ import java.io.Serializable;
|
||||||
*/
|
*/
|
||||||
public class MatchPlayer implements Serializable {
|
public class MatchPlayer implements Serializable {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MatchPlayer.class);
|
||||||
|
|
||||||
private int wins;
|
private int wins;
|
||||||
private int winsNeeded;
|
private int winsNeeded;
|
||||||
private boolean matchWinner;
|
private boolean matchWinner;
|
||||||
|
|
||||||
private Deck deck;
|
private Deck deck;
|
||||||
private Player player;
|
private final Player player;
|
||||||
private String name;
|
private final String name;
|
||||||
|
|
||||||
private boolean quit;
|
private boolean quit;
|
||||||
private boolean doneSideboarding;
|
private boolean doneSideboarding;
|
||||||
|
|
@ -89,22 +92,33 @@ public class MatchPlayer implements Serializable {
|
||||||
return viewerDeck;
|
return viewerDeck;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void submitDeck(Deck deck) {
|
public void submitDeck(Deck newDeck) {
|
||||||
this.deck = deck;
|
this.deck = newDeck;
|
||||||
this.doneSideboarding = true;
|
this.doneSideboarding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDeck(Deck deck) {
|
public boolean updateDeck(Deck newDeck) {
|
||||||
|
// used for auto-save by timeout from client side
|
||||||
|
|
||||||
|
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
||||||
if (this.deck != null) {
|
if (this.deck != null) {
|
||||||
// preserver deck name, important for Tiny Leaders format
|
newDeck.setName(this.getDeck().getName());
|
||||||
deck.setName(this.getDeck().getName());
|
|
||||||
// preserve the original deck hash code before sideboarding to give no information if cards were swapped
|
|
||||||
deck.setDeckHashCode(this.getDeck().getDeckHashCode());
|
|
||||||
}
|
|
||||||
this.deck = deck;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Deck generateDeck(DeckValidator deckValidator) {
|
// make sure it's the same deck (player do not add or remove something)
|
||||||
|
boolean isGood = (this.deck.getDeckHash() == newDeck.getDeckHash());
|
||||||
|
if (!isGood) {
|
||||||
|
logger.error("Found cheating player " + player.getName()
|
||||||
|
+ " with changed deck, main " + newDeck.getCards().size() + ", side " + newDeck.getSideboard().size());
|
||||||
|
newDeck.getCards().clear();
|
||||||
|
newDeck.getSideboard().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deck = newDeck;
|
||||||
|
return isGood;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Deck autoCompleteDeck(DeckValidator deckValidator) {
|
||||||
// auto complete deck
|
// auto complete deck
|
||||||
while (deck.getMaindeckCards().size() < deckValidator.getDeckMinSize() && !deck.getSideboard().isEmpty()) {
|
while (deck.getMaindeckCards().size() < deckValidator.getDeckMinSize() && !deck.getSideboard().isEmpty()) {
|
||||||
Card card = deck.getSideboard().iterator().next();
|
Card card = deck.getSideboard().iterator().next();
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public interface Tournament {
|
||||||
|
|
||||||
void submitDeck(UUID playerId, Deck deck);
|
void submitDeck(UUID playerId, Deck deck);
|
||||||
|
|
||||||
boolean updateDeck(UUID playerId, Deck deck);
|
void updateDeck(UUID playerId, Deck deck);
|
||||||
|
|
||||||
void autoSubmit(UUID playerId, Deck deck);
|
void autoSubmit(UUID playerId, Deck deck);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,11 +139,13 @@ public abstract class TournamentImpl implements Tournament {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck) {
|
||||||
if (players.containsKey(playerId)) {
|
TournamentPlayer player = players.getOrDefault(playerId, null);
|
||||||
return players.get(playerId).updateDeck(deck);
|
if (player == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
player.updateDeck(deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Round createRoundRandom() {
|
protected Round createRoundRandom() {
|
||||||
|
|
|
||||||
|
|
@ -93,18 +93,18 @@ public class TournamentPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateDeck(Deck deck) {
|
public boolean updateDeck(Deck deck) {
|
||||||
// Check if the cards included in the deck are the same as in the original deck
|
// used for auto-save deck
|
||||||
boolean validDeck = (getDeck().getDeckCompleteHashCode() == deck.getDeckCompleteHashCode());
|
|
||||||
if (!validDeck) {
|
// make sure it's the same deck (player do not add or remove something)
|
||||||
// Clear the deck so the player cheating looses the game
|
boolean isGood = (this.getDeck().getDeckHash() == deck.getDeckHash());
|
||||||
// TODO: inform other players about cheating?!
|
if (!isGood) {
|
||||||
logger.error("Found cheating player " + getPlayer().getName()
|
logger.error("Found cheating tourney player " + player.getName()
|
||||||
+ " with changed deck, main " + deck.getCards().size() + ", side " + deck.getSideboard().size());
|
+ " with changed deck, main " + deck.getCards().size() + ", side " + deck.getSideboard().size());
|
||||||
deck.getCards().clear();
|
deck.getCards().clear();
|
||||||
deck.getSideboard().clear();
|
deck.getSideboard().clear();
|
||||||
}
|
}
|
||||||
this.deck = deck;
|
this.deck = deck;
|
||||||
return validDeck;
|
return isGood;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,36 @@
|
||||||
package mage.util;
|
package mage.util;
|
||||||
|
|
||||||
|
import mage.cards.decks.Deck;
|
||||||
|
import mage.players.Player;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2, JayDi85
|
||||||
*/
|
*/
|
||||||
public final class DeckUtil {
|
public final class DeckUtil {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DeckUtil.class);
|
private static final Logger logger = Logger.getLogger(DeckUtil.class);
|
||||||
|
|
||||||
public static long fixedHash(String string) {
|
/**
|
||||||
|
* Find deck hash (main + sideboard)
|
||||||
|
* It must be same after sideboard (do not depend on main/sb structure)
|
||||||
|
*/
|
||||||
|
public static long getDeckHash(List<String> mainCards, List<String> sideboardCards) {
|
||||||
|
List<String> all = new ArrayList<>(mainCards);
|
||||||
|
all.addAll(sideboardCards);
|
||||||
|
Collections.sort(all);
|
||||||
|
return getStringHash(all.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getStringHash(String string) {
|
||||||
long h = 1125899906842597L; // prime
|
long h = 1125899906842597L; // prime
|
||||||
int len = string.length();
|
int len = string.length();
|
||||||
|
|
||||||
|
|
@ -42,4 +58,27 @@ public final class DeckUtil {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make sure it's the same deck (player do not add or remove something)
|
||||||
|
*
|
||||||
|
* @param newDeck will be clear on cheating
|
||||||
|
*/
|
||||||
|
public boolean checkDeckForModification(String checkInfo, Player player, Deck oldDeck, Deck newDeck) {
|
||||||
|
boolean isGood = (oldDeck.getDeckHash() == newDeck.getDeckHash());
|
||||||
|
if (!isGood) {
|
||||||
|
String message = String.format("Found cheating player [%s] in [%s] with changed deck, main %d -> %d, side %d -> %d",
|
||||||
|
player.getName(),
|
||||||
|
checkInfo,
|
||||||
|
oldDeck.getCards().size(),
|
||||||
|
newDeck.getCards().size(),
|
||||||
|
oldDeck.getSideboard().size(),
|
||||||
|
newDeck.getSideboard().size()
|
||||||
|
);
|
||||||
|
logger.error(message);
|
||||||
|
newDeck.getCards().clear();
|
||||||
|
newDeck.getSideboard().clear();
|
||||||
|
}
|
||||||
|
return isGood;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue