diff --git a/megamek/src/megamek/common/util/SerializationHelper.java b/megamek/src/megamek/common/util/SerializationHelper.java
index 11d913dc9ca..82deb88e8dc 100644
--- a/megamek/src/megamek/common/util/SerializationHelper.java
+++ b/megamek/src/megamek/common/util/SerializationHelper.java
@@ -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.
*
@@ -18,12 +18,15 @@
*/
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;
/**
@@ -31,49 +34,69 @@
*/
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);
@@ -93,12 +116,13 @@ 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);
@@ -106,8 +130,10 @@ public boolean canConvert(Class cls) {
@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()) {
@@ -134,6 +160,6 @@ public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingC
}
});
- return xstream;
+ return xStream;
}
}
diff --git a/megamek/src/megamek/server/AbstractGameManager.java b/megamek/src/megamek/server/AbstractGameManager.java
index fe9aeca84b4..3023fd0b1e0 100644
--- a/megamek/src/megamek/server/AbstractGameManager.java
+++ b/megamek/src/megamek/server/AbstractGameManager.java
@@ -21,13 +21,15 @@
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);
@@ -35,6 +37,7 @@ public abstract class AbstractGameManager implements IGameManager {
/**
* Sends the given packet to all connections (all connected Clients = players).
+ *
* @see Server#send(Packet)
*/
public void send(Packet packet) {
@@ -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) {
@@ -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();
}
}
@@ -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());
@@ -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
- *
- all non-ghost, non-observer players are done,
- *
- the present phase does not use turns (e.g. if it's a report phase), and
- *
- we are not in an empty lobby (= no units at all).
- *
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
+ *
+ * - all non-ghost, non-observer players are done,
+ *
+ * - the present phase does not use turns (e.g. if it's a report phase), and
+ *
+ * - we are not in an empty lobby (= no units at all).
+ *
+ * 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()) {
@@ -124,14 +132,17 @@ 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);
@@ -139,7 +150,7 @@ private void receivePlayerDone(Packet packet, int 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!");
}
}
@@ -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)
*/
@@ -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));
}
@@ -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() {
@@ -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);
}
}
diff --git a/megamek/src/megamek/server/GameManagerSaveHelper.java b/megamek/src/megamek/server/GameManagerSaveHelper.java
index 348a5ebce15..a9e3dbbdc6d 100644
--- a/megamek/src/megamek/server/GameManagerSaveHelper.java
+++ b/megamek/src/megamek/server/GameManagerSaveHelper.java
@@ -18,19 +18,27 @@
*/
package megamek.server;
-import megamek.MMConstants;
-import megamek.common.net.enums.PacketCommand;
-import megamek.common.net.packets.Packet;
-import megamek.common.util.SerializationHelper;
-import org.apache.logging.log4j.LogManager;
-
-import java.io.*;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPOutputStream;
+import megamek.MMConstants;
+import megamek.common.net.enums.PacketCommand;
+import megamek.common.net.packets.Packet;
+import megamek.common.util.SerializationHelper;
+import megamek.logging.MMLogger;
+
public class GameManagerSaveHelper {
+ private static final MMLogger logger = MMLogger.create(GameManagerSaveHelper.class);
private final AbstractGameManager gameManager;
@@ -39,8 +47,8 @@ public class GameManagerSaveHelper {
}
/**
- * Saves the game server-side. Will announce the save (or error) in chat if the given sendChat
- * is true.
+ * Saves the game server-side. Will announce the save (or error) in chat if the
+ * given sendChat is true.
*
* @param fileName The filename to use
* @param sendChat When true, the saving (or error) is announced in chat
@@ -55,6 +63,7 @@ protected void saveGame(String fileName, boolean sendChat) {
if (!finalFileName.endsWith(MMConstants.SAVE_FILE_EXT)) {
finalFileName = fileName + MMConstants.SAVE_FILE_EXT;
}
+
File saveGameDir = new File(MMConstants.SAVEGAME_DIR);
if (!saveGameDir.exists()) {
saveGameDir.mkdir();
@@ -63,11 +72,13 @@ protected void saveGame(String fileName, boolean sendChat) {
finalFileName = saveGameDir + File.separator + finalFileName;
try (OutputStream os = new FileOutputStream(finalFileName + ".gz");
- OutputStream gzo = new GZIPOutputStream(os);
- Writer writer = new OutputStreamWriter(gzo, StandardCharsets.UTF_8)) {
+ OutputStream gzo = new GZIPOutputStream(os);
+ Writer writer = new OutputStreamWriter(gzo, StandardCharsets.UTF_8)) {
SerializationHelper.getSaveGameXStream().toXML(gameManager.getGame(), writer);
} catch (Exception e) {
- LogManager.getLogger().error("Unable to save file: {}", finalFileName, e);
+ String message = String.format("Unable to save file: %s", finalFileName);
+ logger.error(e, message);
+
if (sendChat) {
gameManager.sendChat("MegaMek", "Could not save the game to " + finalFileName);
}
@@ -81,12 +92,14 @@ protected void saveGame(String fileName, boolean sendChat) {
/**
* Saves the game and sends it to the specified connection
*
- * @param connId The int
connection id to send to
- * @param fileName The String
filename to use
- * @param localPath The String
path to the file to be used on the client
+ * @param connId The int
connection id to send to
+ * @param fileName The String
filename to use
+ * @param localPath The String
path to the file to be used on the
+ * client
*/
public void sendSaveGame(int connId, String fileName, String localPath) {
saveGame(fileName, false);
+
String finalFileName = fileName;
if (!finalFileName.endsWith(MMConstants.SAVE_FILE_GZ_EXT)) {
if (finalFileName.endsWith(MMConstants.SAVE_FILE_EXT)) {
@@ -95,8 +108,10 @@ public void sendSaveGame(int connId, String fileName, String localPath) {
finalFileName = fileName + MMConstants.SAVE_FILE_GZ_EXT;
}
}
- localPath = localPath.replaceAll("\\|", " ");
+
+ localPath = localPath.replace("|", " ");
String localFile = MMConstants.SAVEGAME_DIR + File.separator + finalFileName;
+
try (InputStream in = new FileInputStream(localFile); InputStream bin = new BufferedInputStream(in)) {
List data = new ArrayList<>();
int input;
@@ -106,7 +121,8 @@ public void sendSaveGame(int connId, String fileName, String localPath) {
gameManager.send(connId, new Packet(PacketCommand.SEND_SAVEGAME, finalFileName, data, localPath));
gameManager.sendChat(connId, "***Server", "Save game has been sent to you.");
} catch (Exception ex) {
- LogManager.getLogger().error("Unable to load file: {}", localFile, ex);
+ String message = String.format("Unable to load file: %s", localFile);
+ logger.error(ex, message);
}
}
}
diff --git a/megamek/src/megamek/server/Server.java b/megamek/src/megamek/server/Server.java
index bf871eb146f..2d6f235aaae 100644
--- a/megamek/src/megamek/server/Server.java
+++ b/megamek/src/megamek/server/Server.java
@@ -1,27 +1,60 @@
/*
* Copyright (c) 2000-2005 - Ben Mazur (bmazur@sev.org)
* Copyright (c) 2013 - Edward Cullen (eddy@obsessedcomputers.co.uk)
- * Copyright (c) 2018-2022 - The MegaMek Team. All Rights Reserved.
+ * Copyright (c) 2018-2024 - The MegaMek Team. All Rights Reserved.
*
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2 of the License, or (at your option) any later
- * version.
+ * This file is part of MegaMek.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
+ * MegaMek is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MegaMek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MegaMek. If not, see .
*/
package megamek.server;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+import java.util.zip.GZIPInputStream;
+
import com.thoughtworks.xstream.XStream;
+
import megamek.MMConstants;
import megamek.MegaMek;
+import megamek.SuiteConstants;
import megamek.Version;
import megamek.client.ui.swing.util.PlayerColour;
import megamek.codeUtilities.StringUtility;
-import megamek.common.*;
+import megamek.common.Game;
+import megamek.common.IGame;
+import megamek.common.Player;
+import megamek.common.Roll;
import megamek.common.annotations.Nullable;
import megamek.common.commandline.AbstractCommandLineParser.ParseException;
import megamek.common.icons.Camouflage;
@@ -37,18 +70,8 @@
import megamek.common.preference.PreferenceManager;
import megamek.common.util.EmailService;
import megamek.common.util.SerializationHelper;
+import megamek.logging.MMLogger;
import megamek.server.commands.ServerCommand;
-import org.apache.logging.log4j.LogManager;
-
-import java.io.*;
-import java.net.*;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Consumer;
-import java.util.zip.GZIPInputStream;
/**
* @author Ben Mazur
@@ -67,6 +90,8 @@ public class Server implements Runnable {
private final EmailService mailer;
+ private static final MMLogger logger = MMLogger.create(Server.class);
+
public static class ReceivedPacket {
private int connectionId;
private Packet packet;
@@ -192,7 +217,8 @@ public void disconnected(DisconnectedEvent e) {
AbstractConnection conn = e.getConnection();
// write something in the log
- LogManager.getLogger().info("s: connection " + conn.getId() + " disconnected");
+ String message = String.format("s: connect %d disconnected.", conn.getId());
+ logger.info(message);
connections.remove(conn);
synchronized (serverLock) {
@@ -243,9 +269,9 @@ public void packetReceived(PacketReceivedEvent e) {
*/
public static String validateServerAddress(String serverAddress) throws ParseException {
if ((serverAddress == null) || serverAddress.isBlank()) {
- String msg = "serverAddress must not be null or empty";
- LogManager.getLogger().error(msg);
- throw new ParseException(msg);
+ String message = "serverAddress must not be null or empty";
+ logger.error(message);
+ throw new ParseException(message);
} else {
return serverAddress.trim();
}
@@ -257,13 +283,13 @@ public static String validateServerAddress(String serverAddress) throws ParseExc
*/
public static String validatePlayerName(String playerName) throws ParseException {
if (playerName == null) {
- String msg = "playerName must not be null";
- LogManager.getLogger().error(msg);
- throw new ParseException(msg);
+ String message = "playerName must not be null";
+ logger.error(message);
+ throw new ParseException(message);
} else if (playerName.isBlank()) {
- String msg = "playerName must not be empty string";
- LogManager.getLogger().error(msg);
- throw new ParseException(msg);
+ String message = "playerName must not be empty string";
+ logger.error(message);
+ throw new ParseException(message);
} else {
return playerName.trim();
}
@@ -281,23 +307,26 @@ public static String validatePlayerName(String playerName) throws ParseException
* Checks a String against the server password
*
* @param password The password provided by the user.
- * @return true if the user-supplied data matches the server password or no password is set.
+ * @return true if the user-supplied data matches the server password or no
+ * password is set.
*/
public boolean passwordMatches(Object password) {
return StringUtility.isNullOrBlank(this.password) || this.password.equals(password);
}
/**
- * @param port if 0 or less, will return default, if illegal number, throws ParseException
+ * @param port if 0 or less, will return default, if illegal number, throws
+ * ParseException
* @return valid port number
*/
public static int validatePort(int port) throws ParseException {
if (port <= 0) {
return MMConstants.DEFAULT_PORT;
} else if ((port < MMConstants.MIN_PORT) || (port > MMConstants.MAX_PORT)) {
- String msg = String.format("Port number %d outside allowed range %d-%d", port, MMConstants.MIN_PORT, MMConstants.MAX_PORT);
- LogManager.getLogger().error(msg);
- throw new ParseException(msg);
+ String message = String.format("Port number %d outside allowed range %d-%d", port, MMConstants.MIN_PORT,
+ MMConstants.MAX_PORT);
+ logger.error(message);
+ throw new ParseException(message);
} else {
return port;
}
@@ -308,30 +337,36 @@ public Server(@Nullable String password, int port, IGameManager gameManager) thr
}
public Server(@Nullable String password, int port, IGameManager gameManager,
- boolean registerWithServerBrowser, @Nullable String metaServerUrl) throws IOException {
+ boolean registerWithServerBrowser, @Nullable String metaServerUrl) throws IOException {
this(password, port, gameManager, registerWithServerBrowser, metaServerUrl, null, false);
}
/**
* Construct a new GameHost and begin listening for incoming clients.
*
- * @param password the String
that is set as a password
- * @param port the int
value that specifies the port that is
- * used
- * @param gameManager the {@link IGameManager} instance for this server instance.
- * @param registerWithServerBrowser a boolean
indicating whether we should register
- * with the master server browser on MegaMek.info
- * @param mailer an email service instance to use for sending round reports.
- * @param dedicated set to true if this server is started from a GUI-less context
+ * @param password the String
that is set as a
+ * password
+ * @param port the int
value that specifies
+ * the port that is used
+ * @param gameManager the {@link IGameManager} instance for this
+ * server instance.
+ * @param registerWithServerBrowser a boolean
indicating whether we
+ * should register with the master server
+ * browser on https://api.megamek.org
+ * @param mailer an email service instance to use for sending
+ * round reports.
+ * @param dedicated set to true if this server is started from a
+ * GUI-less context
*/
public Server(@Nullable String password, int port, IGameManager gameManager,
- boolean registerWithServerBrowser, @Nullable String metaServerUrl,
- @Nullable EmailService mailer, boolean dedicated) throws IOException {
+ boolean registerWithServerBrowser, @Nullable String metaServerUrl,
+ @Nullable EmailService mailer, boolean dedicated) throws IOException {
this.metaServerUrl = StringUtility.isNullOrBlank(metaServerUrl) ? null : metaServerUrl;
this.password = StringUtility.isNullOrBlank(password) ? null : password;
this.gameManager = gameManager;
this.mailer = mailer;
this.dedicated = dedicated;
+ String message = "";
// initialize server socket
serverSocket = new ServerSocket(port);
@@ -339,29 +374,24 @@ public Server(@Nullable String password, int port, IGameManager gameManager,
motd = createMotd();
// display server start text
- LogManager.getLogger().info("s: starting a new server...");
+ logger.info("s: starting a new server...");
try {
- StringBuilder sb = new StringBuilder();
String host = InetAddress.getLocalHost().getHostName();
- sb.append("s: hostname = '");
- sb.append(host);
- sb.append("' port = ");
- sb.append(serverSocket.getLocalPort());
- sb.append('\n');
+ message = String.format("s: hostname = '%s' port = %d", host, serverSocket.getLocalPort());
+ logger.info(message);
+
InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress address : addresses) {
- sb.append("s: hosting on address = ");
- sb.append(address.getHostAddress());
- sb.append('\n');
+ message = String.format("s: hosting on address = %s", address.getHostAddress());
+ logger.info(message);
}
-
- LogManager.getLogger().info(sb.toString());
} catch (Exception ignored) {
-
+ logger.error(ignored, "Ignore Exception.");
}
- LogManager.getLogger().info("s: password = " + this.password);
+ message = String.format("s: password = %s", this.password);
+ logger.info(message);
for (ServerCommand command : gameManager.getCommandList(this)) {
registerCommand(command);
@@ -382,11 +412,12 @@ public void run() {
serverBrowserUpdateTimer = new Timer("Server Browser Register Timer", true);
serverBrowserUpdateTimer.schedule(register, 1, 40000);
} else {
- LogManager.getLogger().error("Invalid URL for server browser " + this.metaServerUrl);
+ message = String.format("Invalid URL for server browser %s", this.metaServerUrl);
+ logger.error(message);
}
}
- // Fully initialised, now accept connections
+ // Fully initialized, now accept connections
connector = new Thread(this, "Connection Listener");
connector.start();
@@ -419,7 +450,7 @@ public EmailService getEmailService() {
* it was found, the build timestamp
*/
private String createMotd() {
- return "Welcome to MegaMek. Server is running version " + MMConstants.VERSION;
+ return "Welcome to MegaMek. Server is running version " + SuiteConstants.VERSION;
}
/**
@@ -473,7 +504,7 @@ public void die() {
try {
serverSocket.close();
} catch (Exception ignored) {
-
+ logger.error(ignored, "Ignored Exception");
}
// kill pending connections
@@ -573,8 +604,7 @@ private void receivePlayerInfo(Packet packet, int connId) {
* @return the String
new player name
*/
private String correctDupeName(String oldName) {
- for (Enumeration i = getGame().getPlayers(); i.hasMoreElements(); ) {
- Player player = i.nextElement();
+ for (Player player : getGame().getPlayersList()) {
if (player.getName().equals(oldName)) {
// We need to correct it.
String newName = oldName;
@@ -597,10 +627,11 @@ private String correctDupeName(String oldName) {
private boolean receivePlayerVersion(Packet packet, int connId) {
final Version version = (Version) packet.getObject(0);
- if (!MMConstants.VERSION.is(version)) {
+
+ if (!SuiteConstants.VERSION.is(version)) {
final String message = String.format("Client/Server Version Mismatch -- Client: %s, Server: %s",
- version, MMConstants.VERSION);
- LogManager.getLogger().error(message);
+ version, SuiteConstants.VERSION);
+ logger.error(message);
final Player player = getPlayer(connId);
sendServerChat(String.format("For %s, Server reports:%s%s",
@@ -611,29 +642,28 @@ private boolean receivePlayerVersion(Packet packet, int connId) {
final String clientChecksum = (String) packet.getObject(1);
final String serverChecksum = MegaMek.getMegaMekSHA256();
- final String message;
+ String message = "";
// print a message indicating client doesn't have jar file
if (clientChecksum == null) {
message = "Client Checksum is null. Client may not have a jar file";
- LogManager.getLogger().info(message);
+ logger.info(message);
// print message indicating server doesn't have jar file
} else if (serverChecksum == null) {
message = "Server Checksum is null. Server may not have a jar file";
- LogManager.getLogger().info(message);
+ logger.info(message);
// print message indicating a client/server checksum mismatch
} else if (!clientChecksum.equals(serverChecksum)) {
message = String.format("Client/Server checksum mismatch. Server reports: %s, Client reports %s",
serverChecksum, clientChecksum);
- LogManager.getLogger().warn(message);
- } else {
- message = "";
+ logger.warn(message);
}
// Now, if we need to, send message!
if (message.isEmpty()) {
- LogManager.getLogger().info("SUCCESS: Client/Server Version (" + version + ") and Checksum ("
- + clientChecksum + ") matched");
+ message = String.format("SUCCESS: Client/Server Version (%s) and Checksum (%s) matched", version,
+ clientChecksum);
+ logger.info(message);
} else {
Player player = getPlayer(connId);
sendServerChat(String.format("For %s, Server reports:%s%s",
@@ -653,25 +683,23 @@ private void receivePlayerName(Packet packet, int connId) {
String name = (String) packet.getObject(0);
boolean isBot = (boolean) packet.getObject(1);
boolean returning = false;
+ String message = "";
// this had better be from a pending connection
if (conn == null) {
- LogManager.getLogger().warn("Got a client name from a non-pending connection");
+ logger.warn("Got a client name from a non-pending connection");
return;
}
// check if they're connecting with the same name as a ghost player
- for (Enumeration i = getGame().getPlayers(); i.hasMoreElements(); ) {
- Player player = i.nextElement();
- if (player.getName().equals(name)) {
- if (player.isGhost()) {
- returning = true;
- player.setGhost(false);
- player.setBot(isBot);
- // switch id
- connId = player.getId();
- conn.setId(connId);
- }
+ for (Player player : getGame().getPlayersList()) {
+ if (player.getName().equals(name) && player.isGhost()) {
+ returning = true;
+ player.setGhost(false);
+ player.setBot(isBot);
+ // switch id
+ connId = player.getId();
+ conn.setId(connId);
}
}
@@ -692,8 +720,9 @@ private void receivePlayerName(Packet packet, int connId) {
// if it is not the lounge phase, this player becomes an observer
Player player = getPlayer(connId);
- if (!getGame().getPhase().isLounge() && (null != player)
- && (getGame().getEntitiesOwnedBy(player) < 1)) {
+ if (!getGame().getPhase().isLounge() &&
+ (null != player) &&
+ (getGame().getEntitiesOwnedBy(player) < 1)) {
player.setObserver(true);
}
@@ -714,27 +743,33 @@ private void receivePlayerName(Packet packet, int connId) {
try {
InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
for (InetAddress address : addresses) {
- LogManager.getLogger().info("s: machine IP " + address.getHostAddress());
+ message = String.format("s: Machine IP %s", address.getHostAddress());
+ logger.info(message);
+
if (showIPAddressesInChat) {
- sendServerChat(connId, "Machine IP is " + address.getHostAddress());
+ sendServerChat(connId, message);
}
}
} catch (Exception ignored) {
-
+ logger.error(ignored, "Ignored Exception");
}
- LogManager.getLogger().info("s: listening on port " + serverSocket.getLocalPort());
+ message = String.format("s: Listening on port %d", serverSocket.getLocalPort());
+ logger.info(message);
+
if (showIPAddressesInChat) {
- // Send the port we're listening on. Only useful for the player
- // on the server machine to check.
- sendServerChat(connId, "Listening on port " + serverSocket.getLocalPort());
+ // Send the port we're listening on. Only useful for the player on the server
+ // machine to check.
+ sendServerChat(connId, message);
}
// Get the player *again*, because they may have disconnected.
player = getPlayer(connId);
if (null != player) {
- String who = player.getName() + " connected from " + getClient(connId).getInetAddress();
- LogManager.getLogger().info("s: player #" + connId + ", " + who);
+ String who = String.format("%s connected from %s", player.getName(), getClient(connId).getInetAddress());
+ message = String.format("s: player #%d, %s", connId, who);
+ logger.info(message);
+
if (showIPAddressesInChat) {
sendServerChat(who);
}
@@ -773,15 +808,13 @@ private Player addNewPlayer(int connId, String name, boolean isBot) {
Player newPlayer = new Player(connId, name);
newPlayer.setBot(isBot);
PlayerColour colour = newPlayer.getColour();
- Enumeration players = getGame().getPlayers();
final PlayerColour[] colours = PlayerColour.values();
- while (players.hasMoreElements()) {
- final Player p = players.nextElement();
- if (p.getId() == newPlayer.getId()) {
+ for (Player player : getGame().getPlayersList()) {
+ if (player.getId() == newPlayer.getId()) {
continue;
}
- if ((p.getColour() == colour) && (colours.length > (colour.ordinal() + 1))) {
+ if ((player.getColour() == colour) && (colours.length > (colour.ordinal() + 1))) {
colour = colours[colour.ordinal() + 1];
}
}
@@ -806,8 +839,7 @@ public void validatePlayerInfo(int playerId) {
final PlayerColour[] playerColours = PlayerColour.values();
boolean allUsed = true;
Set colourUtilization = new HashSet<>();
- for (Enumeration i = getGame().getPlayers(); i.hasMoreElements(); ) {
- final Player otherPlayer = i.nextElement();
+ for (Player otherPlayer : getGame().getPlayersList()) {
if (otherPlayer.getId() != playerId) {
colourUtilization.add(otherPlayer.getColour());
} else {
@@ -859,7 +891,8 @@ public void sendLoadGame(int connId, String sFile) {
* load the game
*
* @param f The File
to load
- * @return A boolean
value whether or not the loading was successful
+ * @return A boolean
value whether or not the loading was
+ * successful
*/
public boolean loadGame(File f) {
return loadGame(f, true);
@@ -870,12 +903,15 @@ public boolean loadGame(File f) {
*
* @param f The File
to load
* @param sendInfo Determines whether the connections should be updated with
- * current info. This may be false if some reconnection remapping
+ * current info. This may be false if some reconnection
+ * remapping
* needs to be done first.
- * @return A boolean
value whether or not the loading was successful
+ * @return A boolean
value whether or not the loading was
+ * successful
*/
public boolean loadGame(File f, boolean sendInfo) {
- LogManager.getLogger().info("s: loading saved game file '" + f.getAbsolutePath() + '\'');
+ String message = String.format("s: Loading saved game file '%s'", f.getAbsolutePath());
+ logger.info(message);
Game newGame;
try (InputStream is = new FileInputStream(f)) {
@@ -890,7 +926,8 @@ public boolean loadGame(File f, boolean sendInfo) {
XStream xstream = SerializationHelper.getLoadSaveGameXStream();
newGame = (Game) xstream.fromXML(gzi);
} catch (Exception e) {
- LogManager.getLogger().error("Unable to load file: " + f, e);
+ message = String.format("Unable to load file: %s", f);
+ logger.error(e, message);
return false;
}
@@ -924,30 +961,35 @@ public void sendSaveGame(int connId, String fileName, String localPath) {
* current ids should change to.
*
* @param nameToIdMap This maps a player name to the current connection ID
- * @param idToNameMap This maps a current conn ID to a player name, and is just the
- * inverse mapping from nameToIdMap
+ * @param idToNameMap This maps a current conn ID to a player name, and is just
+ * the inverse mapping from nameToIdMap
*/
public void remapConnIds(Map nameToIdMap, Map idToNameMap) {
// Keeps track of connections without Ids
List unassignedConns = new ArrayList<>();
+
// Keep track of which ids are used
Set usedPlayerIds = new HashSet<>();
Set currentPlayerNames = new HashSet<>();
- for (Player p : getGame().getPlayersVector()) {
+
+ for (Player p : getGame().getPlayersList()) {
currentPlayerNames.add(p.getName());
}
+
// Map the old connection Id to new value
Map connIdRemapping = new HashMap<>();
- for (Player p : getGame().getPlayersVector()) {
+ for (Player p : getGame().getPlayersList()) {
// Check to see if this player was already connected
Integer oldId = nameToIdMap.get(p.getName());
if ((oldId != null) && (oldId != p.getId())) {
connIdRemapping.put(oldId, p.getId());
}
+
// If the old and new Ids match, make sure we remove ghost status
if ((oldId != null) && (oldId == p.getId())) {
p.setGhost(false);
}
+
// Check to see if this player's Id is taken
String oldName = idToNameMap.get(p.getId());
if ((oldName != null) && !oldName.equals(p.getName())) {
@@ -965,10 +1007,11 @@ public void remapConnIds(Map nameToIdMap, Map
}
// Remap old connection Ids to new ones
- for (Integer currConnId : connIdRemapping.keySet()) {
- Integer newId = connIdRemapping.get(currConnId);
- AbstractConnection conn = connectionIds.get(currConnId);
+ for (Entry currentConnect : connIdRemapping.entrySet()) {
+ Integer newId = currentConnect.getValue();
+ AbstractConnection conn = connectionIds.get(currentConnect.getKey());
conn.setId(newId);
+
// If this Id is used, make sure we reassign that connection
if (connectionIds.containsKey(newId)) {
unassignedConns.add(connectionIds.get(newId));
@@ -983,9 +1026,11 @@ public void remapConnIds(Map nameToIdMap, Map
// It's possible we have players not in the saved game, add 'em
for (AbstractConnection conn : unassignedConns) {
int newId = 0;
+
while (usedPlayerIds.contains(newId)) {
newId++;
}
+
String name = idToNameMap.get(conn.getId());
conn.setId(newId);
Player newPlayer = addNewPlayer(newId, name, false);
@@ -1022,6 +1067,7 @@ private AbstractConnection getClient(int connId) {
/**
* Executes the process on each active connection.
+ *
* @param process The process to execute.
*/
public void forEachConnection(Consumer process) {
@@ -1051,7 +1097,7 @@ public AbstractConnection getConnection(int connId) {
* Sends out all player info to the specified connection
*/
private void transmitPlayerConnect(AbstractConnection connection) {
- for (var player : getGame().getPlayersVector()) {
+ for (Player player : getGame().getPlayersList()) {
var connectionId = connection.getId();
connection.send(createPlayerConnectPacket(player, player.getId() != connectionId));
}
@@ -1104,7 +1150,7 @@ void transmitPlayerUpdate(Player player) {
* Sends out the player info updates for all players to all connections
*/
private void transmitAllPlayerUpdates() {
- for (var player : getGame().getPlayersVector()) {
+ for (Player player : getGame().getPlayersList()) {
transmitPlayerUpdate(player);
}
}
@@ -1153,14 +1199,15 @@ void send(Packet packet) {
}
/**
- * Sends the given packet to the given connection (= player ID).
+ * Sends the given packet to the given connection (= player ID) if it is not
+ * null. Does nothing otherwise.
*/
public void send(int connId, Packet packet) {
- if (getClient(connId) != null) {
- getClient(connId).send(packet);
+ AbstractConnection connection = getClient(connId);
+
+ if (connection != null) {
+ connection.send(packet);
}
- // What should we do if we've lost this client?
- // For now, nothing.
}
/**
@@ -1168,6 +1215,7 @@ public void send(int connId, Packet packet) {
*/
private void sendToPending(int connId, Packet packet) {
AbstractConnection pendingConn = getPendingConnection(connId);
+
if (pendingConn != null) {
pendingConn.send(packet);
}
@@ -1206,12 +1254,13 @@ protected void handle(int connId, Packet packet) {
Player player = getGame().getPlayer(connId);
// Check player. Please note, the connection may be pending.
if ((null == player) && (null == getPendingConnection(connId))) {
- LogManager.getLogger().error("Server does not recognize player at connection " + connId);
+ String message = String.format("Server does not recognize player at connection %d", connId);
+ logger.error(message);
return;
}
if (packet == null) {
- LogManager.getLogger().error("Got null packet");
+ logger.error("Got null packet");
return;
}
// act on it
@@ -1221,7 +1270,7 @@ protected void handle(int connId, Packet packet) {
if (valid) {
sendToPending(connId, new Packet(PacketCommand.SERVER_GREETING));
} else {
- sendToPending(connId, new Packet(PacketCommand.ILLEGAL_CLIENT_VERSION, MMConstants.VERSION));
+ sendToPending(connId, new Packet(PacketCommand.ILLEGAL_CLIENT_VERSION, SuiteConstants.VERSION));
getPendingConnection(connId).close();
}
break;
@@ -1273,7 +1322,7 @@ protected void handle(int connId, Packet packet) {
sendCurrentInfo(conn.getId());
}
} catch (Exception e) {
- LogManager.getLogger().error("Error loading save game sent from client", e);
+ logger.error(e, "Error loading save game sent from client");
}
break;
default:
@@ -1286,14 +1335,16 @@ protected void handle(int connId, Packet packet) {
*/
@Override
public void run() {
+ String message = "";
Thread currentThread = Thread.currentThread();
- LogManager.getLogger().info("s: listening for clients...");
+ logger.info("s: listening for clients...");
while (connector == currentThread) {
try {
Socket s = serverSocket.accept();
synchronized (serverLock) {
int id = getFreeConnectionId();
- LogManager.getLogger().info("s: accepting player connection #" + id + "...");
+ message = String.format("s: accepting player connection #%d...", id);
+ logger.info(message);
AbstractConnection c = ConnectionFactory.getInstance().createServerConnection(s, id);
c.addConnectionListener(connectionListener);
@@ -1321,7 +1372,7 @@ public String getHost() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception ex) {
- LogManager.getLogger().error("", ex);
+ logger.error(ex, "Get Host exception");
return "";
}
}
@@ -1334,7 +1385,8 @@ public int getPort() {
}
/**
- * @return the current server instance. This may be null if a server has not been started
+ * @return the current server instance. This may be null if a server has not
+ * been started
*/
public static @Nullable Server getServerInstance() {
return serverInstance;
@@ -1346,41 +1398,44 @@ private void registerWithServerBrowser(boolean register, String urlString) {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
- try (OutputStream os = conn.getOutputStream();
- DataOutputStream dos = new DataOutputStream(os)) {
- String content = "port=" + URLEncoder.encode(Integer.toString(serverSocket.getLocalPort()), StandardCharsets.UTF_8);
- if (register) {
- for (AbstractConnection iconn : connections) {
- content += "&players[]=" + getPlayer(iconn.getId()).getName();
- }
- if (!getGame().getPhase().isLounge() && !getGame().getPhase().isUnknown()) {
- content += "&close=yes";
- }
- content += "&version=" + MMConstants.VERSION;
- if (isPassworded()) {
- content += "&pw=yes";
- }
- } else {
- content += "&delete=yes";
+ OutputStream os = conn.getOutputStream();
+ DataOutputStream dos = new DataOutputStream(os);
+ String content = "port="
+ + URLEncoder.encode(Integer.toString(serverSocket.getLocalPort()), StandardCharsets.UTF_8);
+ if (register) {
+ for (AbstractConnection iconn : connections) {
+ content += "&players[]=" + getPlayer(iconn.getId()).getName();
+ }
+
+ if (!getGame().getPhase().isLounge() && !getGame().getPhase().isUnknown()) {
+ content += "&close=yes";
}
- if (serverAccessKey != null) {
- content += "&key=" + serverAccessKey;
+ content += "&version=" + SuiteConstants.VERSION;
+
+ if (isPassworded()) {
+ content += "&pw=yes";
}
- dos.writeBytes(content);
- dos.flush();
-
- try (InputStream is = conn.getInputStream();
- InputStreamReader isr = new InputStreamReader(is);
- BufferedReader br = new BufferedReader(isr)) {
- String line;
- if (conn.getResponseCode() == 200) {
- while ((line = br.readLine()) != null) {
- if (serverAccessKey == null) {
- serverAccessKey = line;
- }
- }
+ } else {
+ content += "&delete=yes";
+ }
+
+ if (serverAccessKey != null) {
+ content += "&key=" + serverAccessKey;
+ }
+
+ dos.writeBytes(content);
+ dos.flush();
+
+ InputStream is = conn.getInputStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ String line;
+ if (conn.getResponseCode() == 200) {
+ while ((line = br.readLine()) != null) {
+ if (serverAccessKey == null) {
+ serverAccessKey = line;
}
}
}