Fix issue when exiting server when plugin manager hasn't loaded

This commit is contained in:
KingRainbow44 2022-06-22 00:09:31 -04:00
parent 91d232d625
commit 6175e957b5

View File

@ -1,25 +1,34 @@
package emu.grasscutter; package emu.grasscutter;
import java.io.*; import ch.qos.logback.classic.Logger;
import java.util.Calendar; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.auth.DefaultAuthentication;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.DefaultPermissionHandler; import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.game.managers.energy.EnergyManager; import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.PluginManager;
import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler; import emu.grasscutter.server.http.dispatch.DispatchHandler;
import emu.grasscutter.server.http.handlers.*;
import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.server.http.dispatch.RegionHandler;
import emu.grasscutter.server.http.documentation.DocumentationServerHandler; import emu.grasscutter.server.http.documentation.DocumentationServerHandler;
import emu.grasscutter.server.http.handlers.AnnouncementsHandler;
import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.server.http.handlers.GenericHandler;
import emu.grasscutter.server.http.handlers.LogHandler;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.ConfigContainer; import emu.grasscutter.utils.ConfigContainer;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import org.jline.reader.EndOfFileException; import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader; import org.jline.reader.LineReader;
@ -30,349 +39,343 @@ import org.jline.terminal.TerminalBuilder;
import org.reflections.Reflections; import org.reflections.Reflections;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.utils.Language;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.*;
import java.util.Calendar;
import static emu.grasscutter.Configuration.DATA;
import static emu.grasscutter.Configuration.SERVER;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import static emu.grasscutter.Configuration.*;
public final class Grasscutter { public final class Grasscutter {
private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
private static LineReader consoleLineReader = null; private static LineReader consoleLineReader = null;
private static Language language;
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private static Language language;
public static final File configFile = new File("./config.json");
private static int day; // Current day of week. private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static final File configFile = new File("./config.json");
private static HttpServer httpServer; private static int day; // Current day of week.
private static GameServer gameServer;
private static PluginManager pluginManager;
private static AuthenticationSystem authenticationSystem;
private static PermissionHandler permissionHandler;
public static final Reflections reflector = new Reflections("emu.grasscutter"); private static HttpServer httpServer;
public static ConfigContainer config; private static GameServer gameServer;
private static PluginManager pluginManager;
static { private static AuthenticationSystem authenticationSystem;
// Declare logback configuration. private static PermissionHandler permissionHandler;
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
// Load server configuration. public static final Reflections reflector = new Reflections("emu.grasscutter");
Grasscutter.loadConfig(); public static ConfigContainer config;
// Attempt to update configuration.
ConfigContainer.updateConfig();
// Load translation files. static {
Grasscutter.loadLanguage(); // Declare logback configuration.
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
// Check server structure. // Load server configuration.
Utils.startupCheck(); Grasscutter.loadConfig();
} // Attempt to update configuration.
ConfigContainer.updateConfig();
public static void main(String[] args) throws Exception { // Load translation files.
Crypto.loadKeys(); // Load keys from buffers. Grasscutter.loadLanguage();
// Parse arguments.
boolean exitEarly = false;
for (String arg : args) {
switch (arg.toLowerCase()) {
case "-handbook" -> {
Tools.createGmHandbook(); exitEarly = true;
}
case "-gachamap" -> {
Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true;
}
case "-version" -> {
System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); exitEarly = true;
}
}
}
// Exit early if argument sets it.
if(exitEarly) System.exit(0);
// Initialize server.
Grasscutter.getLogger().info(translate("messages.status.starting"));
Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION));
Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH));
// Load all resources.
Grasscutter.updateDayOfWeek();
ResourceLoader.loadAll();
ScriptLoader.init();
EnergyManager.initialize();
DungeonChallenge.initialize();
// Initialize database.
DatabaseManager.initialize();
// Initialize the default systems.
authenticationSystem = new DefaultAuthentication();
permissionHandler = new DefaultPermissionHandler();
// Create server instances.
httpServer = new HttpServer();
gameServer = new GameServer();
// Create a server hook instance with both servers.
new ServerHook(gameServer, httpServer);
// Create plugin manager instance.
pluginManager = new PluginManager();
// Add HTTP routes after loading plugins.
httpServer.addRouter(HttpServer.UnhandledRequestRouter.class);
httpServer.addRouter(HttpServer.DefaultRequestRouter.class);
httpServer.addRouter(RegionHandler.class);
httpServer.addRouter(LogHandler.class);
httpServer.addRouter(GenericHandler.class);
httpServer.addRouter(AnnouncementsHandler.class);
httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DocumentationServerHandler.class);
// TODO: find a better place?
StaminaManager.initialize();
// Start servers.
var runMode = SERVER.runMode;
if (runMode == ServerRunMode.HYBRID) {
httpServer.start();
gameServer.start();
} else if (runMode == ServerRunMode.DISPATCH_ONLY) {
httpServer.start();
} else if (runMode == ServerRunMode.GAME_ONLY) {
gameServer.start();
} else {
getLogger().error(translate("messages.status.run_mode_error", runMode));
getLogger().error(translate("messages.status.run_mode_help"));
getLogger().error(translate("messages.status.shutdown"));
System.exit(1);
}
// Enable all plugins.
pluginManager.enablePlugins();
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
// Open console.
startConsole();
}
/** // Check server structure.
* Server shutdown event. Utils.startupCheck();
*/ }
private static void onShutdown() {
// Disable all plugins.
pluginManager.disablePlugins();
}
/* public static void main(String[] args) throws Exception {
* Methods for the language system component. Crypto.loadKeys(); // Load keys from buffers.
*/
public static void loadLanguage() {
var locale = config.language.language;
language = Language.getLanguage(Utils.getLanguageCode(locale));
}
/*
* Methods for the configuration system component.
*/
/** // Parse arguments.
* Attempts to load the configuration from a file. boolean exitEarly = false;
*/ for (String arg : args) {
public static void loadConfig() { switch (arg.toLowerCase()) {
// Check if config.json exists. If not, we generate a new config. case "-handbook" -> {
if (!configFile.exists()) { Tools.createGmHandbook();
getLogger().info("config.json could not be found. Generating a default configuration ..."); exitEarly = true;
config = new ConfigContainer(); }
Grasscutter.saveConfig(config); case "-gachamap" -> {
return; Tools.createGachaMapping(DATA("gacha_mappings.js"));
} exitEarly = true;
}
case "-version" -> {
System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH);
exitEarly = true;
}
}
}
// If the file already exists, we attempt to load it. // Exit early if argument sets it.
try (FileReader file = new FileReader(configFile)) { if (exitEarly) System.exit(0);
config = gson.fromJson(file, ConfigContainer.class);
} catch (Exception exception) {
getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
System.exit(1);
}
}
/** // Initialize server.
* Saves the provided server configuration. Grasscutter.getLogger().info(translate("messages.status.starting"));
* @param config The configuration to save, or null for a new one. Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION));
*/ Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH));
public static void saveConfig(@Nullable ConfigContainer config) {
if(config == null) config = new ConfigContainer();
try (FileWriter file = new FileWriter(configFile)) {
file.write(gson.toJson(config));
} catch (IOException ignored) {
Grasscutter.getLogger().error("Unable to write to config file.");
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to save config file.", e);
}
}
/* // Load all resources.
* Getters for the various server components. Grasscutter.updateDayOfWeek();
*/ ResourceLoader.loadAll();
ScriptLoader.init();
public static ConfigContainer getConfig() {
return config;
}
public static Language getLanguage() { // Initialize database.
return language; DatabaseManager.initialize();
}
public static void setLanguage(Language language) { // Initialize the default systems.
authenticationSystem = new DefaultAuthentication();
permissionHandler = new DefaultPermissionHandler();
// Create server instances.
httpServer = new HttpServer();
gameServer = new GameServer();
// Create a server hook instance with both servers.
new ServerHook(gameServer, httpServer);
// Create plugin manager instance.
pluginManager = new PluginManager();
// Add HTTP routes after loading plugins.
httpServer.addRouter(HttpServer.UnhandledRequestRouter.class);
httpServer.addRouter(HttpServer.DefaultRequestRouter.class);
httpServer.addRouter(RegionHandler.class);
httpServer.addRouter(LogHandler.class);
httpServer.addRouter(GenericHandler.class);
httpServer.addRouter(AnnouncementsHandler.class);
httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DocumentationServerHandler.class);
// Start servers.
var runMode = SERVER.runMode;
if (runMode == ServerRunMode.HYBRID) {
httpServer.start();
gameServer.start();
} else if (runMode == ServerRunMode.DISPATCH_ONLY) {
httpServer.start();
} else if (runMode == ServerRunMode.GAME_ONLY) {
gameServer.start();
} else {
getLogger().error(translate("messages.status.run_mode_error", runMode));
getLogger().error(translate("messages.status.run_mode_help"));
getLogger().error(translate("messages.status.shutdown"));
System.exit(1);
}
// Enable all plugins.
pluginManager.enablePlugins();
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
// Open console.
startConsole();
}
/**
* Server shutdown event.
*/
private static void onShutdown() {
// Disable all plugins.
if(pluginManager != null)
pluginManager.disablePlugins();
}
/*
* Methods for the language system component.
*/
public static void loadLanguage() {
var locale = config.language.language;
language = Language.getLanguage(Utils.getLanguageCode(locale));
}
/*
* Methods for the configuration system component.
*/
/**
* Attempts to load the configuration from a file.
*/
public static void loadConfig() {
// Check if config.json exists. If not, we generate a new config.
if (!configFile.exists()) {
getLogger().info("config.json could not be found. Generating a default configuration ...");
config = new ConfigContainer();
Grasscutter.saveConfig(config);
return;
}
// If the file already exists, we attempt to load it.
try (FileReader file = new FileReader(configFile)) {
config = gson.fromJson(file, ConfigContainer.class);
} catch (Exception exception) {
getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
System.exit(1);
}
}
/**
* Saves the provided server configuration.
*
* @param config The configuration to save, or null for a new one.
*/
public static void saveConfig(@Nullable ConfigContainer config) {
if (config == null) config = new ConfigContainer();
try (FileWriter file = new FileWriter(configFile)) {
file.write(gson.toJson(config));
} catch (IOException ignored) {
Grasscutter.getLogger().error("Unable to write to config file.");
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to save config file.", e);
}
}
/*
* Getters for the various server components.
*/
public static ConfigContainer getConfig() {
return config;
}
public static Language getLanguage() {
return language;
}
public static void setLanguage(Language language) {
Grasscutter.language = language; Grasscutter.language = language;
} }
public static Language getLanguage(String langCode) { public static Language getLanguage(String langCode) {
return Language.getLanguage(langCode); return Language.getLanguage(langCode);
} }
public static Logger getLogger() { public static Logger getLogger() {
return log; return log;
} }
public static LineReader getConsole() { public static LineReader getConsole() {
if (consoleLineReader == null) { if (consoleLineReader == null) {
Terminal terminal = null; Terminal terminal = null;
try { try {
terminal = TerminalBuilder.builder().jna(true).build(); terminal = TerminalBuilder.builder().jna(true).build();
} catch (Exception e) { } catch (Exception e) {
try { try {
// Fallback to a dumb jline terminal. // Fallback to a dumb jline terminal.
terminal = TerminalBuilder.builder().dumb(true).build(); terminal = TerminalBuilder.builder().dumb(true).build();
} catch (Exception ignored) { } catch (Exception ignored) {
// When dumb is true, build() never throws. // When dumb is true, build() never throws.
} }
} }
consoleLineReader = LineReaderBuilder.builder() consoleLineReader = LineReaderBuilder.builder()
.terminal(terminal) .terminal(terminal)
.build(); .build();
} }
return consoleLineReader; return consoleLineReader;
} }
public static Gson getGsonFactory() { public static Gson getGsonFactory() {
return gson; return gson;
} }
public static HttpServer getHttpServer() { public static HttpServer getHttpServer() {
return httpServer; return httpServer;
} }
public static GameServer getGameServer() { public static GameServer getGameServer() {
return gameServer; return gameServer;
} }
public static PluginManager getPluginManager() { public static PluginManager getPluginManager() {
return pluginManager; return pluginManager;
} }
public static AuthenticationSystem getAuthenticationSystem() {
return authenticationSystem;
}
public static PermissionHandler getPermissionHandler() { public static AuthenticationSystem getAuthenticationSystem() {
return permissionHandler; return authenticationSystem;
} }
public static int getCurrentDayOfWeek() { public static PermissionHandler getPermissionHandler() {
return day; return permissionHandler;
} }
/*
* Utility methods.
*/
public static void updateDayOfWeek() {
Calendar calendar = Calendar.getInstance();
day = calendar.get(Calendar.DAY_OF_WEEK);
}
public static void startConsole() { public static int getCurrentDayOfWeek() {
// Console should not start in dispatch only mode. return day;
if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { }
getLogger().info(translate("messages.dispatch.no_commands_error"));
return;
}
getLogger().info(translate("messages.status.done")); /*
String input = null; * Utility methods.
boolean isLastInterrupted = false; */
while (config.server.game.enableConsole) {
try {
input = consoleLineReader.readLine("> ");
} catch (UserInterruptException e) {
if (!isLastInterrupted) {
isLastInterrupted = true;
Grasscutter.getLogger().info("Press Ctrl-C again to shutdown.");
continue;
} else {
Runtime.getRuntime().exit(0);
}
} catch (EndOfFileException e) {
Grasscutter.getLogger().info("EOF detected.");
continue;
} catch (IOError e) {
Grasscutter.getLogger().error("An IO error occurred.", e);
continue;
}
isLastInterrupted = false; public static void updateDayOfWeek() {
try { Calendar calendar = Calendar.getInstance();
CommandMap.getInstance().invoke(null, null, input); day = calendar.get(Calendar.DAY_OF_WEEK);
} catch (Exception e) { }
Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
}
}
}
/** public static void startConsole() {
* Sets the authentication system for the server. // Console should not start in dispatch only mode.
* @param authenticationSystem The authentication system to use. if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) {
*/ getLogger().info(translate("messages.dispatch.no_commands_error"));
public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) { return;
Grasscutter.authenticationSystem = authenticationSystem; }
}
/** getLogger().info(translate("messages.status.done"));
* Sets the permission handler for the server. String input = null;
* @param permissionHandler The permission handler to use. boolean isLastInterrupted = false;
*/ while (config.server.game.enableConsole) {
public static void setPermissionHandler(PermissionHandler permissionHandler) { try {
Grasscutter.permissionHandler = permissionHandler; input = consoleLineReader.readLine("> ");
} } catch (UserInterruptException e) {
if (!isLastInterrupted) {
isLastInterrupted = true;
Grasscutter.getLogger().info("Press Ctrl-C again to shutdown.");
continue;
} else {
Runtime.getRuntime().exit(0);
}
} catch (EndOfFileException e) {
Grasscutter.getLogger().info("EOF detected.");
continue;
} catch (IOError e) {
Grasscutter.getLogger().error("An IO error occurred.", e);
continue;
}
/* isLastInterrupted = false;
* Enums for the configuration. try {
*/ CommandMap.getInstance().invoke(null, null, input);
} catch (Exception e) {
public enum ServerRunMode { Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
HYBRID, DISPATCH_ONLY, GAME_ONLY }
} }
}
public enum ServerDebugMode { /**
ALL, MISSING, NONE * Sets the authentication system for the server.
} *
* @param authenticationSystem The authentication system to use.
*/
public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) {
Grasscutter.authenticationSystem = authenticationSystem;
}
/**
* Sets the permission handler for the server.
*
* @param permissionHandler The permission handler to use.
*/
public static void setPermissionHandler(PermissionHandler permissionHandler) {
Grasscutter.permissionHandler = permissionHandler;
}
/*
* Enums for the configuration.
*/
public enum ServerRunMode {
HYBRID, DISPATCH_ONLY, GAME_ONLY
}
public enum ServerDebugMode {
ALL, MISSING, NONE
}
} }