diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form index e35b22b89b..afaf19627e 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -82,7 +82,7 @@ - + @@ -159,7 +159,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -333,16 +333,23 @@ - + - + + + + - + + + + + @@ -357,6 +364,16 @@ + + + + + + + + + + @@ -512,7 +529,7 @@ - + @@ -1155,7 +1172,7 @@ - + @@ -1606,7 +1623,7 @@ - + @@ -1819,7 +1836,7 @@ - + diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index bd764f48ce..ec880ce8f4 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -91,6 +91,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_GAME_ASK_MOVE_TO_GRAVE_ORDER = "gameAskMoveToGraveORder"; public static final String KEY_GAME_LOG_AUTO_SAVE = "gameLogAutoSave"; + public static final String KEY_DRAFT_LOG_AUTO_SAVE = "draftLogAutoSave"; public static final String KEY_CARD_IMAGES_USE_DEFAULT = "cardImagesUseDefault"; public static final String KEY_CARD_IMAGES_PATH = "cardImagesPath"; @@ -362,6 +363,7 @@ public class PreferencesDialog extends javax.swing.JDialog { cbAskMoveToGraveOrder = new javax.swing.JCheckBox(); main_gamelog = new javax.swing.JPanel(); cbGameLogAutoSave = new javax.swing.JCheckBox(); + cbDraftLogAutoSave = new javax.swing.JCheckBox(); tabPhases = new javax.swing.JPanel(); jLabelHeadLine = new javax.swing.JLabel(); jLabelYourTurn = new javax.swing.JLabel(); @@ -643,18 +645,32 @@ public class PreferencesDialog extends javax.swing.JDialog { } }); + cbDraftLogAutoSave.setSelected(true); + cbDraftLogAutoSave.setText("Auto save draft logs (to \"../Mage.Client/gamelogs/\" directory)"); + cbDraftLogAutoSave.setToolTipText("The logs of all your games will be saved to the mentioned folder if this option is switched on."); + cbDraftLogAutoSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cbDraftLogAutoSaveActionPerformed(evt); + } + }); + javax.swing.GroupLayout main_gamelogLayout = new javax.swing.GroupLayout(main_gamelog); main_gamelog.setLayout(main_gamelogLayout); main_gamelogLayout.setHorizontalGroup( main_gamelogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(main_gamelogLayout.createSequentialGroup() .addContainerGap() - .addComponent(cbGameLogAutoSave, javax.swing.GroupLayout.PREFERRED_SIZE, 528, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(main_gamelogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(cbGameLogAutoSave, javax.swing.GroupLayout.PREFERRED_SIZE, 528, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cbDraftLogAutoSave, javax.swing.GroupLayout.PREFERRED_SIZE, 528, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); main_gamelogLayout.setVerticalGroup( main_gamelogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(cbGameLogAutoSave, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(main_gamelogLayout.createSequentialGroup() + .addComponent(cbGameLogAutoSave) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cbDraftLogAutoSave)) ); javax.swing.GroupLayout tabMainLayout = new javax.swing.GroupLayout(tabMain); @@ -675,7 +691,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .addContainerGap() .addComponent(main_card, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(main_game, javax.swing.GroupLayout.PREFERRED_SIZE, 189, Short.MAX_VALUE) + .addComponent(main_game, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(main_gamelog, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) @@ -861,7 +877,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .addComponent(checkBoxEndTurnOthers)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(phases_stopSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(13, Short.MAX_VALUE)) + .addContainerGap(45, Short.MAX_VALUE)) ); tabsPanel.addTab("Phases", tabPhases); @@ -1435,7 +1451,7 @@ public class PreferencesDialog extends javax.swing.JDialog { ); tabAvatarsLayout.setVerticalGroup( tabAvatarsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(avatarPane, javax.swing.GroupLayout.DEFAULT_SIZE, 359, Short.MAX_VALUE) + .addComponent(avatarPane, javax.swing.GroupLayout.DEFAULT_SIZE, 432, Short.MAX_VALUE) ); tabsPanel.addTab("Avatars", tabAvatars); @@ -1585,7 +1601,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .addGroup(connection_serversLayout.createSequentialGroup() .addGroup(connection_serversLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(lblURLServerList, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(txtURLServerList, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(txtURLServerList, javax.swing.GroupLayout.DEFAULT_SIZE, 28, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel17)) ); @@ -1617,7 +1633,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .addComponent(cbProxyType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addComponent(pnlProxySettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(54, Short.MAX_VALUE)) + .addContainerGap(90, Short.MAX_VALUE)) ); pnlProxySettings.getAccessibleContext().setAccessibleDescription(""); @@ -1681,6 +1697,7 @@ public class PreferencesDialog extends javax.swing.JDialog { save(prefs, dialog.cbConfirmEmptyManaPool, KEY_GAME_CONFIRM_EMPTY_MANA_POOL, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbAskMoveToGraveOrder, KEY_GAME_ASK_MOVE_TO_GRAVE_ORDER, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbGameLogAutoSave, KEY_GAME_LOG_AUTO_SAVE, "true", "false", UPDATE_CACHE_POLICY); + save(prefs, dialog.cbDraftLogAutoSave, KEY_DRAFT_LOG_AUTO_SAVE, "true", "false", UPDATE_CACHE_POLICY); // Phases save(prefs, dialog.checkBoxUpkeepYou, UPKEEP_YOU); @@ -2001,6 +2018,10 @@ public class PreferencesDialog extends javax.swing.JDialog { // TODO add your handling code here: }//GEN-LAST:event_cbAskMoveToGraveOrderActionPerformed + private void cbDraftLogAutoSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDraftLogAutoSaveActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cbDraftLogAutoSaveActionPerformed + private void showProxySettings() { if (cbProxyType.getSelectedItem() == Connection.ProxyType.SOCKS) { this.pnlProxy.setVisible(true); @@ -2079,6 +2100,7 @@ public class PreferencesDialog extends javax.swing.JDialog { load(prefs, dialog.cbAskMoveToGraveOrder, KEY_GAME_ASK_MOVE_TO_GRAVE_ORDER, "true"); load(prefs, dialog.cbGameLogAutoSave, KEY_GAME_LOG_AUTO_SAVE, "true"); + load(prefs, dialog.cbDraftLogAutoSave, KEY_DRAFT_LOG_AUTO_SAVE, "true"); load(prefs, dialog.checkBoxUpkeepYou, UPKEEP_YOU, "on", "on"); load(prefs, dialog.checkBoxDrawYou, DRAW_YOU, "on", "on"); @@ -2449,6 +2471,7 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JCheckBox cbAskMoveToGraveOrder; private javax.swing.JCheckBox cbCheckForNewImages; private javax.swing.JCheckBox cbConfirmEmptyManaPool; + private javax.swing.JCheckBox cbDraftLogAutoSave; private javax.swing.JCheckBox cbEnableBattlefieldBGM; private javax.swing.JCheckBox cbEnableDraftSounds; private javax.swing.JCheckBox cbEnableGameSounds; diff --git a/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java b/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java index da4ea2b425..99645ecaed 100644 --- a/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java +++ b/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java @@ -41,10 +41,21 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; import java.util.HashSet; +import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JComponent; @@ -53,9 +64,12 @@ import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.Timer; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; import mage.client.MageFrame; import mage.client.components.tray.MageTray; import mage.client.deckeditor.SortSettingDraft; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.CardsViewUtil; import mage.client.util.Event; @@ -94,6 +108,19 @@ public class DraftPanel extends javax.swing.JPanel { // id of card with popup menu protected UUID cardIdPopupMenu; + // Filename for the draft log (only updated if writing the log). + private String logFilename; + + // Number of the current booster (for draft log writing). + private int packNo; + + // Number of the current card pick (for draft log writing). + private int pickNo; + + // Cached booster data to be written into the log (see logLastPick). + private String currentBoosterHeader; + private String[] currentBooster; + private static final CardsView emptyView = new CardsView(); /** Creates new form DraftPanel */ @@ -149,6 +176,20 @@ public class DraftPanel extends javax.swing.JPanel { if (!session.joinDraft(draftId)) { hideDraft(); } + + if (isLogging()) { + // If we are logging the draft create a file that will contain + // the log. + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss"); + logFilename = "Draft_" + sdf.format(new Date()) + "_" + draftId + ".txt"; + try { + Files.write(pathToDraftLog(), "".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException ex) { + Logger.getLogger(DraftPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } else { + logFilename = null; + } } public void updateDraft(DraftView draftView) { @@ -167,6 +208,9 @@ public class DraftPanel extends javax.swing.JPanel { this.chkPack3.setSelected(draftView.getBoosterNum() > 2); this.txtCardNo.setText(Integer.toString(draftView.getCardNum())); + packNo = draftView.getBoosterNum(); + pickNo = draftView.getCardNum(); + int right = draftView.getPlayers().size() / 2; int left = draftView.getPlayers().size() - right; int height = left * 18; @@ -247,6 +291,7 @@ public class DraftPanel extends javax.swing.JPanel { } public void loadBooster(DraftPickView draftPickView) { + logLastPick(draftPickView); // upper area that shows the picks loadCardsToPickedCardsArea(draftPickView.getPicks()); @@ -413,6 +458,94 @@ public class DraftPanel extends javax.swing.JPanel { draftPicks.loadCards(CardsViewUtil.convertSimple(pickedCardsShown), bigCard, null); } + // Log the last card picked into the draft log together with booster + // contents. + // We don't get any event when the card is selected due to timeout + // that's why instead of proactively logging our pick we instead + // log *last* pick from the list of picks. + // To make this possible we cache the list of cards from the + // previous booster and it's sequence number (pack number / pick number) + // in fields currentBooster and currentBoosterHeader. + private void logLastPick(DraftPickView pickView) { + if (!isLogging()) { + return; + } + if (currentBooster != null) { + String lastPick = getCardName(getLastPick(pickView.getPicks().values())); + if (lastPick != null) { + logPick(lastPick); + } + currentBooster = null; + } + setCurrentBoosterForLog(pickView.getBooster()); + if (currentBooster.length == 1) { + logPick(currentBooster[0]); + } + } + + private static boolean isLogging() { + String autoSave = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DRAFT_LOG_AUTO_SAVE, "true"); + return autoSave.equals("true"); + } + + private void setCurrentBoosterForLog(SimpleCardsView booster) { + LinkedList cards = new LinkedList<>(); + for (SimpleCardView simple: booster.values()) { + String cardName = getCardName(simple); + if (cardName != null) { + cards.add(cardName); + } + } + + currentBoosterHeader = "Pack " + packNo + " pick " + pickNo + ":\n"; + currentBooster = cards.toArray(new String[cards.size()]); + } + + private void logPick(String pick) { + StringBuilder b = new StringBuilder(); + b.append(currentBoosterHeader); + for (String name : currentBooster) { + b.append(pick.equals(name) ? "--> " : " "); + b.append(name); + b.append('\n'); + } + b.append('\n'); + appendToDraftLog(b.toString()); + } + + private Path pathToDraftLog() { + File saveDir = new File("gamelogs"); + if(!saveDir.exists()) { + saveDir.mkdirs(); + } + return new File(saveDir, logFilename).toPath(); + } + + private void appendToDraftLog(String data) { + try { + Files.write(pathToDraftLog(), data.getBytes(), StandardOpenOption.APPEND); + } catch (IOException ex) { + Logger.getLogger(DraftPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private static SimpleCardView getLastPick(Collection picks) { + SimpleCardView last = null; + for (SimpleCardView pick : picks) { + last = pick; + } + return last; + } + + private static String getCardName(SimpleCardView card) { + if (card == null) { + return null; + } + CardInfo cardInfo = CardRepository.instance.findCard(card.getExpansionSetCode(), card.getCardNumber()); + return cardInfo != null ? cardInfo.getName() : null; + } + + /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is