Skip to content

Commit

Permalink
Merge pull request #5764 from rjhancock/possible-map-entry-fix
Browse files Browse the repository at this point in the history
Save File Fix
  • Loading branch information
HammerGS authored Jul 21, 2024
2 parents 90bb98b + d43af7b commit db636e4
Show file tree
Hide file tree
Showing 4 changed files with 373 additions and 259 deletions.
110 changes: 68 additions & 42 deletions megamek/src/megamek/common/util/SerializationHelper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022 - The MegaMek Team. All Rights Reserved.
* Copyright (c) 2020-2024 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MegaMek.
*
Expand All @@ -18,62 +18,85 @@
*/
package megamek.common.util;

import java.util.regex.Pattern;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

import megamek.common.Coords;

/**
* Class that off-loads serialization related code from Server.java
*/
public class SerializationHelper {

private SerializationHelper() {
}

/**
* Factory method that produces an XStream object suitable for working with MegaMek save games
* Factory method that produces an XStream object suitable for working with
* MegaMek save games
*/
public static XStream getSaveGameXStream() {
final XStream xStream = new XStream();

// This will make save games much smaller by using a more efficient means of referencing
// objects in the XML graph
// This will make save games much smaller by using a more efficient means of
// referencing objects in the XML graph
xStream.setMode(XStream.ID_REFERENCES);

// Setup Permissions
xStream.allowTypes(new Class[] {
megamek.client.bot.princess.BehaviorSettings.class,
megamek.common.ArtilleryTracker.ArtilleryModifier.class,
megamek.common.Board.class,
megamek.common.Coords.class,
megamek.common.CompositeTechLevel.DateRange.class,
megamek.common.CriticalSlot.class,
megamek.common.EquipmentMode.class,
megamek.common.Flare.class,
megamek.common.Game.class,
megamek.common.Hex.class,
megamek.common.Minefield.class,
megamek.common.PilotingRollData.class,
megamek.common.Player.class,
megamek.common.Sensor.class,
megamek.common.SpecialHexDisplay.class,
megamek.common.TagInfo.class,
megamek.common.TargetRollModifier.class,
megamek.common.Team.class,
megamek.common.Terrain.class,
megamek.common.Report.class,
megamek.common.force.Force.class,
megamek.server.SmokeCloud.class,
megamek.common.EntityFluff.class,
megamek.common.NarcPod.class,
megamek.common.INarcPod.class,
megamek.common.net.packets.Packet.class,
megamek.common.BoardLocation.class,
megamek.common.strategicBattleSystems.SBFMovePath.class,
// megamek.common.strategicBattleSystems.SBFUnit.class

xStream.allowTypesByRegExp(new Pattern[] {
Pattern.compile("\\[C$"),
Pattern.compile("\\[I$"),
Pattern.compile("\\[Ljava\\.lang\\.Enum;$"),
Pattern.compile("java\\.io\\.File$"),
Pattern.compile("java\\.lang\\.Boolean$"),
Pattern.compile("java\\.lang\\.Enum$"),
Pattern.compile("java\\.lang\\.Integer$"),
Pattern.compile("java\\.lang\\.Double$"),
Pattern.compile("java\\.lang\\.Number$"),
Pattern.compile("java\\.lang\\.StringBuffer$"),
Pattern.compile("java\\.util\\.ArrayList$"),
Pattern.compile("java\\.util\\.Collections\\$SetFromMap$"),
Pattern.compile("java\\.util\\.Collections\\$UnmodifiableCollection$"),
Pattern.compile("java\\.util\\.Collections\\$UnmodifiableList$"),
Pattern.compile("java\\.util\\.concurrent\\.ConcurrentHashMap$"),
Pattern.compile("java\\.util\\.concurrent\\.ConcurrentHashMap\\$Segment$"),
Pattern.compile("java\\.util\\.concurrent\\.CopyOnWriteArrayList$"),
Pattern.compile("java\\.util\\.concurrent\\.locks\\.AbstractOwnableSynchronizer$"),
Pattern.compile("java\\.util\\.concurrent\\.locks\\.AbstractQueuedSynchronizer$"),
Pattern.compile("java\\.util\\.concurrent\\.locks\\.ReentrantLock$"),
Pattern.compile("java\\.util\\.concurrent\\.locks\\.ReentrantLock\\$NonfairSync$"),
Pattern.compile("java\\.util\\.concurrent\\.locks\\.ReentrantLock\\$Sync$"),
Pattern.compile("java\\.util\\.UUID$"),
Pattern.compile("java\\.util\\.EnumMap$"),
Pattern.compile("java\\.util\\.EnumSet.*"),
Pattern.compile("java\\.util\\.HashMap$"),
Pattern.compile("java\\.util\\.HashSet$"),
Pattern.compile("java\\.util\\.Hashtable$"),
Pattern.compile("java\\.util\\.LinkedHashSet$"),
Pattern.compile("java\\.util\\.LinkedList$"),
Pattern.compile("java\\.util\\.TreeMap$"),
Pattern.compile("java\\.util\\.TreeSet$"),
Pattern.compile("java\\.util\\.Vector$"),
Pattern.compile("\\[Ljava\\.lang\\.Object;$"),
Pattern.compile("\\[Ljava\\.lang\\.String;$"),
Pattern.compile("\\[Ljava\\.util\\.concurrent\\.ConcurrentHashMap\\$Segment;$"),
Pattern.compile("\\[\\[Lmegamek.*"),
Pattern.compile("\\[Lmegamek.*"),
Pattern.compile("megamek.*"),
Pattern.compile("\\[\\[Lmekhq.*"),
Pattern.compile("\\[Lmekhq.*"),
Pattern.compile("mekhq.*"),
Pattern.compile("\\[\\[Lmegameklab.*"),
Pattern.compile("\\[Lmegameklab.*"),
Pattern.compile("megameklab.*"),
Pattern.compile("\\[Z$")
});

xStream.allowTypeHierarchy(megamek.common.BTObject.class);
xStream.allowTypeHierarchy(megamek.common.Building.class);
xStream.allowTypeHierarchy(megamek.common.Crew.class);
Expand All @@ -93,21 +116,24 @@ public static XStream getSaveGameXStream() {
}

/**
* Factory method that produces an XStream object suitable for loading MegaMek save games
* Factory method that produces an XStream object suitable for loading MegaMek
* save games
*/
public static XStream getLoadSaveGameXStream() {
XStream xstream = getSaveGameXStream();
XStream xStream = getSaveGameXStream();

xstream.registerConverter(new Converter() {
xStream.registerConverter(new Converter() {
@Override
public boolean canConvert(Class cls) {
return (cls == Coords.class);
}

@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
int x = 0, y = 0;
boolean foundX = false, foundY = false;
int x = 0;
int y = 0;
boolean foundX = false;
boolean foundY = false;
while (reader.hasMoreChildren()) {
reader.moveDown();
switch (reader.getNodeName()) {
Expand All @@ -134,6 +160,6 @@ public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingC
}
});

return xstream;
return xStream;
}
}
85 changes: 51 additions & 34 deletions megamek/src/megamek/server/AbstractGameManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@
import megamek.common.IGame;
import megamek.common.Player;
import megamek.common.enums.GamePhase;
import megamek.common.net.enums.PacketCommand;
import megamek.common.net.packets.Packet;
import megamek.common.options.OptionsConstants;
import megamek.common.preference.PreferenceManager;
import megamek.common.util.StringUtil;
import org.apache.logging.log4j.LogManager;
import megamek.logging.MMLogger;

public abstract class AbstractGameManager implements IGameManager {
private static final MMLogger logger = MMLogger.create(AbstractGameManager.class);

protected final GameManagerPacketHelper packetHelper = new GameManagerPacketHelper(this);
protected final GameManagerSaveHelper saveHandler = new GameManagerSaveHelper(this);
protected final AutosaveService autoSaveService = new AutosaveService(this);

/**
* Sends the given packet to all connections (all connected Clients = players).
*
* @see Server#send(Packet)
*/
public void send(Packet packet) {
Expand All @@ -43,6 +46,7 @@ public void send(Packet packet) {

/**
* Sends the given packet to the given connection (= player ID).
*
* @see Server#send(int, Packet)
*/
public void send(int connId, Packet p) {
Expand All @@ -51,12 +55,10 @@ public void send(int connId, Packet p) {

@Override
public void handlePacket(int connId, Packet packet) {
switch (packet.getCommand()) {
case PLAYER_READY:
receivePlayerDone(packet, connId);
send(packetHelper.createPlayerDonePacket(connId));
checkReady();
break;
if (packet.getCommand() == PacketCommand.PLAYER_READY) {
receivePlayerDone(packet, connId);
send(packetHelper.createPlayerDonePacket(connId));
checkReady();
}
}

Expand All @@ -66,20 +68,21 @@ public void handlePacket(int connId, Packet packet) {
protected abstract void endCurrentPhase();

/**
* Do anything we need to work through the current phase, such as give a turn to the
* first player to play.
* Do anything we need to work through the current phase, such as give a turn to
* the first player to play.
*/
protected abstract void executeCurrentPhase();

/**
* Prepares for the game's current phase. This typically involves resetting the states of
* units in the game and making sure the clients have the information they need for the new phase.
* Prepares for the game's current phase. This typically involves resetting the
* states of units in the game and making sure the clients have the information
* they need for the new phase.
*/
protected abstract void prepareForCurrentPhase();

/**
* Switches to the given new Phase and preforms preparation, checks if it should be skipped
* and executes it.
* Switches to the given new Phase and preforms preparation, checks if it should
* be skipped and executes it.
*/
public final void changePhase(GamePhase newPhase) {
getGame().setLastPhase(getGame().getPhase());
Expand All @@ -97,13 +100,18 @@ public final void changePhase(GamePhase newPhase) {
}

/**
* Called when a player declares that they are "done". By default, this method advances to the next phase, if
* <BR>- all non-ghost, non-observer players are done,
* <BR>- the present phase does not use turns (e.g. if it's a report phase), and
* <BR>- we are not in an empty lobby (= no units at all).
* <BR>In other circumstances, ending the current phase is triggered elsewhere. Note that specifically,
* ghost players are not checked for their status here so the game can advance through non-turn (report)
* phases even with ghost players.
* Called when a player declares that they are "done". By default, this method
* advances to the next phase, if
* <BR>
* - all non-ghost, non-observer players are done,
* <BR>
* - the present phase does not use turns (e.g. if it's a report phase), and
* <BR>
* - we are not in an empty lobby (= no units at all).
* <BR>
* In other circumstances, ending the current phase is triggered elsewhere. Note
* that specifically, ghost players are not checked for their status here so the
* game can advance through non-turn (report) phases even with ghost players.
*/
protected void checkReady() {
for (Player player : getGame().getPlayersList()) {
Expand All @@ -124,22 +132,25 @@ protected void transmitAllPlayerDones() {
getGame().getPlayersList().forEach(player -> send(packetHelper.createPlayerDonePacket(player.getId())));
}

/** @return True when the game is in the lobby phase and is empty (no units present). */
/**
* @return True when the game is in the lobby phase and is empty (no units
* present).
*/
protected boolean isEmptyLobby() {
return getGame().getPhase().isLounge() && getGame().getInGameObjects().isEmpty();
}

/**
* Sets a player's ready status as received from the Client. This method does not perform any
* follow-up actions.
* Sets a player's ready status as received from the Client. This method does
* not perform any follow-up actions.
*/
private void receivePlayerDone(Packet packet, int connIndex) {
boolean ready = packet.getBooleanValue(0);
Player player = getGame().getPlayer(connIndex);
if (null != player) {
player.setDone(ready);
} else {
LogManager.getLogger().error("Tried to set done status of non-existent player!");
logger.error("Tried to set done status of non-existent player!");
}
}

Expand All @@ -155,8 +166,8 @@ protected void transmitPlayerUpdate(Player player) {
}

/**
* Shares all player objects with all players. Private info is redacted before being sent
* to other players.
* Shares all player objects with all players. Private info is redacted before
* being sent to other players.
*
* @see #transmitPlayerUpdate(Player)
*/
Expand All @@ -165,15 +176,18 @@ public void transmitAllPlayerUpdates() {
}

/**
* Performs an automatic save (does not check the autosave settings - the autosave will simply be done).
* Depending on the settings, the "autosave" filename is appended
* with a timestamp and/or a chat message is sent announcing the autosave.
* Performs an automatic save (does not check the autosave settings - the
* autosave will simply be done). Depending on the settings, the "autosave"
* filename is appended with a timestamp and/or a chat message is sent
* announcing the autosave.
*/
public void autoSave() {
String fileName = "autosave";

if (PreferenceManager.getClientPreferences().stampFilenames()) {
fileName = StringUtil.addDateTimeStamp(fileName);
}

saveGame(fileName, getGame().getOptions().booleanOption(OptionsConstants.BASE_AUTOSAVE_MSG));
}

Expand Down Expand Up @@ -204,7 +218,9 @@ public void sendServerChat(int connId, String message) {
}

/**
* Sends the current list of player turns as stored in the game's turn list to the Clients.
* Sends the current list of player turns as stored in the game's turn list to
* the Clients.
*
* @see IGame#getTurnsList()
*/
public void sendCurrentTurns() {
Expand All @@ -228,14 +244,15 @@ public AutosaveService getAutoSaveService() {
}

/**
* Sends out a notification message indicating that a ghost player's turn may be skipped with the
* /skip command.
* Sends out a notification message indicating that a ghost player's turn may be
* skipped with the /skip command.
*
* @param ghost the Player who is ghosted. This value must not be null.
*/
protected void sendGhostSkipMessage(Player ghost) {
String message = "Player '" + ghost.getName() +
"' is disconnected. You may skip their current turn with the /skip command.";
String message = String.format(
"Player '%s' is disconnected. You may skip their current turn with the /skip command.",
ghost.getName());
sendServerChat(message);
}
}
Loading

0 comments on commit db636e4

Please sign in to comment.