Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: separate gif game sumary from minimap game summary #6602

Merged
merged 4 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions megamek/i18n/megamek/client/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -902,12 +902,14 @@ ClientGUI.BotCommand=Bot Command
ClientGUI.clientTitleSuffix=\ - MegaMek
ClientGUI.dialogMovementReport=Movement Report
ClientGUI.dialogTacticalGeniusReport=Tactical Genius Report
ClientGUI.descriptionGIFFiles=GIF (Graphics Interchange Format)
ClientGUI.descriptionMULFiles=Mul Files
ClientGUI.Disconnected.message=You have become disconnected from the server.
ClientGUI.Disconnected.title=Disconnected\!
ClientGUI.distance=distance:
ClientGUI.errorLoadingFile=Error Loading File
ClientGUI.errorSavingFile=Error Saving File
ClientGUI.errorSavingFileGifMessage=MegaMek was unable to save {0}
ClientGUI.errorSelectingPlayer=Error selecting player
ClientGUI.failedToLoadAudioFile=Failed to load audio file named
ClientGUI.FatalError.message=Could not initialise:\n
Expand All @@ -933,8 +935,11 @@ ClientGUI.openUnitListFileDialog.noReinforceTitle=Must have a team assigned!
ClientGUI.openUnitListFileDialog.noReinforceMessage=Players must have an assigned team in order to reinforce units!\nObservers may join a team using the /joinTeam command.\nFor more information, use /help joinTeam.
ClientGUI.openUnitListFileDialog.title=Open Unit List File
ClientGUI.saveUnitListFileDialog.title=Save Unit List As
ClientGUI.saveGameSummaryGifFileDialog.title=Save Game Summary GIF As
ClientGUI.SaveUnitsDialog.message=Do you want to save a record of all units\n(including salvage) to a file?
ClientGUI.SaveUnitsDialog.title=Save Units?
ClientGUI.SaveGifDialog.title=Save GIF?
ClientGUI.SaveGifDialog.message=Do you want to save a GIF of the game summary?
ClientGUI.selectMenuItem=Select\u0020
ClientGUI.skinningHelpPath=docs/help/en/skinning/skinningHowTo.html
ClientGUI.skinningHelpPath.title=How To: Skinning
Expand Down Expand Up @@ -1284,6 +1289,8 @@ CommonSettingsDialog.showUnitDisplayNamesOnMinimap.name=Show unit display name o
CommonSettingsDialog.showUnitDisplayNamesOnMinimap.tooltip=Useful when you want to persist the game summary with the unit names. Helps to navigate the targets at a glance on a large map.
CommonSettingsDialog.gameSummaryMM.name=Save game summary image of minimap
CommonSettingsDialog.gameSummaryMM.tooltip=At the end of certain phases, save an image of the minimap to {0}, creating a visual summary of the progression of the game.
CommonSettingsDialog.gifGameSummaryMM.name=Save game summary GIF of minimap
CommonSettingsDialog.gifGameSummaryMM.tooltip=At the end of certain phases, a still image of the minimap will be turned into a frame of a GIF, which will be saved to {0}.
CommonSettingsDialog.generateNames=Generate a random name for the pilots of new units.
CommonSettingsDialog.getFocus=Get Focus when a new phase begins.
CommonSettingsDialog.guiScale=GUI Scale
Expand Down
84 changes: 84 additions & 0 deletions megamek/src/megamek/client/ui/swing/ClientGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
import megamek.logging.MMLogger;
import org.apache.commons.lang3.SystemUtils;

import static megamek.common.Configuration.gameSummaryImagesMMDir;

public class ClientGUI extends AbstractClientGUI implements BoardViewListener,
ActionListener, IPreferenceChangeListener, MekDisplayListener, ILocalBots, IDisconnectSilently, IHasUnitDisplay, IHasBoardView, IHasMenuBar, IHasCurrentPanel {
private final static MMLogger logger = MMLogger.create(ClientGUI.class);
Expand Down Expand Up @@ -235,6 +237,8 @@ public class ClientGUI extends AbstractClientGUI implements BoardViewListener,
public static final String CG_FILEEXTENTIONMUL = ".mul";
public static final String CG_FILEEXTENTIONXML = ".xml";
public static final String CG_FILEEXTENTIONPNG = ".png";
public static final String CG_FILEEXTENTIONGIF = ".gif";
public static final String CG_FILEPATHGIF = "gif";
public static final String CG_FILEFORMATNAMEPNG = "png";

// a frame, to show stuff in
Expand Down Expand Up @@ -290,6 +294,7 @@ public class ClientGUI extends AbstractClientGUI implements BoardViewListener,
*/
private JFileChooser dlgLoadList;
private JFileChooser dlgSaveList;
private JFileChooser dlgSaveGifList;
private final Client client;

private File curfileBoardImage;
Expand Down Expand Up @@ -2255,6 +2260,68 @@ public void printList(ArrayList<Entity> unitList, JButton button) {
}
}

private void saveGifGameSummary() {
String filename = StringUtil.addDateTimeStamp(client.getLocalPlayer().getName());
String gameUuid = client.getGame().getUUIDString();
File tempGifFile = new File(gameSummaryImagesMMDir(), gameUuid + CG_FILEEXTENTIONGIF);

// Build the "save gif" dialog, if necessary.
if (dlgSaveGifList == null) {
dlgSaveGifList = new JFileChooser(".");
dlgSaveGifList.setLocation(frame.getLocation().x + 150, frame.getLocation().y + 100);
dlgSaveGifList.setDialogTitle(Messages.getString("ClientGUI.saveGameSummaryGifFileDialog.title"));
FileNameExtensionFilter filter = new FileNameExtensionFilter(
Messages.getString("ClientGUI.descriptionGIFFiles"), CG_FILEPATHGIF);
dlgSaveGifList.setFileFilter(filter);
}

dlgSaveGifList.setSelectedFile(new File(filename + CG_FILEEXTENTIONGIF));

int returnVal = dlgSaveGifList.showSaveDialog(frame);
if ((returnVal != JFileChooser.APPROVE_OPTION) || (dlgSaveGifList.getSelectedFile() == null)) {
// without a file there is no saving for the file, which them means we can't save the gif
// and instead we delete it
if (tempGifFile.delete()) {
logger.info("Game summary GIF deleted");
} else {
logger.error("Failed to delete game summary GIF");
}
return;
}

// Did the player select a file?
File gifFile = dlgSaveGifList.getSelectedFile();
if (gifFile != null) {
if (!gifFile.getName().toLowerCase().endsWith(CG_FILEEXTENTIONGIF)) {
try {
gifFile = new File(gifFile.getCanonicalPath() + CG_FILEEXTENTIONGIF);
} catch (Exception ignored) {
// without a file there is no saving for the file, which them means we can't save the gif
// and instead we delete it
if (tempGifFile.delete()) {
logger.info("Game summary GIF deleted");
} else {
logger.error("Failed to delete game summary GIF");
}
return;
}
}

try {
if (tempGifFile.renameTo(gifFile)) {
logger.info("Game summary GIF saved to {}", gifFile);
} else {
logger.error("Failed to save game summary GIF to {}", gifFile);
doAlertDialog(Messages.getString("ClientGUI.errorSavingFile"),
Messages.getString("ClientGUI.errorSavingFileGifMessage", gifFile.toString()));
}
} catch (Exception ex) {
logger.error(ex, "saveVictoryList");
doAlertDialog(Messages.getString("ClientGUI.errorSavingFile"), ex.getMessage());
}
}
}

protected void saveVictoryList() {
String filename = client.getLocalPlayer().getName();

Expand Down Expand Up @@ -2475,6 +2542,23 @@ public void gameEnd(GameEndEvent e) {
}
}

if (GUIP.getGifGameSummaryMinimap()) {
// Ask if you want to persist the final unit list from a battle encounter
if (doYesNoDialog(Messages.getString("ClientGUI.SaveGifDialog.title"),
Messages.getString("ClientGUI.SaveGifDialog.message"))) {
saveGifGameSummary();
} else {
String gameUuid = client.getGame().getUUIDString();
File tempGifFile = new File(gameSummaryImagesMMDir(), gameUuid + CG_FILEEXTENTIONGIF);
if (tempGifFile.delete()) {
logger.info("Deleted temporary game summary GIF {}", tempGifFile);
} else {
logger.error("Failed to delete temporary game summary GIF {}", tempGifFile);
}
}
}


// save all destroyed units in a separate "salvage MUL"
ArrayList<Entity> destroyed = new ArrayList<>();
Enumeration<Entity> graveyard = getClient().getGame().getGraveyardEntities();
Expand Down
60 changes: 27 additions & 33 deletions megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,7 @@
*/
package megamek.client.ui.swing;

import static java.util.stream.Collectors.toList;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Stream;

import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputAdapter;

import com.formdev.flatlaf.icons.FlatHelpButtonIcon;

import megamek.MMConstants;
import megamek.client.ui.Messages;
import megamek.client.ui.baseComponents.AbstractButtonDialog;
Expand All @@ -76,6 +45,25 @@
import megamek.common.util.fileUtils.MegaMekFile;
import megamek.logging.MMLogger;

import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
* The Client Settings Dialog offering GUI options concerning tooltips, map
* display, keybinds etc.
Expand Down Expand Up @@ -349,6 +337,8 @@ private <T> void moveElement(DefaultListModel<T> srcModel, int srcIndex, int trg
Messages.getString("CommonSettingsDialog.gameSummaryBV.name"));
private final JCheckBox gameSummaryMM = new JCheckBox(
Messages.getString("CommonSettingsDialog.gameSummaryMM.name"));
private final JCheckBox gifGameSummaryMM = new JCheckBox(
Messages.getString("CommonSettingsDialog.gifGameSummaryMM.name"));
private final JCheckBox showUnitDisplayNamesOnMinimap = new JCheckBox(
Messages.getString("CommonSettingsDialog.showUnitDisplayNamesOnMinimap.name"));
private JComboBox<String> skinFiles;
Expand Down Expand Up @@ -1674,6 +1664,9 @@ private JPanel getMiniMapPanel() {
comps.add(checkboxEntry(gameSummaryMM,
Messages.getString("CommonSettingsDialog.gameSummaryMM.tooltip",
Configuration.gameSummaryImagesMMDir())));
comps.add(checkboxEntry(gifGameSummaryMM,
Messages.getString("CommonSettingsDialog.gifGameSummaryMM.tooltip",
Configuration.gameSummaryImagesMMDir())));
comps.add(checkboxEntry(drawFacingArrowsOnMiniMap, null));
comps.add(checkboxEntry(drawSensorRangeOnMiniMap, null));
comps.add(checkboxEntry(paintBordersOnMiniMap, null));
Expand Down Expand Up @@ -2114,7 +2107,7 @@ public void setVisible(boolean visible) {

gameSummaryBV.setSelected(GUIP.getGameSummaryBoardView());
gameSummaryMM.setSelected(GUIP.getGameSummaryMinimap());

gifGameSummaryMM.setSelected(GUIP.getGifGameSummaryMinimap());
skinFiles.removeAllItems();
ArrayList<String> xmlFiles = new ArrayList<>(filteredFiles(Configuration.skinsDir(), ".xml"));

Expand Down Expand Up @@ -2584,10 +2577,11 @@ protected void okAction() {
GUIP.setAutoSelectNextUnit(useAutoSelectNext.isSelected());
GUIP.setGameSummaryBoardView(gameSummaryBV.isSelected());
GUIP.setGameSummaryMinimap(gameSummaryMM.isSelected());
GUIP.setGifGameSummaryMinimap(gifGameSummaryMM.isSelected());
GUIP.setShowUnitDisplayNamesOnMinimap(showUnitDisplayNamesOnMinimap.isSelected());
UITheme newUITheme = (UITheme) uiThemes.getSelectedItem();
String oldUITheme = GUIP.getUITheme();
if (!oldUITheme.equals(newUITheme.getClassName())) {
if (newUITheme != null && !oldUITheme.equals(newUITheme.getClassName())) {
GUIP.setUITheme(newUITheme.getClassName());
}

Expand Down
11 changes: 11 additions & 0 deletions megamek/src/megamek/client/ui/swing/GUIPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ public class GUIPreferences extends PreferenceStoreProxy {
public static final String SPLIT_PANE_A_DIVIDER_LOCATION = "SplitPaneADividerLocation";
public static final String GAME_SUMMARY_BOARD_VIEW = "GameSummaryBoardView";
public static final String GAME_SUMMARY_MINIMAP = "GameSummaryMinimap";
public static final String GIF_GAME_SUMMARY_MINIMAP = "GifGameSummaryMinimap";
public static final String SHOW_UNIT_DISPLAY_NAMES_ON_MINIMAP = "ShowUnitDisplayNamesOnMinimap";
public static final String ENTITY_OWNER_LABEL_COLOR = "EntityOwnerLabelColor";
public static final String UNIT_LABEL_BORDER = "EntityOwnerLabelColor";
Expand Down Expand Up @@ -689,6 +690,8 @@ protected GUIPreferences() {
store.setDefault(MINI_MAP_SHOW_FACING_ARROW, true);
store.setDefault(MINI_MAP_PAINT_BORDERS, true);
store.setDefault(MINI_MAP_MOVE_PATH_PERSISTENCE, 2);
store.setDefault(GIF_GAME_SUMMARY_MINIMAP, true);
store.setDefault(GAME_SUMMARY_MINIMAP, false);
store.setDefault(SHOW_UNIT_DISPLAY_NAMES_ON_MINIMAP, false);
store.setDefault(MOVE_DISPLAY_TAB_DURING_PHASES, true);
store.setDefault(FIRE_DISPLAY_TAB_DURING_PHASES, true);
Expand Down Expand Up @@ -1078,6 +1081,10 @@ public boolean getGameSummaryMinimap() {
return store.getBoolean(GAME_SUMMARY_MINIMAP);
}

public boolean getGifGameSummaryMinimap() {
return store.getBoolean(GIF_GAME_SUMMARY_MINIMAP);
}

public boolean showUnitDisplayNamesOnMinimap() {
return store.getBoolean(SHOW_UNIT_DISPLAY_NAMES_ON_MINIMAP);
}
Expand Down Expand Up @@ -1927,6 +1934,10 @@ public void setGameSummaryMinimap(boolean state) {
store.setValue(GAME_SUMMARY_MINIMAP, state);
}

public void setGifGameSummaryMinimap(boolean state) {
store.setValue(GIF_GAME_SUMMARY_MINIMAP, state);
}

public void setShowUnitDisplayNamesOnMinimap(boolean state) {
store.setValue(SHOW_UNIT_DISPLAY_NAMES_ON_MINIMAP, state);
}
Expand Down
24 changes: 14 additions & 10 deletions megamek/src/megamek/client/ui/swing/minimap/Minimap.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public final class Minimap extends JPanel implements IPreferenceChangeListener {
private static final int MARGIN = 3;
private static final int BUTTON_HEIGHT = 14;


/**
* The minimap zoom at which game summary images are saved regardless of the
* ingame minimap setting.
Expand Down Expand Up @@ -303,7 +304,7 @@ private void initializeListeners() {

@Override
public void gamePhaseChange(GamePhaseChangeEvent e) {
if (GUIP.getGameSummaryMinimap()
if ((GUIP.getGameSummaryMinimap() || GUIP.getGifGameSummaryMinimap())
&& (e.getOldPhase().isDeployment() || e.getOldPhase().isMovement()
|| e.getOldPhase().isTargeting() || e.getOldPhase().isPremovement()
|| e.getOldPhase().isPrefiring() || e.getOldPhase().isFiring()
Expand All @@ -315,20 +316,23 @@ public void gamePhaseChange(GamePhaseChangeEvent e) {
}
File imgFile = new File(dir, "round_" + game.getRoundCount() + "_" + e.getOldPhase().ordinal() + "_"
+ e.getOldPhase() + ".png");
if (gifWriterThread == null) {
gifWriterThread = new GifWriterThread(new GifWriter(game.getUUIDString()), "GifWriterThread");
gifWriterThread.start();
}
try {

try {
BufferedImage image = getMinimapImage(game, bv, GAME_SUMMARY_ZOOM, clientGui, null, movePathLines);
ImageIO.write(image, "png", imgFile);
long frameDurationInMillis = e.getOldPhase().isFiring()? 400 : 200;
gifWriterThread.addFrame(image, frameDurationInMillis);
if (GUIP.getGameSummaryMinimap()) {
ImageIO.write(image, "png", imgFile);
}
if (GUIP.getGifGameSummaryMinimap()) {
if (gifWriterThread == null) {
gifWriterThread = new GifWriterThread(new GifWriter(game.getUUIDString()), "GifWriterThread");
gifWriterThread.start();
}
gifWriterThread.addFrame(image, 400);
}
} catch (Exception ex) {
logger.error(ex, "Error saving game summary image.");
}
if (e.getNewPhase().isVictory() && gifWriterThread.isAlive()) {
if (e.getNewPhase().isVictory() && (gifWriterThread != null) && gifWriterThread.isAlive()) {
try {
gifWriterThread.stopThread();
} catch (Exception ex) {
Expand Down