forked from External/mage
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 {
|
||||
|
||||
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 String LITE_MODE_ARG = "-lite";
|
||||
|
|
@ -398,7 +397,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
}
|
||||
|
||||
private void bootstrapSetsAndFormats() {
|
||||
logger.info("Loading sets and formats...");
|
||||
LOGGER.info("Loading sets and formats...");
|
||||
ConstructedFormats.ensureLists();
|
||||
}
|
||||
|
||||
|
|
@ -1460,7 +1459,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
try {
|
||||
instance = new MageFrame();
|
||||
} 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1861,9 +1861,9 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
|
|||
List<CardView> gridStack = new ArrayList<>();
|
||||
gridRow.add(gridStack);
|
||||
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
|
||||
= trackedCards.get(info.getSetCode()).get(info.getCardNum());
|
||||
= trackedCards.get(info.getSetCode()).get(info.getCardNumber());
|
||||
if (!candidates.isEmpty()) {
|
||||
gridStack.add(candidates.remove(0));
|
||||
thisMaxStackSize = Math.max(thisMaxStackSize, gridStack.size());
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ public class DeckGeneratorDialog {
|
|||
tmp.getParentFile().mkdirs();
|
||||
tmp.createNewFile();
|
||||
deck.setName(deckName);
|
||||
XMAGE.getExporter().writeDeck(tmp.getAbsolutePath(), deck.getDeckCardLists());
|
||||
XMAGE.getExporter().writeDeck(tmp.getAbsolutePath(), deck.prepareCardsOnlyDeck());
|
||||
cleanUp();
|
||||
return tmp.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -794,18 +794,14 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
dialog.showDialog();
|
||||
|
||||
if (!dialog.getTmpPath().isEmpty()) {
|
||||
Deck deckToAppend = null;
|
||||
StringBuilder errorMessages = new StringBuilder();
|
||||
|
||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
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);
|
||||
|
||||
if (deckToAppend != null) {
|
||||
deck = Deck.append(deckToAppend, deck);
|
||||
this.deck = Deck.append(deckToAppend, this.deck);
|
||||
refreshDeck();
|
||||
}
|
||||
} catch (GameException e1) {
|
||||
JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
|
||||
} finally {
|
||||
|
|
@ -864,7 +860,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
|
||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
try {
|
||||
DeckFormats.writeDeck(needFileName, deck.getDeckCardLists());
|
||||
DeckFormats.writeDeck(needFileName, deck.prepareCardsOnlyDeck());
|
||||
|
||||
try {
|
||||
MageFrame.getPreferences().put("lastExportFolder", file.getCanonicalPath());
|
||||
|
|
@ -1364,7 +1360,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
useDeckInfo = true;
|
||||
}
|
||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
DeckCardLists cardLists = deck.getDeckCardLists();
|
||||
DeckCardLists cardLists = deck.prepareCardsOnlyDeck();
|
||||
cardLists.setCardLayout(deckArea.getCardLayout());
|
||||
cardLists.setSideboardLayout(deckArea.getSideboardLayout());
|
||||
if (!useDeckInfo) {
|
||||
|
|
@ -1444,12 +1440,14 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
if (mode == DeckEditorMode.SIDEBOARDING
|
||||
|| mode == DeckEditorMode.LIMITED_BUILDING
|
||||
|| mode == DeckEditorMode.LIMITED_SIDEBOARD_BUILDING) {
|
||||
// in game mode - move all to sideboard
|
||||
for (Card card : deck.getCards()) {
|
||||
deck.getSideboard().add(card);
|
||||
}
|
||||
deck.getCards().clear();
|
||||
cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard);
|
||||
} else {
|
||||
// in deck editor mode - clear all cards
|
||||
deck = new Deck();
|
||||
}
|
||||
refreshDeck();
|
||||
|
|
@ -1488,7 +1486,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
updateDeckTask.cancel(true);
|
||||
}
|
||||
|
||||
if (SessionHandler.submitDeck(mode, tableId, deck.getDeckCardLists())) {
|
||||
if (SessionHandler.submitDeck(mode, tableId, deck.prepareCardsOnlyDeck())) {
|
||||
removeDeckEditor();
|
||||
}
|
||||
}//GEN-LAST:event_btnSubmitActionPerformed
|
||||
|
|
@ -1504,7 +1502,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
updateDeckTask.cancel(true);
|
||||
}
|
||||
|
||||
if (SessionHandler.submitDeck(mode, tableId, deck.getDeckCardLists())) {
|
||||
if (SessionHandler.submitDeck(mode, tableId, deck.prepareCardsOnlyDeck())) {
|
||||
SwingUtilities.invokeLater(this::removeDeckEditor);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -1617,7 +1615,7 @@ class UpdateDeckTask extends SwingWorker<Void, Void> {
|
|||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
while (!isCancelled()) {
|
||||
SessionHandler.updateDeck(tableId, deck.getDeckCardLists());
|
||||
SessionHandler.updateDeck(tableId, deck.prepareCardsOnlyDeck());
|
||||
TimeUnit.SECONDS.sleep(5);
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public class DeckExportClipboardDialog extends MageDialog {
|
|||
DeckExporter exporter = formats.get(formatIndex).getExporter();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
exporter.writeDeck(baos, deck.getDeckCardLists());
|
||||
exporter.writeDeck(baos, deck.prepareCardsOnlyDeck());
|
||||
editData.setText(baos.toString());
|
||||
editData.setCaretPosition(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ public class DeckLegalityPanel extends javax.swing.JPanel {
|
|||
} else {
|
||||
// contains mock cards, e.g. it's a Deck Editor
|
||||
try {
|
||||
deckToValidate = Deck.load(deck.getDeckCardLists(), true, false);
|
||||
deckToValidate = Deck.load(deck.prepareCardsOnlyDeck(), true, false);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Can't load real deck cards for validate: " + ex.getMessage(), ex);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -557,7 +557,7 @@ public class CustomOptionsDialog extends MageDialog {
|
|||
}
|
||||
if (perPlayerEmblemDeck != null) {
|
||||
perPlayerEmblemDeck.clearLayouts();
|
||||
options.setPerPlayerEmblemCards(perPlayerEmblemDeck.getDeckCardLists().getCards());
|
||||
options.setPerPlayerEmblemCards(perPlayerEmblemDeck.prepareCardsOnlyDeck().getCards());
|
||||
} else {
|
||||
options.setPerPlayerEmblemCards(Collections.emptySet());
|
||||
}
|
||||
|
|
@ -571,7 +571,7 @@ public class CustomOptionsDialog extends MageDialog {
|
|||
}
|
||||
if (startingPlayerEmblemDeck != null) {
|
||||
startingPlayerEmblemDeck.clearLayouts();
|
||||
options.setGlobalEmblemCards(startingPlayerEmblemDeck.getDeckCardLists().getCards());
|
||||
options.setGlobalEmblemCards(startingPlayerEmblemDeck.prepareCardsOnlyDeck().getCards());
|
||||
} else {
|
||||
options.setGlobalEmblemCards(Collections.emptySet());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -394,7 +394,6 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
String countryName = CountryUtil.getCountryName(flagName);
|
||||
basicTooltipText = "<HTML>Name: " + player.getName()
|
||||
+ "<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)";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1297,20 +1297,6 @@ public class SessionImpl implements Session {
|
|||
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
|
||||
public boolean submitDeck(UUID tableId, DeckCardLists deck) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ public class PlayerView implements Serializable {
|
|||
private final Counters counters;
|
||||
private final int wins;
|
||||
private final int winsNeeded;
|
||||
private final long deckHashCode;
|
||||
private final int libraryCount;
|
||||
private final int handCount;
|
||||
private final boolean isActive;
|
||||
|
|
@ -68,8 +67,6 @@ public class PlayerView implements Serializable {
|
|||
this.counters = player.getCounters();
|
||||
this.wins = player.getMatchPlayer().getWins();
|
||||
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.handCount = player.getHand().size();
|
||||
this.manaPool = new ManaPoolView(player.getManaPool());
|
||||
|
|
@ -206,10 +203,6 @@ public class PlayerView implements Serializable {
|
|||
return winsNeeded;
|
||||
}
|
||||
|
||||
public long getDeckHashCode() {
|
||||
return deckHashCode;
|
||||
}
|
||||
|
||||
public int getHandCount() {
|
||||
return this.handCount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
private static final int MAX_SIMULATED_NODES_PER_CALC = 5000;
|
||||
|
||||
// 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(
|
||||
COMPUTER_MAX_THREADS_FOR_SIMULATIONS,
|
||||
COMPUTER_MAX_THREADS_FOR_SIMULATIONS,
|
||||
|
|
|
|||
|
|
@ -13,15 +13,13 @@ public class CubeFromDeck extends DraftCube {
|
|||
public CubeFromDeck(Deck cubeFromDeck) {
|
||||
super("Cube From Deck");
|
||||
|
||||
DeckCardLists cards = null;
|
||||
if (cubeFromDeck != null) {
|
||||
cards = cubeFromDeck.getDeckCardLists();
|
||||
if (cubeFromDeck == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cards != null) {
|
||||
DeckCardLists cards = cubeFromDeck.prepareCardsOnlyDeck();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and join real player
|
||||
*/
|
||||
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);
|
||||
if (!_user.isPresent()) {
|
||||
logger.error("Join Table: can't find user to join " + name + " Id = " + userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// table - already joined
|
||||
User user = _user.get();
|
||||
if (userPlayerMap.containsKey(userId) && playerType == PlayerType.HUMAN) {
|
||||
user.showUserMessage("Join Table", new StringBuilder("You can join a table only one time.").toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// table - already started
|
||||
if (table.getState() != TableState.WAITING) {
|
||||
user.showUserMessage("Join Table", "No available seats.");
|
||||
return false;
|
||||
}
|
||||
// check password
|
||||
|
||||
// table - wrong password
|
||||
if (!table.getMatch().getOptions().getPassword().isEmpty() && playerType == PlayerType.HUMAN) {
|
||||
if (!table.getMatch().getOptions().getPassword().equals(password)) {
|
||||
user.showUserMessage("Join Table", "Wrong password.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// table - no more free seats
|
||||
Seat seat = table.getNextAvailableSeat(playerType);
|
||||
if (seat == null) {
|
||||
user.showUserMessage("Join Table", "No available seats.");
|
||||
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 - validate
|
||||
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");
|
||||
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!");
|
||||
user.showUserMessage("Join Table", sb.toString());
|
||||
|
||||
// owner must create table with valid deck only
|
||||
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());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Check quit ratio.
|
||||
|
||||
// user - restrict by quit ratio
|
||||
int quitRatio = table.getMatch().getOptions().getQuitRatio();
|
||||
if (quitRatio < user.getMatchQuitRatio()) {
|
||||
String message = new StringBuilder("Your quit ratio ").append(user.getMatchQuitRatio())
|
||||
|
|
@ -288,7 +307,7 @@ public class TableController {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check minimum rating.
|
||||
// user - restrict by rating
|
||||
int minimumRating = table.getMatch().getOptions().getMinimumRating();
|
||||
int userRating;
|
||||
if (table.getMatch().getOptions().isLimited()) {
|
||||
|
|
@ -303,7 +322,7 @@ public class TableController {
|
|||
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();
|
||||
if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase(Locale.ENGLISH).equals("commander")) {
|
||||
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);
|
||||
if (!playerOpt.isPresent()) {
|
||||
String message = "Could not create player " + name + " of type " + seat.getPlayerType();
|
||||
|
|
@ -356,14 +376,18 @@ public class TableController {
|
|||
user.showUserMessage("Join Table", message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// player - restrict by player type, e.g. draft bot can join to tourney only
|
||||
Player player = playerOpt.get();
|
||||
if (!player.canJoinTable(table)) {
|
||||
user.showUserMessage("Join Table", "A " + seat.getPlayerType() + " player can't join this table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// all fine, player can be added
|
||||
match.addPlayer(player, deck);
|
||||
table.joinTable(player, seat);
|
||||
logger.trace(player.getName() + " joined tableId: " + table.getId());
|
||||
|
||||
//only inform human players and add them to sessionPlayerMap
|
||||
if (seat.getPlayer().isHuman()) {
|
||||
seat.getPlayer().setUserData(user.getUserData());
|
||||
|
|
@ -373,6 +397,7 @@ public class TableController {
|
|||
user.ccJoinedTable(table.getRoomId(), table.getId(), false);
|
||||
userPlayerMap.put(userId, player.getId());
|
||||
}
|
||||
|
||||
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 {
|
||||
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()) {
|
||||
TournamentPlayer player = tournament.getPlayer(playerId);
|
||||
if (player == null || player.hasQuit()) {
|
||||
|
|
@ -403,23 +442,26 @@ public class TableController {
|
|||
if (mPlayer == null || mPlayer.hasQuit()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
if (table.getState() != TableState.SIDEBOARDING && table.getState() != TableState.CONSTRUCTING) {
|
||||
|
||||
// tourney - too late for submit
|
||||
if (table.getState() != TableState.SIDEBOARDING
|
||||
&& table.getState() != TableState.CONSTRUCTING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// deck - try to load real deck (any unknown cards will raise exception error here)
|
||||
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) {
|
||||
MatchPlayer mPlayer = table.getMatch().getPlayer(playerId);
|
||||
if (mPlayer != null) {
|
||||
deck.setName(mPlayer.getDeck().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// deck - validate
|
||||
if (!Main.isTestMode() && !table.getValidator().validate(deck)) {
|
||||
Optional<User> _user = managerFactory.userManager().getUser(userId);
|
||||
if (!_user.isPresent()) {
|
||||
|
|
@ -434,21 +476,20 @@ public class TableController {
|
|||
_user.get().showUserMessage("Submit deck", sb.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
submitDeck(userId, playerId, deck);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateDeck(UUID userId, DeckCardLists deckList) throws MageException {
|
||||
boolean validDeck;
|
||||
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;
|
||||
}
|
||||
Deck deck = Deck.load(deckList, false, false);
|
||||
validDeck = updateDeck(userId, playerId, deck);
|
||||
if (!validDeck && getTableState() == TableState.SIDEBOARDING) {
|
||||
logger.warn(" userId: " + userId + " - Modified deck card list!");
|
||||
}
|
||||
updateDeck(userId, playerId, 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) {
|
||||
boolean validDeck = true;
|
||||
private void updateDeck(UUID userId, UUID playerId, Deck deck) {
|
||||
if (table.isTournament()) {
|
||||
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 {
|
||||
logger.fatal("Tournament == null table: " + table.getId() + " userId: " + userId);
|
||||
}
|
||||
} else if (TableState.SIDEBOARDING == table.getState()) {
|
||||
validDeck = match.updateDeck(playerId, deck);
|
||||
} else if (table.getState() == TableState.SIDEBOARDING) {
|
||||
match.updateDeck(playerId, deck);
|
||||
} else {
|
||||
// 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) {
|
||||
|
|
@ -538,6 +580,7 @@ public class TableController {
|
|||
} else {
|
||||
UUID playerId = userPlayerMap.get(userId);
|
||||
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) {
|
||||
table.leaveNotStartedTable(playerId);
|
||||
if (table.isTournament()) {
|
||||
|
|
@ -884,7 +927,7 @@ public class TableController {
|
|||
private void autoSideboard() {
|
||||
for (MatchPlayer player : match.getPlayers()) {
|
||||
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);
|
||||
|
||||
boolean updateDeck(UUID tournamentId, UUID playerId, Deck deck);
|
||||
void updateDeck(UUID tournamentId, UUID playerId, Deck deck);
|
||||
|
||||
TournamentView getTournamentView(UUID tournamentId);
|
||||
|
||||
|
|
|
|||
|
|
@ -324,11 +324,13 @@ public class TournamentController {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
||||
if (tournamentSessions.containsKey(playerId)) {
|
||||
return tournamentSessions.get(playerId).updateDeck(deck);
|
||||
public void updateDeck(UUID playerId, Deck deck) {
|
||||
TournamentSession session = tournamentSessions.getOrDefault(playerId, null);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
|
||||
session.updateDeck(deck);
|
||||
}
|
||||
|
||||
public void timeout(UUID userId) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentMap;
|
|||
*/
|
||||
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 ConcurrentMap<UUID, TournamentController> controllers = new ConcurrentHashMap<>();
|
||||
|
||||
|
|
@ -61,8 +62,8 @@ public class TournamentManagerImpl implements TournamentManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeck(UUID tournamentId, UUID playerId, Deck deck) {
|
||||
return controllers.get(tournamentId).updateDeck(playerId, deck);
|
||||
public void updateDeck(UUID tournamentId, UUID playerId, Deck deck) {
|
||||
controllers.get(tournamentId).updateDeck(playerId, deck);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ public class TournamentSession {
|
|||
tournament.submitDeck(playerId, deck);
|
||||
}
|
||||
|
||||
public boolean updateDeck(Deck deck) {
|
||||
return tournament.updateDeck(playerId, deck);
|
||||
public void updateDeck(Deck deck) {
|
||||
tournament.updateDeck(playerId, deck);
|
||||
}
|
||||
|
||||
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
|
||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
||||
return true;
|
||||
public void updateDeck(UUID playerId, Deck deck) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,28 +1,34 @@
|
|||
package mage.cards.decks;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.repository.CardInfo;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.game.GameException;
|
||||
import mage.util.Copyable;
|
||||
import mage.util.DeckUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
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> sideboard = new LinkedHashSet<>();
|
||||
private DeckCardLayout cardsLayout;
|
||||
private DeckCardLayout sideboardLayout;
|
||||
private long deckHashCode = 0;
|
||||
private long deckCompleteHashCode = 0;
|
||||
private DeckCardLayout cardsLayout; // client side only
|
||||
private DeckCardLayout sideboardLayout; // client side only
|
||||
|
||||
public Deck() {
|
||||
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.cardsLayout = deck.cardsLayout == null ? null : deck.cardsLayout.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 {
|
||||
|
|
@ -46,27 +50,15 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
return Deck.load(deckCardLists, ignoreErrors, true);
|
||||
}
|
||||
|
||||
public static Deck append(Deck deckToAppend, Deck currentDeck) throws GameException {
|
||||
List<String> deckCardNames = new ArrayList<>();
|
||||
|
||||
for (Card card : deckToAppend.getCards()) {
|
||||
if (card != null) {
|
||||
currentDeck.cards.add(card);
|
||||
deckCardNames.add(card.getName());
|
||||
}
|
||||
}
|
||||
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 append(Deck sourceDeck, Deck currentDeck) throws GameException {
|
||||
Deck newDeck = currentDeck.copy();
|
||||
sourceDeck.getCards().forEach(card -> {
|
||||
newDeck.cards.add(card.copy());
|
||||
});
|
||||
sourceDeck.getSideboard().forEach(card -> {
|
||||
newDeck.sideboard.add(card.copy());
|
||||
});
|
||||
return newDeck;
|
||||
}
|
||||
|
||||
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.cardsLayout = deckCardLists.getCardLayout() == null ? null : deckCardLists.getCardLayout().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) {
|
||||
throw createCardNotFoundGameException(deckCardInfo, deckCardLists.getName());
|
||||
// load only real cards
|
||||
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);
|
||||
if (card != null) {
|
||||
if (totalCards > MAX_CARDS_PER_DECK) {
|
||||
break;
|
||||
}
|
||||
deck.sideboard.add(card);
|
||||
sbCardNames.add(card.getName());
|
||||
deck.cards.add(card);
|
||||
totalCards++;
|
||||
} else if (!ignoreErrors) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +137,7 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
String cardError = String.format("Card not found - %s - %s - %s in deck %s.",
|
||||
deckCardInfo.getCardName(),
|
||||
deckCardInfo.getSetCode(),
|
||||
deckCardInfo.getCardNum(),
|
||||
deckCardInfo.getCardNumber(),
|
||||
deckName
|
||||
);
|
||||
cardError += "\n\nPossible reasons:";
|
||||
|
|
@ -168,15 +156,15 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
CardInfo cardInfo;
|
||||
if (cardInfoCache != null) {
|
||||
// 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);
|
||||
if (cardInfo == null) {
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||
cardInfoCache.put(key, cardInfo);
|
||||
}
|
||||
} else {
|
||||
// from db
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum());
|
||||
cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNumber());
|
||||
}
|
||||
|
||||
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.setName(name);
|
||||
|
||||
for (Card card : cards) {
|
||||
deckCardLists.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
||||
}
|
||||
|
||||
for (Card card : sideboard) {
|
||||
deckCardLists.getSideboard().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode()));
|
||||
}
|
||||
|
|
@ -227,6 +220,7 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
return cards;
|
||||
}
|
||||
|
||||
// TODO: delete and replace by getCards()
|
||||
public Set<Card> getMaindeckCards() {
|
||||
return cards
|
||||
.stream()
|
||||
|
|
@ -262,22 +256,6 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
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() {
|
||||
this.cardsLayout = null;
|
||||
this.sideboardLayout = null;
|
||||
|
|
@ -287,4 +265,11 @@ public class Deck implements Serializable, Copyable<Deck> {
|
|||
public Deck copy() {
|
||||
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;
|
||||
|
||||
import mage.util.Copyable;
|
||||
|
|
@ -7,14 +5,18 @@ import mage.util.Copyable;
|
|||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Client side: card record in deck file
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
||||
|
||||
static final int MAX_AMOUNT_PER_CARD = 99;
|
||||
|
||||
private String cardName;
|
||||
private String setCode;
|
||||
private String cardNum;
|
||||
private int quantity;
|
||||
private String cardNumber;
|
||||
private int amount;
|
||||
|
||||
public DeckCardInfo() {
|
||||
super();
|
||||
|
|
@ -23,19 +25,29 @@ public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
|||
protected DeckCardInfo(final DeckCardInfo info) {
|
||||
this.cardName = info.cardName;
|
||||
this.setCode = info.setCode;
|
||||
this.cardNum = info.cardNum;
|
||||
this.quantity = info.quantity;
|
||||
this.cardNumber = info.cardNumber;
|
||||
this.amount = info.amount;
|
||||
}
|
||||
|
||||
public DeckCardInfo(String cardName, String cardNum, String setCode) {
|
||||
this(cardName, cardNum, setCode, 1);
|
||||
public DeckCardInfo(String cardName, String cardNumber, String setCode) {
|
||||
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.cardNum = cardNum;
|
||||
this.cardNumber = cardNumber;
|
||||
this.setCode = setCode;
|
||||
this.quantity = quantity;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getCardName() {
|
||||
|
|
@ -46,21 +58,16 @@ public class DeckCardInfo implements Serializable, Copyable<DeckCardInfo> {
|
|||
return setCode;
|
||||
}
|
||||
|
||||
public String getCardNum() {
|
||||
return cardNum;
|
||||
public String getCardNumber() {
|
||||
return cardNumber;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public DeckCardInfo increaseQuantity() {
|
||||
quantity++;
|
||||
return this;
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String getCardKey() {
|
||||
return setCode + cardNum;
|
||||
return setCode + cardNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import mage.util.Copyable;
|
|||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
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
|
||||
*/
|
||||
|
|
@ -46,12 +48,15 @@ public class DeckCardLists implements Serializable, Copyable<DeckCardLists> {
|
|||
public DeckCardLayout getCardLayout() {
|
||||
return cardLayout;
|
||||
}
|
||||
|
||||
public void setCardLayout(DeckCardLayout layout) {
|
||||
this.cardLayout = layout;
|
||||
}
|
||||
|
||||
public DeckCardLayout getSideboardLayout() {
|
||||
return sideboardLayout;
|
||||
}
|
||||
|
||||
public void setSideboardLayout(DeckCardLayout layout) {
|
||||
this.sideboardLayout = layout;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ public enum DeckFormats {
|
|||
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 {
|
||||
writeDeck(new File(file), deck);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ public enum ExpansionRepository {
|
|||
instanceInitialized = true;
|
||||
|
||||
eventSource.fireRepositoryDbLoaded();
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (SQLException e) {
|
||||
// TODO: add app close?
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import mage.cards.repository.CardRepository;
|
|||
import mage.constants.Zone;
|
||||
import mage.game.command.Emblem;
|
||||
import mage.util.CardUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -50,7 +49,7 @@ public final class EmblemOfCard extends Emblem {
|
|||
public static Card cardFromDeckInfo(DeckCardInfo info) {
|
||||
return lookupCard(
|
||||
info.getCardName(),
|
||||
info.getCardNum(),
|
||||
info.getCardNumber(),
|
||||
info.getSetCode(),
|
||||
"DeckCardInfo"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public interface Match {
|
|||
|
||||
void submitDeck(UUID playerId, Deck deck);
|
||||
|
||||
boolean updateDeck(UUID playerId, Deck deck);
|
||||
void updateDeck(UUID playerId, Deck deck);
|
||||
|
||||
void startMatch();
|
||||
|
||||
|
|
|
|||
|
|
@ -379,9 +379,8 @@ public abstract class MatchImpl implements Match {
|
|||
public void submitDeck(UUID playerId, Deck deck) {
|
||||
MatchPlayer player = getPlayer(playerId);
|
||||
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.setDeckHashCode(player.getDeck().getDeckHashCode());
|
||||
player.submitDeck(deck);
|
||||
}
|
||||
synchronized (this) {
|
||||
|
|
@ -390,21 +389,15 @@ public abstract class MatchImpl implements Match {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
||||
boolean validDeck = true;
|
||||
public void updateDeck(UUID playerId, Deck deck) {
|
||||
// used for auto-save deck
|
||||
MatchPlayer player = getPlayer(playerId);
|
||||
if (player != null) {
|
||||
// Check if the cards included in the deck are the same as in the original deck
|
||||
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();
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.updateDeck(deck);
|
||||
}
|
||||
return validDeck;
|
||||
}
|
||||
|
||||
protected String createGameStartMessage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
|
@ -416,9 +409,6 @@ public abstract class MatchImpl implements Match {
|
|||
sb.append(" QUITTED");
|
||||
}
|
||||
sb.append("<br/>");
|
||||
if (mp.getDeck() != null) {
|
||||
sb.append("DeckHash: ").append(mp.getDeck().getDeckHashCode()).append("<br/>");
|
||||
}
|
||||
}
|
||||
if (getDraws() > 0) {
|
||||
sb.append(" Draws: ").append(getDraws()).append("<br/>");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mage.cards.Card;
|
|||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckValidator;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
@ -12,13 +13,15 @@ import java.io.Serializable;
|
|||
*/
|
||||
public class MatchPlayer implements Serializable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MatchPlayer.class);
|
||||
|
||||
private int wins;
|
||||
private int winsNeeded;
|
||||
private boolean matchWinner;
|
||||
|
||||
private Deck deck;
|
||||
private Player player;
|
||||
private String name;
|
||||
private final Player player;
|
||||
private final String name;
|
||||
|
||||
private boolean quit;
|
||||
private boolean doneSideboarding;
|
||||
|
|
@ -89,22 +92,33 @@ public class MatchPlayer implements Serializable {
|
|||
return viewerDeck;
|
||||
}
|
||||
|
||||
public void submitDeck(Deck deck) {
|
||||
this.deck = deck;
|
||||
public void submitDeck(Deck newDeck) {
|
||||
this.deck = newDeck;
|
||||
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) {
|
||||
// preserver deck name, important for Tiny Leaders format
|
||||
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;
|
||||
newDeck.setName(this.getDeck().getName());
|
||||
}
|
||||
|
||||
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
|
||||
while (deck.getMaindeckCards().size() < deckValidator.getDeckMinSize() && !deck.getSideboard().isEmpty()) {
|
||||
Card card = deck.getSideboard().iterator().next();
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public interface Tournament {
|
|||
|
||||
void submitDeck(UUID playerId, Deck deck);
|
||||
|
||||
boolean updateDeck(UUID playerId, Deck deck);
|
||||
void updateDeck(UUID playerId, Deck deck);
|
||||
|
||||
void autoSubmit(UUID playerId, Deck deck);
|
||||
|
||||
|
|
|
|||
|
|
@ -139,11 +139,13 @@ public abstract class TournamentImpl implements Tournament {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeck(UUID playerId, Deck deck) {
|
||||
if (players.containsKey(playerId)) {
|
||||
return players.get(playerId).updateDeck(deck);
|
||||
public void updateDeck(UUID playerId, Deck deck) {
|
||||
TournamentPlayer player = players.getOrDefault(playerId, null);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
|
||||
player.updateDeck(deck);
|
||||
}
|
||||
|
||||
protected Round createRoundRandom() {
|
||||
|
|
|
|||
|
|
@ -93,18 +93,18 @@ public class TournamentPlayer {
|
|||
}
|
||||
|
||||
public boolean updateDeck(Deck deck) {
|
||||
// Check if the cards included in the deck are the same as in the original deck
|
||||
boolean validDeck = (getDeck().getDeckCompleteHashCode() == deck.getDeckCompleteHashCode());
|
||||
if (!validDeck) {
|
||||
// Clear the deck so the player cheating looses the game
|
||||
// TODO: inform other players about cheating?!
|
||||
logger.error("Found cheating player " + getPlayer().getName()
|
||||
// used for auto-save deck
|
||||
|
||||
// make sure it's the same deck (player do not add or remove something)
|
||||
boolean isGood = (this.getDeck().getDeckHash() == deck.getDeckHash());
|
||||
if (!isGood) {
|
||||
logger.error("Found cheating tourney player " + player.getName()
|
||||
+ " with changed deck, main " + deck.getCards().size() + ", side " + deck.getSideboard().size());
|
||||
deck.getCards().clear();
|
||||
deck.getSideboard().clear();
|
||||
}
|
||||
this.deck = deck;
|
||||
return validDeck;
|
||||
return isGood;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,20 +1,36 @@
|
|||
package mage.util;
|
||||
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
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 {
|
||||
|
||||
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
|
||||
int len = string.length();
|
||||
|
||||
|
|
@ -42,4 +58,27 @@ public final class DeckUtil {
|
|||
}
|
||||
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