mirror of https://github.com/FreeCol/freecol.git
1718 lines
61 KiB
Java
1718 lines
61 KiB
Java
/**
|
|
* Copyright (C) 2002-2022 The FreeCol Team
|
|
*
|
|
* This file is part of FreeCol.
|
|
*
|
|
* FreeCol 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.
|
|
*
|
|
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package net.sf.freecol;
|
|
|
|
import static net.sf.freecol.common.util.CollectionUtils.alwaysTrue;
|
|
import static net.sf.freecol.common.util.CollectionUtils.find;
|
|
import static net.sf.freecol.common.util.CollectionUtils.map;
|
|
import static net.sf.freecol.common.util.CollectionUtils.transform;
|
|
import static net.sf.freecol.common.util.StringUtils.upCase;
|
|
|
|
import java.awt.Dimension;
|
|
import java.awt.GraphicsDevice;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.Writer;
|
|
import java.net.Inet4Address;
|
|
import java.net.InetAddress;
|
|
import java.net.JarURLConnection;
|
|
import java.net.URL;
|
|
import java.net.UnknownHostException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.jar.JarFile;
|
|
import java.util.jar.Manifest;
|
|
import java.util.logging.Handler;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import java.util.stream.Collectors;
|
|
import java.util.zip.ZipEntry;
|
|
|
|
import javax.swing.SwingUtilities;
|
|
import javax.xml.stream.XMLStreamException;
|
|
|
|
import org.apache.commons.cli.CommandLine;
|
|
import org.apache.commons.cli.CommandLineParser;
|
|
import org.apache.commons.cli.DefaultParser;
|
|
import org.apache.commons.cli.HelpFormatter;
|
|
import org.apache.commons.cli.Option;
|
|
import org.apache.commons.cli.Options;
|
|
import org.apache.commons.cli.ParseException;
|
|
|
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
|
import net.sf.freecol.client.ClientOptions;
|
|
import net.sf.freecol.client.FreeColClient;
|
|
import net.sf.freecol.client.gui.SplashScreen;
|
|
import net.sf.freecol.common.FreeColException;
|
|
import net.sf.freecol.common.FreeColSeed;
|
|
import net.sf.freecol.common.debug.FreeColDebugger;
|
|
import net.sf.freecol.common.i18n.Messages;
|
|
import net.sf.freecol.common.io.FreeColDirectories;
|
|
import net.sf.freecol.common.io.FreeColModFile;
|
|
import net.sf.freecol.common.io.FreeColRules;
|
|
import net.sf.freecol.common.io.FreeColSavegameFile;
|
|
import net.sf.freecol.common.io.FreeColTcFile;
|
|
import net.sf.freecol.common.logging.DefaultHandler;
|
|
import net.sf.freecol.common.model.Constants.IntegrityType;
|
|
import net.sf.freecol.common.model.NationOptions.Advantages;
|
|
import net.sf.freecol.common.model.Specification;
|
|
import net.sf.freecol.common.model.StringTemplate;
|
|
import net.sf.freecol.common.option.OptionGroup;
|
|
import net.sf.freecol.common.util.LogBuilder;
|
|
import net.sf.freecol.common.util.OSUtils;
|
|
import net.sf.freecol.common.util.Utils;
|
|
import net.sf.freecol.server.FreeColServer;
|
|
import net.sf.freecol.server.control.Controller;
|
|
|
|
|
|
/**
|
|
* This class is responsible for handling the command-line arguments
|
|
* and starting either the stand-alone server or the client-GUI.
|
|
*
|
|
* @see net.sf.freecol.client.FreeColClient FreeColClient
|
|
* @see net.sf.freecol.server.FreeColServer FreeColServer
|
|
*/
|
|
public final class FreeCol {
|
|
|
|
static {
|
|
/*
|
|
* Deactivate automatic UI scaling since we are solving it
|
|
* manually instead.
|
|
*
|
|
* This needs to be done before any Swing/AWT class gets
|
|
* loaded. Please keep this static block right here at the top.
|
|
*/
|
|
System.setProperty("sun.java2d.uiScale", "1.0");
|
|
System.setProperty("sun.java2d.uiScale.enabled", "false");
|
|
}
|
|
|
|
private static final Logger logger = Logger.getLogger(FreeCol.class.getName());
|
|
|
|
/** The FreeCol release version number. */
|
|
private static final String FREECOL_VERSION = "1.0.0+git";
|
|
|
|
/** The FreeCol protocol version number. */
|
|
private static final String FREECOL_PROTOCOL_VERSION = "0.1.6";
|
|
|
|
/** The difficulty levels. */
|
|
private static final String[] DIFFICULTIES = {
|
|
"veryEasy", "easy", "medium", "hard", "veryHard"
|
|
};
|
|
|
|
/** The extension for FreeCol saved games. */
|
|
public static final String FREECOL_SAVE_EXTENSION = "fsg";
|
|
|
|
/** The extension for FreeCol maps. */
|
|
public static final String FREECOL_MAP_EXTENSION = "fsm";
|
|
|
|
/** The Java version. */
|
|
private static final String JAVA_VERSION
|
|
= System.getProperty("java.version");
|
|
|
|
/** The maximum available memory. */
|
|
private static final long MEMORY_MAX = Runtime.getRuntime().maxMemory();
|
|
private static final long MEGA = 1000000; // Metric
|
|
|
|
public static final String CLIENT_THREAD = "FreeColClient:";
|
|
public static final String SERVER_THREAD = "FreeColServer:";
|
|
public static final String METASERVER_THREAD = "FreeColMetaServer:";
|
|
|
|
/** Specific revision number (currently the git tag of trunk at release) */
|
|
private static String freeColRevision = null;
|
|
|
|
/** The locale, either default or command-line specified. */
|
|
private static Locale locale = null;
|
|
|
|
// Cli defaults.
|
|
private static final Advantages ADVANTAGES_DEFAULT = Advantages.SELECTABLE;
|
|
private static final String DIFFICULTY_DEFAULT = "model.difficulty.medium";
|
|
private static final int EUROPEANS_DEFAULT = 4;
|
|
private static final int EUROPEANS_MIN = 1;
|
|
public static final float GUI_SCALE_DEFAULT = 1.0f;
|
|
private static final int GUI_SCALE_MIN_PCT = 100;
|
|
private static final int GUI_SCALE_MAX_PCT = 200;
|
|
public static final float GUI_SCALE_MIN = GUI_SCALE_MIN_PCT / 100.0f;
|
|
public static final float GUI_SCALE_MAX = GUI_SCALE_MAX_PCT / 100.0f;
|
|
private static final int GUI_SCALE_STEP_PCT = 25;
|
|
public static final float GUI_SCALE_STEP = GUI_SCALE_STEP_PCT / 100.0f;
|
|
private static final Level LOGLEVEL_DEFAULT = Level.INFO;
|
|
private static final String JAVA_VERSION_MIN = "11";
|
|
private static final long MEMORY_MIN = 2000000000; // a bit less than 2GB
|
|
private static final String META_SERVER_ADDRESS = "meta.freecol.org";
|
|
private static final int META_SERVER_PORT = 3540;
|
|
private static final int PORT_DEFAULT = 3541;
|
|
private static final String SPLASH_DEFAULT = "splash.jpg";
|
|
private static final String TC_DEFAULT = "default";
|
|
private static final String RULES_DEFAULT = "freecol";
|
|
public static final long TIMEOUT_DEFAULT = 60L; // 1 minute
|
|
public static final long TIMEOUT_MIN = 10L; // 10s
|
|
public static final long TIMEOUT_MAX = 3600000L; // 1000hours:-)
|
|
private static final Dimension WINDOWSIZE_FALLBACK = new Dimension(-1, -1);
|
|
|
|
|
|
// Cli values. Often set to null so the default can be applied in
|
|
// the accessor function.
|
|
private static boolean checkIntegrity = false,
|
|
consoleLogging = false,
|
|
debugStart = false,
|
|
fastStart = false,
|
|
headless = false,
|
|
introVideo = true,
|
|
javaCheck = true,
|
|
memoryCheck = true,
|
|
publicServer = true,
|
|
sound = true,
|
|
standAloneServer = false;
|
|
|
|
/** The type of advantages. */
|
|
private static Advantages advantages = null;
|
|
|
|
/** The difficulty level id. */
|
|
private static String difficulty = null;
|
|
|
|
/** The number of European nations to enable by default. */
|
|
private static int europeanCount = EUROPEANS_DEFAULT;
|
|
|
|
/** A font override. */
|
|
private static String fontName = null;
|
|
|
|
/** The levels of logging in this game. */
|
|
private static class LogLevel {
|
|
|
|
public final String name;
|
|
public final Level level;
|
|
// We need to keep a hard reference to the instantiated logger, as
|
|
// Logger only uses weak references.
|
|
public Logger logger;
|
|
|
|
public LogLevel(String name, Level level) {
|
|
this.name = name;
|
|
this.level = level;
|
|
this.logger = null;
|
|
}
|
|
|
|
public void buildLogger() {
|
|
this.logger = Logger.getLogger("net.sf.freecol"
|
|
+ ((this.name.isEmpty()) ? "" : "." + this.name));
|
|
this.logger.setLevel(this.level);
|
|
}
|
|
}
|
|
private static final List<LogLevel> logLevels = new ArrayList<>();
|
|
static {
|
|
logLevels.add(new LogLevel("", LOGLEVEL_DEFAULT));
|
|
}
|
|
|
|
/** Meta-server location. */
|
|
private static String metaServerAddress = META_SERVER_ADDRESS;
|
|
private static int metaServerPort = META_SERVER_PORT;
|
|
|
|
/** The client player name. */
|
|
private static String name = null;
|
|
|
|
/** How to name and configure the server. */
|
|
private static InetAddress serverAddress = null;
|
|
private static int serverPort = -1;
|
|
private static String serverName = null;
|
|
|
|
/** A stream to get the splash image from. */
|
|
private static InputStream splashStream;
|
|
|
|
/** The TotalConversion in play, defaults to "default". */
|
|
private static String tc = null;
|
|
|
|
/** The ruleset in play, defaults to "freecol". */
|
|
private static String rules = null;
|
|
|
|
/** The time out (seconds) for otherwise blocking commands. */
|
|
private static long timeout = -1L;
|
|
|
|
/**
|
|
* The size of window to create, defaults to impossible dimensions
|
|
* to require windowed mode with best determined screen size.
|
|
*/
|
|
private static Dimension windowSize = WINDOWSIZE_FALLBACK;
|
|
|
|
/** The special client options that must be processed early. */
|
|
private static Map<String,String> specialOptions = null;
|
|
|
|
|
|
private FreeCol() {} // Hide constructor
|
|
|
|
/**
|
|
* The entrypoint.
|
|
*
|
|
* @param args The command-line arguments.
|
|
*/
|
|
public static void main(String[] args) {
|
|
freeColRevision = FREECOL_VERSION;
|
|
JarURLConnection juc;
|
|
|
|
try {
|
|
juc = getJarURLConnection(FreeCol.class);
|
|
} catch (ClassCastException cce) {
|
|
juc = null;
|
|
System.err.println("Unable to cast class properly: "
|
|
+ cce.getMessage());
|
|
} catch (IOException ioe) {
|
|
juc = null;
|
|
System.err.println("Unable to open class jar: "
|
|
+ ioe.getMessage());
|
|
}
|
|
if (juc != null) {
|
|
try {
|
|
String revision = readVersion(juc);
|
|
if (revision != null) {
|
|
freeColRevision += " (Revision: " + revision + ")";
|
|
}
|
|
} catch (Exception e) {
|
|
System.err.println("Unable to load Manifest: "
|
|
+ e.getMessage());
|
|
}
|
|
try {
|
|
splashStream = getDefaultSplashStream(juc);
|
|
} catch (Exception e) {
|
|
System.err.println("Unable to open default splash: "
|
|
+ e.getMessage());
|
|
}
|
|
}
|
|
|
|
// We can not even emit localized error messages until we find
|
|
// the data directory, which might have been specified on the
|
|
// command line.
|
|
String dataDirectoryArg = findArg("--freecol-data", args);
|
|
String err = FreeColDirectories.setDataDirectory(dataDirectoryArg);
|
|
if (err != null) fatal(err); // This must not fail.
|
|
|
|
// Now we have the data directory, establish the base locale.
|
|
// Beware, the locale may change!
|
|
String localeArg = findArg("--default-locale", args);
|
|
setLocale(localeArg);
|
|
Messages.loadMessageBundle(getLocale());
|
|
// Now that we can emit error messages, parse the other
|
|
// command line arguments.
|
|
handleArgs(args);
|
|
|
|
// Do the potentially fatal system checks as early as possible.
|
|
if (javaCheck && JAVA_VERSION_MIN.compareTo(JAVA_VERSION) > 0) {
|
|
fatal(StringTemplate.template("main.javaVersion")
|
|
.addName("%version%", JAVA_VERSION)
|
|
.addName("%minVersion%", JAVA_VERSION_MIN));
|
|
}
|
|
if (memoryCheck && MEMORY_MAX < MEMORY_MIN) {
|
|
// Memory message is in Mbytes, hence division by MEGA.
|
|
fatal(StringTemplate.template("main.memory")
|
|
.addAmount("%memory%", MEMORY_MAX)
|
|
.addAmount("%minMemory%", MEMORY_MIN / MEGA));
|
|
}
|
|
|
|
// Having parsed the command line args, we know where the user
|
|
// directories should be, so we can set up the rest of the
|
|
// file/directory structure. Exit on failure here.
|
|
StringTemplate key = FreeColDirectories.setUserDirectories();
|
|
if (key != null) fatal(key);
|
|
|
|
// Now we have the log file path, start logging.
|
|
final Logger baseLogger = Logger.getLogger("");
|
|
for (Handler handler : baseLogger.getHandlers()) {
|
|
baseLogger.removeHandler(handler);
|
|
}
|
|
try {
|
|
Writer writer = FreeColDirectories.getLogWriter();
|
|
baseLogger.addHandler(new DefaultHandler(consoleLogging, writer));
|
|
for (LogLevel ll : logLevels) ll.buildLogger();
|
|
} catch (FreeColException e) {
|
|
System.err.println("Logging initialization failure: " + e);
|
|
}
|
|
|
|
// This is overridden by FreeColClient.
|
|
Thread.setDefaultUncaughtExceptionHandler((Thread thread, Throwable e) -> {
|
|
baseLogger.log(Level.WARNING, "Uncaught exception from thread: " + thread, e);
|
|
if (e instanceof Error) {
|
|
e.printStackTrace();
|
|
System.exit(1);
|
|
}
|
|
});
|
|
|
|
// Now we can find the client options, allow the options
|
|
// setting to override the locale, but only if no command line
|
|
// option had been specified. This works for our users who,
|
|
// say, have machines that default to Finnish but play FreeCol in
|
|
// English.
|
|
//
|
|
// If the user has selected automatic language selection, do
|
|
// nothing, since we have already set up the default locale.
|
|
try {
|
|
specialOptions = ClientOptions.getSpecialOptions();
|
|
} catch (FreeColException fce) {
|
|
specialOptions = new HashMap<>();
|
|
logger.log(Level.WARNING, "Special options unavailable", fce);
|
|
}
|
|
String cLang;
|
|
if (localeArg == null
|
|
&& (cLang = specialOptions.get(ClientOptions.LANGUAGE)) != null
|
|
&& !Messages.AUTOMATIC.equalsIgnoreCase(cLang)
|
|
&& setLocale(cLang)) {
|
|
Locale loc = getLocale();
|
|
Messages.loadMessageBundle(loc);
|
|
logger.info("Loaded messages for " + loc);
|
|
}
|
|
|
|
// Now we have the user mods directory and the locale is now
|
|
// stable, load the rules, the mods and their messages.
|
|
FreeColRules.loadRules();
|
|
FreeColModFile.loadMods();
|
|
Messages.loadModMessageBundle(getLocale());
|
|
|
|
// Sort out the special graphics options before touching the GUI
|
|
// (which is initialized by FreeColClient). These options control
|
|
// graphics pipeline initialization, and are ineffective if twiddled
|
|
// after the first Java2D call is made.
|
|
headless |= Utils.isHeadless();
|
|
processSpecialOptions();
|
|
|
|
// Report on where we are.
|
|
logger.info(getConfiguration().toString());
|
|
|
|
// Ready to specialize into client or server.
|
|
if (standAloneServer) {
|
|
startServer();
|
|
} else {
|
|
if (headless) {
|
|
if (!FreeColDebugger.isInDebugMode()
|
|
|| FreeColDebugger.getDebugRunTurns() <= 0) {
|
|
fatal(logger, Messages.message("client.headlessDebug"));
|
|
}
|
|
}
|
|
startClient();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the special options that need to be processed early.
|
|
*/
|
|
private static void processSpecialOptions() {
|
|
LogBuilder lb = new LogBuilder(64);
|
|
// Work around a Java 2D bug that seems to be X11 specific.
|
|
// According to:
|
|
// http://www.oracle.com/technetwork/java/javase/index-142560.html
|
|
//
|
|
// ``The use of pixmaps typically results in better
|
|
// performance. However, in certain cases, the opposite is true.''
|
|
//
|
|
// The standard workaround is to use -Dsun.java2d.pmoffscreen=false,
|
|
// but this is too hard for some users, so provide an option to
|
|
// do it easily. However respect the initial value if present.
|
|
//
|
|
// Remove this if Java 2D is ever fixed. DHYB.
|
|
//
|
|
final String pmoffscreen = "sun.java2d.pmoffscreen";
|
|
final String pmoValue = System.getProperty(pmoffscreen);
|
|
if (pmoValue == null) {
|
|
String usePixmaps = specialOptions.get(ClientOptions.USE_PIXMAPS);
|
|
if (usePixmaps != null) {
|
|
System.setProperty(pmoffscreen, usePixmaps);
|
|
lb.add(pmoffscreen, " using client option: ", usePixmaps);
|
|
} else {
|
|
lb.add(pmoffscreen, " unset/ignored: ");
|
|
}
|
|
} else {
|
|
lb.add(pmoffscreen, " overrides client option: ", pmoValue);
|
|
}
|
|
|
|
// There is also this option, BR#3102.
|
|
final String openGL = "sun.java2d.opengl";
|
|
final String openGLValue = System.getProperty(openGL);
|
|
if (openGLValue == null) {
|
|
String useOpenGL = specialOptions.get(ClientOptions.USE_OPENGL);
|
|
if (useOpenGL != null) {
|
|
System.setProperty(openGL, useOpenGL);
|
|
lb.add(", ", openGL, " using client option: ", useOpenGL);
|
|
} else {
|
|
lb.add(", ", openGL, " unset/ignored");
|
|
}
|
|
} else {
|
|
lb.add(", ", openGL, " overrides client option: ", openGLValue);
|
|
}
|
|
|
|
// XRender is available for most unix (not MacOS?)
|
|
if (OSUtils.onUnix()) {
|
|
final String xrender = "sun.java2d.xrender";
|
|
final String xrValue = System.getProperty(xrender);
|
|
if (xrValue == null) {
|
|
String useXR = specialOptions.get(ClientOptions.USE_XRENDER);
|
|
if (useXR != null) {
|
|
System.setProperty(xrender, useXR);
|
|
lb.add(", ", xrender, " using client option: ", useXR);
|
|
} else {
|
|
lb.add(", ", xrender, " unset/ignored");
|
|
}
|
|
} else {
|
|
lb.add(", ", xrender, " overrides client option: ", xrValue);
|
|
}
|
|
}
|
|
|
|
lb.log(logger, Level.INFO);
|
|
}
|
|
|
|
/**
|
|
* Get the JarURLConnection from a class.
|
|
*
|
|
* @param c The {@code Class} to get the connection for.
|
|
* @return The {@code JarURLConnection}.
|
|
* @exception IOException if the connection fails to open.
|
|
*/
|
|
private static JarURLConnection getJarURLConnection(Class c)
|
|
throws IOException {
|
|
String resourceName = "/" + c.getName().replace('.', '/') + ".class";
|
|
URL url = c.getResource(resourceName);
|
|
return (JarURLConnection)url.openConnection();
|
|
}
|
|
|
|
/**
|
|
* Extract the package version from the class.
|
|
*
|
|
* @param juc The {@code JarURLConnection} to extract from.
|
|
* @return A value of the package version attribute.
|
|
* @exception IOException if the manifest is not available.
|
|
*/
|
|
private static String readVersion(JarURLConnection juc) throws IOException {
|
|
Manifest mf = juc.getManifest();
|
|
return (mf == null) ? null
|
|
: mf.getMainAttributes().getValue("Package-Version");
|
|
}
|
|
|
|
/**
|
|
* Get a stream for the default splash file.
|
|
*
|
|
* Note: Not bothering to check for nulls as this is called in try
|
|
* block that ignores all exceptions.
|
|
*
|
|
* @param juc The {@code JarURLConnection} to extract from.
|
|
* @return A suitable {@code InputStream}, or null on error.
|
|
* @exception IOException if the connection fails to open.
|
|
*/
|
|
private static InputStream getDefaultSplashStream(JarURLConnection juc)
|
|
throws IOException {
|
|
JarFile jf = juc.getJarFile();
|
|
ZipEntry ze = jf.getEntry(SPLASH_DEFAULT);
|
|
return jf.getInputStream(ze);
|
|
}
|
|
|
|
/**
|
|
* Quit FreeCol. Route all exits through here.
|
|
*
|
|
* @param status Exit status.
|
|
*/
|
|
@SuppressFBWarnings(value="DM_EXIT", justification="Deliberate")
|
|
public static void quit(int status) {
|
|
System.exit(status);
|
|
}
|
|
|
|
/**
|
|
* Quit and exit with an error.
|
|
*
|
|
* @param logger An optional {@code Logger} to log to.
|
|
* @param err The error message.
|
|
*/
|
|
public static void fatal(Logger logger, String err) {
|
|
if (logger != null) logger.log(Level.SEVERE, err);
|
|
FreeCol.fatal(err);
|
|
}
|
|
|
|
/**
|
|
* Exit printing fatal error message.
|
|
*
|
|
* @param template A {@code StringTemplate} to print.
|
|
*/
|
|
private static void fatal(StringTemplate template) {
|
|
fatal(Messages.message(template));
|
|
}
|
|
|
|
/**
|
|
* Exit printing fatal error message.
|
|
*
|
|
* @param err The error message to print.
|
|
*/
|
|
private static void fatal(String err) {
|
|
if (err == null || err.isEmpty()) {
|
|
err = "Bogus null fatal error message";
|
|
Thread.dumpStack();
|
|
}
|
|
System.err.println(err);
|
|
quit(1);
|
|
}
|
|
|
|
/**
|
|
* Just gripe to System.err.
|
|
*
|
|
* @param template A {@code StringTemplate} to print.
|
|
*/
|
|
private static void gripe(StringTemplate template) {
|
|
System.err.println(Messages.message(template));
|
|
}
|
|
|
|
/**
|
|
* Just gripe to System.err.
|
|
*
|
|
* @param key A message key.
|
|
*/
|
|
private static void gripe(String key) {
|
|
System.err.println(Messages.message(key));
|
|
}
|
|
|
|
/**
|
|
* Log a warning with a stack trace.
|
|
*
|
|
* @param logger The {@code Logger} to log to.
|
|
* @param warn The warning message.
|
|
*/
|
|
public static void trace(Logger logger, String warn) {
|
|
FreeColDebugger.trace(logger, warn);
|
|
}
|
|
|
|
/**
|
|
* Find an option before the real option handling can get started.
|
|
* Takes care to use the *last* instance.
|
|
*
|
|
* @param option The option to find.
|
|
* @param args The command-line arguments.
|
|
* @return The option's parameter.
|
|
*/
|
|
private static String findArg(String option, String[] args) {
|
|
for (int i = args.length - 2; i >= 0; i--) {
|
|
if (option.equals(args[i])) {
|
|
return args[i+1];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Definitions for all the options. */
|
|
private static String argDir = "cli.arg.directory";
|
|
private static String argFile = "cli.arg.file";
|
|
private static String[][] optionsTable = {
|
|
// Help options
|
|
{ "?", "usage", "cli.help", null },
|
|
{ "@", "help", "cli.help", null },
|
|
// Special early options
|
|
{ "d", "freecol-data", "cli.freecol-data", argDir },
|
|
{ "L", "default-locale", "cli.default-locale", "cli.arg.locale" },
|
|
// Ordinary options
|
|
{ "a", "advantages", getAdvantagesDescription(), "cli.arg.advantages" },
|
|
{ null, "check-savegame", "cli.check-savegame", argFile },
|
|
{ "O", "clientOptions", "cli.clientOptions", "cli.arg.clientOptions" },
|
|
{ null, "debug", getDebugDescription(), "cli.arg.debug" },
|
|
{ "R", "debug-run", "cli.debug-run", "cli.arg.debugRun" },
|
|
{ "S", "debug-start", "cli.debug-start", null },
|
|
{ "D", "difficulty", "cli.difficulty", "cli.arg.difficulty" },
|
|
{ "e", "europeans", "cli.european-count", "cli.arg.europeans" },
|
|
{ null, "fast", "cli.fast", null },
|
|
{ "f", "font", "cli.font", "cli.arg.font" },
|
|
{ "F", "full-screen", "cli.full-screen", null },
|
|
{ "H", "headless", "cli.headless", null },
|
|
{ "l", "load-savegame", "cli.load-savegame", argFile },
|
|
{ null, "log-console", "cli.log-console", null },
|
|
{ null, "log-file", "cli.log-file", "cli.arg.name" },
|
|
{ null, "log-level", "cli.log-level", "cli.arg.loglevel" },
|
|
{ "m", "meta-server", "cli.meta-server", "cli.arg.metaServer" },
|
|
{ "n", "name", "cli.name", "cli.arg.name" },
|
|
{ null, "no-intro", "cli.no-intro", null },
|
|
{ null, "no-java-check", "cli.no-java-check", null },
|
|
{ null, "no-memory-check", "cli.no-memory-check", null },
|
|
{ null, "no-sound", "cli.no-sound", null },
|
|
{ null, "no-splash", "cli.no-splash", null },
|
|
{ "p", "private", "cli.private", null },
|
|
{ "r", "rules", "cli.rules", "cli.arg.name" },
|
|
{ "Z", "seed", "cli.seed", "cli.arg.seed" },
|
|
{ null, "server", "cli.server", null },
|
|
{ null, "server-name", "cli.server-name", "cli.arg.name" },
|
|
{ null, "server-ip", "cli.server-ip", "cli.arg.serverIp" },
|
|
{ null, "server-port", "cli.server-port", "cli.arg.port" },
|
|
{ "s", "splash", "cli.splash", "!" + argFile },
|
|
{ "t", "tc", "cli.tc", "cli.arg.name" },
|
|
{ "T", "timeout", "cli.timeout", "cli.arg.timeout" },
|
|
{ "C", "user-cache-directory", "cli.user-cache-directory", argDir },
|
|
{ "c", "user-config-directory", "cli.user-config-directory", argDir },
|
|
{ "u", "user-data-directory", "cli.user-data-directory", argDir },
|
|
{ "v", "version", "cli.version", null },
|
|
{ "w", "windowed", "cli.windowed", "!cli.arg.dimensions" },
|
|
};
|
|
|
|
/**
|
|
* Processes the command-line arguments and takes appropriate
|
|
* actions for each of them.
|
|
*
|
|
* @param args The command-line arguments.
|
|
*/
|
|
private static void handleArgs(String[] args) {
|
|
Options options = new Options();
|
|
for (String[] o : optionsTable) {
|
|
String arg = o[3];
|
|
Option op = new Option(o[0], o[1], arg != null,
|
|
((o[2].startsWith("cli.")) ? Messages.message(o[2]) : o[2]));
|
|
if (arg != null) {
|
|
boolean optional = false;
|
|
if (arg.startsWith("!")) {
|
|
optional = true;
|
|
arg = arg.substring(1, arg.length());
|
|
}
|
|
if (arg.startsWith(argDir)
|
|
|| arg.startsWith(argFile)) op.setType(File.class);
|
|
if (arg.startsWith("cli.")) arg = Messages.message(arg);
|
|
op.setArgName(arg);
|
|
op.setOptionalArg(optional);
|
|
}
|
|
options.addOption(op);
|
|
}
|
|
|
|
CommandLineParser parser = new DefaultParser();
|
|
boolean usageError = false;
|
|
try {
|
|
CommandLine line = parser.parse(options, args);
|
|
if (line.hasOption("help") || line.hasOption("usage")) {
|
|
printUsage(options, 0);
|
|
}
|
|
|
|
// Ignore "default-locale", "freecol-data", which are
|
|
// already handled in main()
|
|
|
|
if (line.hasOption("advantages")) {
|
|
String arg = line.getOptionValue("advantages");
|
|
Advantages a = selectAdvantages(arg);
|
|
if (a == null) {
|
|
fatal(StringTemplate.template("cli.error.advantages")
|
|
.addName("%advantages%", getValidAdvantages())
|
|
.addName("%arg%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("check-savegame")) {
|
|
String arg = line.getOptionValue("check-savegame");
|
|
if (!FreeColDirectories.setSavegameFile(arg)) {
|
|
fatal(StringTemplate.template("cli.err.save")
|
|
.addName("%string%", arg));
|
|
}
|
|
checkIntegrity = true;
|
|
standAloneServer = true;
|
|
}
|
|
|
|
if (line.hasOption("clientOptions")) {
|
|
String fileName = line.getOptionValue("clientOptions");
|
|
if (!FreeColDirectories.setClientOptionsFile(fileName)) {
|
|
// Not fatal.
|
|
gripe(StringTemplate.template("cli.error.clientOptions")
|
|
.addName("%string%", fileName));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("debug")) {
|
|
// If the optional argument is supplied use limited mode.
|
|
String arg = line.getOptionValue("debug");
|
|
if (arg == null || arg.isEmpty()) {
|
|
// Let empty argument default to menus functionality.
|
|
arg = FreeColDebugger.DebugMode.MENUS.toString();
|
|
}
|
|
if (!FreeColDebugger.setDebugModes(arg)) { // Not fatal.
|
|
gripe(StringTemplate.template("cli.error.debug")
|
|
.addName("%modes%", FreeColDebugger.getDebugModes()));
|
|
}
|
|
// Keep doing this before checking log-level option!
|
|
logLevels.add(new LogLevel("", Level.FINEST));
|
|
}
|
|
if (line.hasOption("debug-run")) {
|
|
FreeColDebugger.enableDebugMode(FreeColDebugger.DebugMode.MENUS);
|
|
FreeColDebugger.configureDebugRun(line.getOptionValue("debug-run"));
|
|
}
|
|
if (line.hasOption("debug-start")) {
|
|
debugStart = true;
|
|
FreeColDebugger.enableDebugMode(FreeColDebugger.DebugMode.MENUS);
|
|
}
|
|
|
|
if (line.hasOption("difficulty")) {
|
|
String arg = line.getOptionValue("difficulty");
|
|
String difficulty = selectDifficulty(arg);
|
|
if (difficulty == null) {
|
|
fatal(StringTemplate.template("cli.error.difficulties")
|
|
.addName("%difficulties%", getValidDifficulties())
|
|
.addName("%arg%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("europeans")) {
|
|
int e = selectEuropeanCount(line.getOptionValue("europeans"));
|
|
if (e < 0) {
|
|
gripe(StringTemplate.template("cli.error.europeans")
|
|
.addAmount("%min%", EUROPEANS_MIN));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("fast")) {
|
|
fastStart = true;
|
|
introVideo = false;
|
|
}
|
|
|
|
if (line.hasOption("font")) {
|
|
fontName = line.getOptionValue("font");
|
|
}
|
|
|
|
if (line.hasOption("full-screen")) {
|
|
windowSize = null;
|
|
}
|
|
|
|
if (line.hasOption("headless")) {
|
|
headless = true;
|
|
}
|
|
|
|
if (line.hasOption("load-savegame")) {
|
|
String arg = line.getOptionValue("load-savegame");
|
|
if (!FreeColDirectories.setSavegameFile(arg)) {
|
|
fatal(StringTemplate.template("cli.error.save")
|
|
.addName("%string%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("log-console")) {
|
|
consoleLogging = true;
|
|
}
|
|
|
|
if (line.hasOption("log-file")) {
|
|
FreeColDirectories.setLogFilePath(line.getOptionValue("log-file"));
|
|
}
|
|
|
|
if (line.hasOption("log-level")) {
|
|
for (String value : line.getOptionValues("log-level")) {
|
|
String[] s = value.split(":");
|
|
logLevels.add((s.length == 1)
|
|
? new LogLevel("", Level.parse(upCase(s[0])))
|
|
: new LogLevel(s[0], Level.parse(upCase(s[1]))));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("meta-server")) {
|
|
String arg = line.getOptionValue("meta-server");
|
|
if (!setMetaServer(arg)) {
|
|
gripe(StringTemplate.template("cli.error.meta-server")
|
|
.addName("%arg%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("name")) {
|
|
setName(line.getOptionValue("name"));
|
|
}
|
|
|
|
if (line.hasOption("no-intro")) {
|
|
introVideo = false;
|
|
}
|
|
if (line.hasOption("no-java-check")) {
|
|
javaCheck = false;
|
|
}
|
|
if (line.hasOption("no-memory-check")) {
|
|
memoryCheck = false;
|
|
}
|
|
if (line.hasOption("no-sound")) {
|
|
sound = false;
|
|
}
|
|
if (line.hasOption("no-splash")) {
|
|
splashStream = null;
|
|
}
|
|
|
|
if (line.hasOption("private")) {
|
|
publicServer = false;
|
|
}
|
|
|
|
if (line.hasOption("server")) {
|
|
standAloneServer = true;
|
|
}
|
|
if (line.hasOption("server-name")) {
|
|
serverName = line.getOptionValue("server-name");
|
|
}
|
|
if (line.hasOption("server-port")) {
|
|
String arg = line.getOptionValue("server-port");
|
|
if (!setServerPort(arg)) {
|
|
fatal(StringTemplate.template("cli.error.serverPort")
|
|
.addName("%string%", arg));
|
|
}
|
|
}
|
|
if (line.hasOption("server-ip")) {
|
|
String arg = line.getOptionValue("server-ip");
|
|
if (!setServerAddress(arg)) {
|
|
fatal(StringTemplate.template("cli.error.serverIp")
|
|
.addName("%string%", arg));
|
|
}
|
|
}
|
|
|
|
boolean seeded = (line.hasOption("seed")
|
|
&& FreeColSeed.setFreeColSeed(line.getOptionValue("seed")));
|
|
if (!seeded) FreeColSeed.generateFreeColSeed();
|
|
|
|
if (line.hasOption("splash")) {
|
|
String splash = line.getOptionValue("splash");
|
|
try {
|
|
InputStream fis = Files.newInputStream(Paths.get(splash));
|
|
splashStream = fis;
|
|
} catch (IOException ioe) {
|
|
gripe(StringTemplate.template("cli.error.splash")
|
|
.addName("%name%", splash));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("tc")) {
|
|
setTc(line.getOptionValue("tc")); // Failure is deferred.
|
|
}
|
|
|
|
if (line.hasOption("rules")) {
|
|
setRules(line.getOptionValue("rules")); // Failure is deferred.
|
|
}
|
|
|
|
if (line.hasOption("timeout")) {
|
|
String arg = line.getOptionValue("timeout");
|
|
try {
|
|
setTimeout(arg); // Not fatal
|
|
} catch (NumberFormatException nfe) {
|
|
gripe(StringTemplate.template("cli.error.timeout")
|
|
.addName("%string%", arg)
|
|
.addName("%minimum%", Long.toString(TIMEOUT_MIN)));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("user-cache-directory")) {
|
|
String arg = line.getOptionValue("user-cache-directory");
|
|
String errMsg = FreeColDirectories.setUserCacheDirectory(arg);
|
|
if (errMsg != null) { // Not fatal.
|
|
gripe(StringTemplate.template(errMsg)
|
|
.addName("%string%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("user-config-directory")) {
|
|
String arg = line.getOptionValue("user-config-directory");
|
|
String errMsg = FreeColDirectories.setUserConfigDirectory(arg);
|
|
if (errMsg != null) { // Not fatal.
|
|
gripe(StringTemplate.template(errMsg)
|
|
.addName("%string%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("user-data-directory")) {
|
|
String arg = line.getOptionValue("user-data-directory");
|
|
String errMsg = FreeColDirectories.setUserDataDirectory(arg);
|
|
if (errMsg != null) { // Fatal, unable to save.
|
|
fatal(StringTemplate.template(errMsg)
|
|
.addName("%string%", arg));
|
|
}
|
|
}
|
|
|
|
if (line.hasOption("version")) {
|
|
System.out.println("FreeCol " + getVersion());
|
|
quit(0);
|
|
}
|
|
|
|
if (line.hasOption("windowed")) {
|
|
String arg = line.getOptionValue("windowed");
|
|
setWindowSize(arg); // Does not fail
|
|
}
|
|
|
|
} catch (ParseException e) {
|
|
System.err.println("\n" + e.getMessage() + "\n");
|
|
usageError = true;
|
|
}
|
|
if (usageError) printUsage(options, 1);
|
|
}
|
|
|
|
/**
|
|
* Prints the usage message and exits.
|
|
*
|
|
* @param options The command line {@code Options}.
|
|
* @param status The status to exit with.
|
|
*/
|
|
private static void printUsage(Options options, int status) {
|
|
HelpFormatter formatter = new HelpFormatter();
|
|
formatter.printHelp("java -Xmx2G -jar freecol.jar [OPTIONS]",
|
|
options);
|
|
quit(status);
|
|
}
|
|
|
|
/**
|
|
* Get the specification from a given TC file.
|
|
*
|
|
* @param rulesFile The {@code FreeColModFile} to load.
|
|
* @param advantages An optional {@code Advantages} setting.
|
|
* @param difficulty An optional difficulty level.
|
|
* @return A {@code Specification}.
|
|
*/
|
|
public static Specification loadSpecification(FreeColModFile rulesFile,
|
|
Advantages advantages,
|
|
String difficulty) {
|
|
Specification spec = null;
|
|
try {
|
|
if (rulesFile != null) spec = rulesFile.getSpecification();
|
|
} catch (IOException|XMLStreamException ex) {
|
|
System.err.println("Spec read failed in " + rulesFile.getId()
|
|
+ ": " + ex.getMessage() + "\n");
|
|
}
|
|
if (spec != null) spec.prepare(advantages, difficulty);
|
|
return spec;
|
|
}
|
|
|
|
/**
|
|
* Get the specification from the currently selected rules.
|
|
*
|
|
* @return A {@code Specification}, quits on error.
|
|
*/
|
|
private static Specification getRulesSpecification() {
|
|
Specification spec = loadSpecification(getRulesFile(), getAdvantages(),
|
|
getDifficulty());
|
|
if (spec == null) {
|
|
fatal(StringTemplate.template("cli.error.badTC")
|
|
.addName("%tc%", getRules()));
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
// Accessors, mutators and support for the cli variables.
|
|
|
|
/**
|
|
* Are we in headless mode, either externally true or explicitly
|
|
* from the command line.
|
|
*
|
|
* @return True if in headless mode.
|
|
*/
|
|
public static boolean getHeadless() {
|
|
return headless;
|
|
}
|
|
|
|
/**
|
|
* Set the headless state.
|
|
*
|
|
* Used by the test suite.
|
|
*
|
|
* @param newHeadless The new headless state.
|
|
*/
|
|
public static void setHeadless(boolean newHeadless) {
|
|
headless = newHeadless;
|
|
}
|
|
|
|
/**
|
|
* Gets the default advantages type.
|
|
*
|
|
* @return Usually Advantages.SELECTABLE, but can be overridden at the
|
|
* command line.
|
|
*/
|
|
public static Advantages getAdvantages() {
|
|
return (advantages == null) ? ADVANTAGES_DEFAULT
|
|
: advantages;
|
|
}
|
|
|
|
/**
|
|
* Sets the advantages type.
|
|
*
|
|
* Called from NewPanel when a selection is made.
|
|
*
|
|
* @param as The name of the new advantages type.
|
|
* @return The type of advantages set, or null if none.
|
|
*/
|
|
private static Advantages selectAdvantages(String as) {
|
|
Advantages a = find(Advantages.values(), Messages.matchesNamed(as));
|
|
if (a != null) setAdvantages(a);
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* Sets the advantages type.
|
|
*
|
|
* @param advantages The new {@code Advantages} type.
|
|
*/
|
|
public static void setAdvantages(Advantages advantages) {
|
|
FreeCol.advantages = advantages;
|
|
}
|
|
|
|
/**
|
|
* Gets a comma separated list of localized advantage type names.
|
|
*
|
|
* @return A list of advantage types.
|
|
*/
|
|
private static String getValidAdvantages() {
|
|
return transform(Advantages.values(), alwaysTrue(),
|
|
a -> Messages.getName(a), Collectors.joining(","));
|
|
}
|
|
|
|
/**
|
|
* Get a description for the advantages argument.
|
|
*
|
|
* @return A suitable description.
|
|
*/
|
|
private static String getAdvantagesDescription() {
|
|
return Messages.message(StringTemplate.template("cli.advantages")
|
|
.addName("%advantages%", getValidAdvantages()));
|
|
}
|
|
|
|
/**
|
|
* Get a description for the debug argument.
|
|
*
|
|
* @return A suitable description.
|
|
*/
|
|
private static String getDebugDescription() {
|
|
return Messages.message(StringTemplate.template("cli.debug")
|
|
.addName("%modes%", FreeColDebugger.getDebugModes()));
|
|
}
|
|
|
|
/**
|
|
* Gets the difficulty level.
|
|
*
|
|
* @return The name of a difficulty level.
|
|
*/
|
|
public static String getDifficulty() {
|
|
return (difficulty == null) ? DIFFICULTY_DEFAULT : difficulty;
|
|
}
|
|
|
|
/**
|
|
* Selects a difficulty level.
|
|
*
|
|
* @param arg The supplied difficulty argument.
|
|
* @return The name of the selected difficulty, or null if none.
|
|
*/
|
|
private static String selectDifficulty(String arg) {
|
|
String difficulty
|
|
= find(map(DIFFICULTIES, d -> "model.difficulty." + d),
|
|
Messages.matchesName(arg));
|
|
if (difficulty != null) setDifficulty(difficulty);
|
|
return difficulty;
|
|
}
|
|
|
|
/**
|
|
* Sets the difficulty level.
|
|
*
|
|
* @param difficulty The actual {@code OptionGroup}
|
|
* containing the difficulty level.
|
|
*/
|
|
public static void setDifficulty(OptionGroup difficulty) {
|
|
setDifficulty(difficulty.getId());
|
|
}
|
|
|
|
/**
|
|
* Sets the difficulty level.
|
|
*
|
|
* @param difficulty The new difficulty.
|
|
*/
|
|
public static void setDifficulty(String difficulty) {
|
|
FreeCol.difficulty = difficulty;
|
|
}
|
|
|
|
/**
|
|
* Gets the names of the valid difficulty levels.
|
|
*
|
|
* @return The valid difficulty levels, comma separated.
|
|
*/
|
|
public static String getValidDifficulties() {
|
|
return transform(DIFFICULTIES, alwaysTrue(),
|
|
d -> Messages.getName("model.difficulty." + d),
|
|
Collectors.joining(","));
|
|
}
|
|
|
|
/**
|
|
* Get the number of European nations to enable by default.
|
|
*
|
|
* @return The default European nation count.
|
|
*/
|
|
public static int getEuropeanCount() {
|
|
return europeanCount;
|
|
}
|
|
|
|
/**
|
|
* Sets the number of enabled European nations.
|
|
*
|
|
* @param n The number of nations to enable.
|
|
*/
|
|
public static void setEuropeanCount(int n) {
|
|
europeanCount = n;
|
|
}
|
|
|
|
/**
|
|
* Gets the valid scale factors for the GUI.
|
|
*
|
|
* @return A string containing these.
|
|
*/
|
|
public static String getValidGUIScales() {
|
|
StringBuilder sb = new StringBuilder(64);
|
|
for (int i = GUI_SCALE_MIN_PCT; i <= GUI_SCALE_MAX_PCT;
|
|
i += GUI_SCALE_STEP_PCT) sb.append(i).append(',');
|
|
sb.setLength(sb.length()-1);
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Selects a European nation count.
|
|
*
|
|
* @param arg The supplied count argument.
|
|
* @return A valid nation number, or negative on error.
|
|
*/
|
|
private static int selectEuropeanCount(String arg) {
|
|
try {
|
|
int n = Integer.parseInt(arg);
|
|
if (n >= EUROPEANS_MIN) {
|
|
setEuropeanCount(n);
|
|
return n;
|
|
}
|
|
} catch (NumberFormatException nfe) {}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get the meta-server address.
|
|
*
|
|
* @return The current meta-server address.
|
|
*/
|
|
public static String getMetaServerAddress() {
|
|
return metaServerAddress;
|
|
}
|
|
|
|
/**
|
|
* Get the meta-server port.
|
|
*
|
|
* @return The current meta-server port.
|
|
*/
|
|
public static int getMetaServerPort() {
|
|
return metaServerPort;
|
|
}
|
|
|
|
/**
|
|
* Set the meta-server location.
|
|
*
|
|
* @param arg The new meta-server location in HOST:PORT format.
|
|
* @return True if the location was set.
|
|
*/
|
|
private static boolean setMetaServer(String arg) {
|
|
String[] s = arg.split(":");
|
|
int port = -1;
|
|
try {
|
|
port = (s.length == 2) ? Integer.parseInt(s[1]) : -1;
|
|
} catch (NumberFormatException nfe) {}
|
|
if (s.length != 2 || s[0] == null || "".equals(s[0])) return false;
|
|
|
|
metaServerAddress = s[0];
|
|
metaServerPort = port;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets the user name.
|
|
*
|
|
* @return The user name, defaults to the user.name property, then to
|
|
* the "main.defaultPlayerName" message value.
|
|
*/
|
|
public static String getName() {
|
|
return (name != null) ? name
|
|
: System.getProperty("user.name",
|
|
Messages.message("main.defaultPlayerName"));
|
|
}
|
|
|
|
/**
|
|
* Sets the user name.
|
|
*
|
|
* @param name The new user name.
|
|
*/
|
|
public static void setName(String name) {
|
|
FreeCol.name = name;
|
|
logger.info("Set FreeCol.name = " + name);
|
|
}
|
|
|
|
/**
|
|
* Get the selected locale.
|
|
*
|
|
* @return The {@code Locale} currently in use.
|
|
*/
|
|
public static Locale getLocale() {
|
|
return (FreeCol.locale == null) ? Locale.getDefault() : FreeCol.locale;
|
|
}
|
|
|
|
/**
|
|
* Set the locale.
|
|
*
|
|
* Public for the test suite.
|
|
*
|
|
* @param localeArg The locale specification, null implies the
|
|
* default locale.
|
|
* @return True if the {@code Locale} changed.
|
|
*/
|
|
@SuppressFBWarnings(value="MDM_SETDEFAULTLOCALE",
|
|
justification="Locale can be reset by user")
|
|
public static boolean setLocale(String localeArg) {
|
|
Locale newLocale = null;
|
|
if (localeArg == null) {
|
|
newLocale = Locale.getDefault();
|
|
} else {
|
|
int index = localeArg.indexOf('.'); // Strip encoding if present
|
|
if (index > 0) localeArg = localeArg.substring(0, index);
|
|
newLocale = Messages.getLocale(localeArg);
|
|
}
|
|
if (newLocale != FreeCol.locale) {
|
|
FreeCol.locale = newLocale;
|
|
Locale.setDefault(newLocale);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the current revision of game.
|
|
*
|
|
* @return The current version and SVN Revision of the game.
|
|
*/
|
|
public static String getRevision() {
|
|
return freeColRevision;
|
|
}
|
|
|
|
/**
|
|
* Get the default server host name.
|
|
*
|
|
* @return The host name.
|
|
*/
|
|
public static String getServerHost() {
|
|
try {
|
|
return InetAddress.getLocalHost().getHostAddress();
|
|
} catch (Exception e) {}
|
|
return InetAddress.getLoopbackAddress().getHostAddress();
|
|
}
|
|
|
|
/**
|
|
* Gets the server network port.
|
|
*
|
|
* @return The port number the server will be listening on.
|
|
*/
|
|
public static int getServerPort() {
|
|
return (serverPort < 0) ? PORT_DEFAULT : serverPort;
|
|
}
|
|
|
|
/**
|
|
* Gets the server address.
|
|
*
|
|
* @return The {@code InetAddress} the server will be listening on.
|
|
*/
|
|
public static InetAddress getServerName() {
|
|
try {
|
|
return (serverAddress == null) ? InetAddress.getByName("0.0.0.0") : serverAddress;
|
|
} catch (UnknownHostException e) {
|
|
return Inet4Address.getLoopbackAddress();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the server port.
|
|
*
|
|
* @param arg The server port number.
|
|
* @return True if the port was set.
|
|
*/
|
|
private static boolean setServerPort(String arg) {
|
|
if (arg == null) return false;
|
|
try {
|
|
serverPort = Integer.parseInt(arg);
|
|
} catch (NumberFormatException nfe) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sets the server address.
|
|
*
|
|
* @param arg The server address.
|
|
* @return True if the address was set.
|
|
*/
|
|
private static boolean setServerAddress(String arg) {
|
|
if (arg == null) return false;
|
|
try {
|
|
serverAddress = InetAddress.getByName(arg);
|
|
} catch (UnknownHostException e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets the current Total-Conversion.
|
|
*
|
|
* @return Usually TC_DEFAULT, but can be overridden at the command line.
|
|
*/
|
|
public static String getTc() {
|
|
return (tc == null) ? TC_DEFAULT : tc;
|
|
}
|
|
|
|
/**
|
|
* Sets the Total-Conversion.
|
|
*
|
|
* Called from NewPanel when a selection is made.
|
|
*
|
|
* @param tc The name of the new total conversion.
|
|
*/
|
|
public static void setTc(String tc) {
|
|
FreeCol.tc = tc;
|
|
}
|
|
|
|
/**
|
|
* Gets the current rules
|
|
*
|
|
* @return Usually RULES_DEFAULT, but can be overridden at the command line.
|
|
*/
|
|
public static String getRules() {
|
|
return (rules == null) ? RULES_DEFAULT : rules;
|
|
}
|
|
|
|
/**
|
|
* Sets the rules.
|
|
*
|
|
* Called from NewPanel when a selection is made.
|
|
*
|
|
* @param tc The name of the new total conversion.
|
|
*/
|
|
public static void setRules(String tc) {
|
|
FreeCol.rules = tc;
|
|
}
|
|
|
|
/**
|
|
* Gets the FreeColModFile for the current rules.
|
|
*
|
|
* @return The {@code FreeColModFile}.
|
|
*/
|
|
public static FreeColModFile getRulesFile() {
|
|
return FreeColRules.getFreeColRulesFile(getRules());
|
|
}
|
|
|
|
/**
|
|
* Gets the timeout.
|
|
* Use the command line specified one if any, otherwise default
|
|
* to `infinite' in single player and the TIMEOUT_DEFAULT for
|
|
* multiplayer.
|
|
*
|
|
* @param singlePlayer True if this is a single player game.
|
|
* @return A suitable timeout value.
|
|
*/
|
|
public static long getTimeout(boolean singlePlayer) {
|
|
if (timeout < 0L) {
|
|
timeout = (singlePlayer) ? TIMEOUT_MAX : TIMEOUT_DEFAULT;
|
|
}
|
|
return timeout;
|
|
}
|
|
|
|
/**
|
|
* Sets the timeout.
|
|
*
|
|
* @param timeout A string containing the new timeout.
|
|
*/
|
|
public static void setTimeout(String timeout) {
|
|
long result = Long.parseLong(timeout);
|
|
if (TIMEOUT_MIN <= result && result <= TIMEOUT_MAX) {
|
|
FreeCol.timeout = result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current version of game.
|
|
*
|
|
* @return The current version of the game using the format "x.y.z",
|
|
* where "x" is major, "y" is minor and "z" is revision.
|
|
*/
|
|
public static String getVersion() {
|
|
return FREECOL_VERSION;
|
|
}
|
|
|
|
/**
|
|
* Gets the current version of the FreeCol protocol.
|
|
*
|
|
* @return The version of the FreeCol protocol.
|
|
*/
|
|
public static String getFreeColProtocolVersion() {
|
|
return FREECOL_PROTOCOL_VERSION;
|
|
}
|
|
|
|
/**
|
|
* Sets the window size.
|
|
*
|
|
* Does not fail because any empty or invalid value is interpreted as
|
|
* `windowed but use as much screen as possible'.
|
|
*
|
|
* @param arg The window size specification.
|
|
*/
|
|
private static void setWindowSize(String arg) {
|
|
windowSize = WINDOWSIZE_FALLBACK; // Set fallback up front
|
|
if (arg != null) {
|
|
String[] xy = arg.split("[^0-9]");
|
|
if (xy.length == 2) {
|
|
try {
|
|
windowSize = new Dimension(Integer.parseInt(xy[0]),
|
|
Integer.parseInt(xy[1]));
|
|
} catch (NumberFormatException nfe) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate a failure message depending on a file parameter.
|
|
*
|
|
* @param messageId The failure message identifier.
|
|
* @param file The {@code File} that caused the failure.
|
|
* @return A {@code StringTemplate} with the error message.
|
|
*/
|
|
public static StringTemplate badFile(String messageId, File file) {
|
|
return StringTemplate.template(messageId)
|
|
.addName("%name%", (file == null) ? "-" : file.getPath());
|
|
}
|
|
|
|
/**
|
|
* Build an error template from an exception.
|
|
*
|
|
* @param ex The {@code Exception} to make an error from.
|
|
* @param fallbackKey A message key to use to make a fallback message
|
|
* if the exception is unsuitable.
|
|
* @return An error {@code StringTemplate}.
|
|
*/
|
|
public static StringTemplate errorFromException(Exception ex,
|
|
String fallbackKey) {
|
|
return errorFromException(ex, StringTemplate.template(fallbackKey));
|
|
}
|
|
|
|
/**
|
|
* Build an error template from an exception.
|
|
*
|
|
* @param ex The {@code Exception} to make an error from.
|
|
* @param fallback A {@code StringTemplate} to use as a fall
|
|
* back if the exception is unsuitable.
|
|
* @return An error {@code StringTemplate}.
|
|
*/
|
|
public static StringTemplate errorFromException(Exception ex,
|
|
StringTemplate fallback) {
|
|
String msg;
|
|
return (ex == null || (msg = ex.getMessage()) == null)
|
|
? fallback
|
|
: (Messages.containsKey(msg))
|
|
? StringTemplate.template(msg)
|
|
: (FreeColDebugger.isInDebugMode(FreeColDebugger.DebugMode.MENUS))
|
|
? StringTemplate.name(msg)
|
|
: fallback;
|
|
}
|
|
|
|
/**
|
|
* We get a lot of lame bug reports with insufficient configuration
|
|
* information. Get a buffer containing as much information as we can
|
|
* to embed in the log file and saved games.
|
|
*
|
|
* @return A {@code StringBuilder} full of configuration information.
|
|
*/
|
|
public static StringBuilder getConfiguration() {
|
|
File autosave = FreeColDirectories.getAutosaveDirectory();
|
|
File clientOptionsFile = FreeColDirectories.getClientOptionsFile();
|
|
File save = FreeColDirectories.getSaveDirectory();
|
|
File userConfig = FreeColDirectories.getUserConfigDirectory();
|
|
File userData = FreeColDirectories.getUserDataDirectory();
|
|
File userMods = FreeColDirectories.getUserModsDirectory();
|
|
StringBuilder sb = new StringBuilder(256);
|
|
sb.append("Configuration:")
|
|
.append("\n version ").append(getRevision())
|
|
.append("\n java: ").append(JAVA_VERSION)
|
|
.append("\n memory: ").append(MEMORY_MAX)
|
|
.append("\n locale: ").append(getLocale())
|
|
.append("\n data: ")
|
|
.append(FreeColDirectories.getDataDirectory().getPath())
|
|
.append("\n userConfig: ")
|
|
.append((userConfig == null) ? "NONE" : userConfig.getPath())
|
|
.append("\n userData: ")
|
|
.append((userData == null) ? "NONE" : userData.getPath())
|
|
.append("\n autosave: ")
|
|
.append((autosave == null) ? "NONE" : autosave.getPath())
|
|
.append("\n logFile: ")
|
|
.append(FreeColDirectories.getLogFilePath())
|
|
.append("\n options: ")
|
|
.append((clientOptionsFile == null) ? "NONE"
|
|
: clientOptionsFile.getPath())
|
|
.append("\n save: ")
|
|
.append((save == null) ? "NONE" : save.getPath())
|
|
.append("\n userMods: ")
|
|
.append((userMods == null) ? "NONE" : userMods.getPath())
|
|
.append("\n seed: ").append(FreeColSeed.getFreeColSeed())
|
|
.append("\n debug: ")
|
|
.append(FreeColDebugger.getDebugModes());
|
|
return sb;
|
|
}
|
|
|
|
|
|
// The major final actions.
|
|
|
|
/**
|
|
* Start a client.
|
|
*/
|
|
private static void startClient() {
|
|
SwingUtilities.invokeLater(() -> {
|
|
/*
|
|
* Please do NOT move the splash screen into SwingGUI again. Make a separate
|
|
* interface if this needs to be pluggable.
|
|
*
|
|
* The Swing-classes need to be constructed in the EDT -- and the splash screen
|
|
* is never displayed correctly if SwingGUI is loaded correctly in the EDT.
|
|
*
|
|
* In addition, the splash screen should not be displayed at the same time
|
|
* as a fullscreen frame.
|
|
*/
|
|
|
|
final SplashScreen splashScreen = createSplashScreen();
|
|
SwingUtilities.invokeLater(() -> {
|
|
Specification spec = null;
|
|
File savegame = FreeColDirectories.getSavegameFile();
|
|
if (debugStart) {
|
|
spec = FreeCol.getRulesSpecification();
|
|
} else if (fastStart) {
|
|
if (savegame == null) {
|
|
// continue last saved game if possible,
|
|
// otherwise start a new one
|
|
savegame = FreeColDirectories.getLastSaveGameFile();
|
|
if (savegame == null) {
|
|
spec = FreeCol.getRulesSpecification();
|
|
}
|
|
}
|
|
// savegame was specified on command line
|
|
}
|
|
|
|
new FreeColClient(splashScreen, fontName, windowSize,
|
|
(String)null, sound, introVideo, savegame, spec);
|
|
});
|
|
});
|
|
|
|
|
|
}
|
|
|
|
private static SplashScreen createSplashScreen() {
|
|
SplashScreen splashScreen = null;
|
|
if (splashStream != null) {
|
|
try {
|
|
final GraphicsDevice defaultScreenDevice = Utils.getGoodGraphicsDevice();
|
|
splashScreen = new SplashScreen(defaultScreenDevice, splashStream);
|
|
splashScreen.setVisible(true);
|
|
} catch (Exception e) {
|
|
logger.log(Level.WARNING, "Splash screen failure", e);
|
|
}
|
|
}
|
|
return splashScreen;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for the test suite to start a test client.
|
|
*
|
|
* @param spec The {@code Specification} to use in the new client.
|
|
* @return The new {@code FreeColClient}.
|
|
*/
|
|
public static FreeColClient startTestClient(Specification spec) {
|
|
FreeCol.setHeadless(true);
|
|
return new FreeColClient(null, (String)null, (Dimension)null,
|
|
(String)null, false, false, (File)null, spec);
|
|
}
|
|
|
|
/**
|
|
* Check the integrity of a FreeCol server.
|
|
*
|
|
* @param freeColServer The server to check.
|
|
*/
|
|
private static void checkServerIntegrity(FreeColServer freeColServer) {
|
|
String key;
|
|
int ret;
|
|
IntegrityType integ = IntegrityType.INTEGRITY_GOOD;
|
|
if (freeColServer == null) {
|
|
logger.warning("Integrity test blocked");
|
|
integ = integ.fail();
|
|
} else {
|
|
integ = freeColServer.getIntegrity();
|
|
}
|
|
switch (integ) {
|
|
case INTEGRITY_GOOD:
|
|
key = "cli.check-savegame.success";
|
|
ret = 0;
|
|
break;
|
|
case INTEGRITY_FIXED:
|
|
key = "cli.check-savegame.fixed";
|
|
ret = 2;
|
|
break;
|
|
default:
|
|
key = "cli.check-savegame.failed";
|
|
ret = 3;
|
|
break;
|
|
}
|
|
gripe(StringTemplate.template(key)
|
|
.add("%log%", FreeColDirectories.getLogFilePath()));
|
|
quit(ret);
|
|
}
|
|
|
|
/**
|
|
* Start the server.
|
|
*/
|
|
private static void startServer() {
|
|
logger.info("Starting stand-alone server.");
|
|
FreeColServer freeColServer;
|
|
File saveGame = FreeColDirectories.getSavegameFile();
|
|
if (saveGame != null) {
|
|
try {
|
|
final FreeColSavegameFile fis
|
|
= new FreeColSavegameFile(saveGame);
|
|
freeColServer = new FreeColServer(fis, (Specification)null,
|
|
serverAddress, serverPort, serverName);
|
|
freeColServer.setPublicServer(publicServer);
|
|
if (!freeColServer.registerWithMetaServer()) {
|
|
fatal(Messages.message("server.noRouteToServer"));
|
|
}
|
|
} catch (Exception e) {
|
|
logger.log(Level.SEVERE, "Load fail", e);
|
|
fatal(Messages.message(badFile("error.couldNotLoad", saveGame))
|
|
+ ": " + e);
|
|
freeColServer = null;
|
|
}
|
|
if (checkIntegrity) checkServerIntegrity(freeColServer);
|
|
if (freeColServer == null) return;
|
|
} else {
|
|
Specification spec = FreeCol.getRulesSpecification();
|
|
try {
|
|
freeColServer = new FreeColServer(publicServer, false, spec,
|
|
serverAddress, serverPort, serverName);
|
|
if (!freeColServer.registerWithMetaServer()) {
|
|
fatal(Messages.message("server.noRouteToServer"));
|
|
}
|
|
} catch (Exception e) {
|
|
fatal(Messages.message("server.initialize")
|
|
+ ": " + e.getMessage());
|
|
return;
|
|
}
|
|
if (publicServer && !freeColServer.getPublicServer()) {
|
|
gripe(Messages.message("server.noRouteToServer"));
|
|
}
|
|
}
|
|
|
|
String quit = FreeCol.SERVER_THREAD + "Quit Game";
|
|
final Controller controller = freeColServer.getController();
|
|
Runtime.getRuntime().addShutdownHook(new Thread(quit) {
|
|
@Override
|
|
public void run() {
|
|
controller.shutdown();
|
|
}
|
|
});
|
|
}
|
|
}
|