package com.cardshifter.server.model; import java.nio.file.Paths; import java.util.Arrays; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.function.Supplier; import com.cardshifter.server.main.ServerConfiguration; import com.cardshifter.server.utils.export.DataExportCommand; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import com.cardshifter.ai.FakeAIClientTCG; import com.cardshifter.api.ClientIO; import com.cardshifter.api.both.ChatMessage; import com.cardshifter.api.incoming.LoginMessage; import com.cardshifter.core.game.ModCollection; import com.cardshifter.core.game.ServerGame; import com.cardshifter.core.game.TCGGame; import com.cardshifter.server.commands.AICommand; import com.cardshifter.server.commands.AICommand.AICommandParameters; import com.cardshifter.server.commands.CommandContext; import com.cardshifter.server.commands.EntityCommand; import com.cardshifter.server.commands.EntityCommand.EntityInspectParameters; import com.cardshifter.server.commands.HelpCommand; import com.cardshifter.server.commands.HelpCommand.HelpParameters; import com.cardshifter.server.commands.ReplayAllCommand; import com.cardshifter.server.commands.ReplayAllCommand.ReplayAllParameters; import com.cardshifter.server.commands.ReplayCommand; import com.cardshifter.server.commands.ReplayCommand.ReplayParameters; /** *Starts the Server object, sets up the AIs and GameFactories, and controls other functions of Server * * @author Simon Forsberg */ public class MainServer { private static final Logger logger = LogManager.getLogger(MainServer.class); /** * Server handles incoming messages and passes them to appropriate methods */ private final Server server = new Server(); /** * ModCollection is where the Phrancis mods are initialized */ private final ModCollection mods = ModCollection.defaultMods(); private final ServerConfiguration config; private Thread consoleThread; public MainServer(ServerConfiguration serverConfiguration) { this.config = serverConfiguration; } /** * Adds connections, AIs, and GameFactories to the Server, and starts the ServerConsole. * CommandHandler is a reference to the CommandHandler in Server * * @return The configured Server object */ public Server start() { mods.loadExternal(mods.getDefaultModLocation()); mods.loadExternal(Paths.get(config.getModsDirectory())); try { logger.info("Starting Server..."); server.addConnections(new ServerSock(server, config)); server.addConnections(new ServerWeb(server, config)); logger.info("Starting Console..."); CommandHandler commandHandler = server.getCommandHandler(); initializeCommands(commandHandler); ServerConsole console = new ServerConsole(server, commandHandler); consoleThread = new Thread(console, "Console-Thread"); consoleThread.start(); mods.getAIs().entrySet().forEach(entry -> { ClientIO tcgAI = new FakeAIClientTCG(server, entry.getValue()); server.newClient(tcgAI); server.getIncomingHandler().perform(new LoginMessage("AI " + entry.getKey()), tcgAI); }); final Supplier<ScheduledExecutorService> aiExecutor = () -> server.getScheduler(); mods.getAvailableMods().forEach(name -> server.addGameFactory(name, (serv, id) -> new TCGGame(aiExecutor, name, id, mods.getModFor(name)))); logger.info("Started"); } catch (Exception e) { logger.error("Initializing Error", e); } return server; } /** * Adds specific commands to the CommandHandler such as "exit" and "chat" and attaches them to various methods * * @param commandHandler The command handler that commands will be added to */ private void initializeCommands(CommandHandler commandHandler) { commandHandler.addHandler("exit", () -> new Object(), this::shutdown); commandHandler.addHandler("help", () -> new HelpParameters(), new HelpCommand(commandHandler)); commandHandler.addHandler("export", () -> new DataExportCommand.DataExportParameters(), new DataExportCommand()); commandHandler.addHandler("users", this::users); commandHandler.addHandler("say", this::say); commandHandler.addHandler("chat", this::chatInfo); commandHandler.addHandler("games", this::showGames); commandHandler.addHandler("invites", this::showInvites); commandHandler.addHandler("test", this::test); commandHandler.addHandler("ai", () -> new AICommandParameters(), new AICommand()); commandHandler.addHandler("ent", () -> new EntityInspectParameters(), new EntityCommand()); commandHandler.addHandler("threads", cmd -> showAllStackTraces(server, System.out::println)); commandHandler.addHandler("replay", () -> new ReplayParameters(), new ReplayCommand()); commandHandler.addHandler("allreplays", () -> new ReplayAllParameters(), new ReplayAllCommand()); } private void test(Command command) { ServerGame game = server.createGame(command.getParameter(1)); FakeAIClientTCG ai1 = new FakeAIClientTCG(server, mods.getAIs().get("Fighter")); FakeAIClientTCG ai2 = new FakeAIClientTCG(server, mods.getAIs().get("Idiot")); game.start(Arrays.asList(ai1, ai2)); } /** * Prints out the game invites of the Server * * @param command The command object */ private void showInvites(Command command) { CommandContext context = new CommandContext(server, command, command.getSender()); for (GameInvite e : server.getInviteManager().getInvites()) { context.sendChatResponse(e.toString()); } } /** * Prints out the current games of the Server * * @param command The command object */ private void showGames(Command command) { CommandContext context = new CommandContext(server, command, command.getSender()); for (Entry<Integer, ServerGame> ee : server.getGames().entrySet()) { context.sendChatResponse(ee.getKey() + " = " + ee.getValue()); } } /** * Sends a chat message to the master chat of the Server * * @param command The command object */ private void say(Command command) { ChatArea chat = server.getMainChat(); chat.broadcast(new ChatMessage(chat.getId(), "Server", command.getFullCommand(1))); } /** * Either prints out all of the chats, or the users currently in the master chat * * @param command The command object */ private void chatInfo(Command command) { int chatId = command.getParameterInt(1); CommandContext context = new CommandContext(server, command, command.getSender()); if (chatId == 0) { context.sendChatResponse(server.getChats().keySet().toString()); } else { ChatArea chat = server.getChats().get(chatId); context.sendChatResponse(chat.getUsers().toString()); } } /** * Stops the Server and consoleThread, and shows all stack traces for Server * * @param command A command object with the Server and ClientIO also (unused) * @param parameters Unused parameter */ private void shutdown(CommandContext command, Object parameters) { shutdown(); } /** * Shutdown everything */ public void shutdown() { server.stop(); try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("Interrupted when shutting down"); } showAllStackTraces(server, System.out::println); consoleThread.interrupt(); } /** * Print out the clients * * @param command The command object */ private void users(Command command) { server.getClients().values().forEach(cl -> System.out.println(cl)); } /** * The Consumer accepts all stack traces for all threads * * @param server Unused parameter * @param output Receives all stack trace strings */ private void showAllStackTraces(Server server, Consumer<String> output) { output.accept("All stack traces:"); Map<Thread, StackTraceElement[]> allTraces = Thread.getAllStackTraces(); for (Thread thread : allTraces.keySet()) { output.accept(thread.getName()); this.stackTrace(thread, output); } } /** * Convert the stack traces to strings and send them to the output * * @param thread Thread to get a stack trace for * @param output Where the stack traces are sent */ private void stackTrace(Thread thread, Consumer<String> output) { StackTraceElement[] stackTrace = thread.getStackTrace(); output.accept("Stack trace for thread " + thread.getId() + ": " + thread.getName()); for (StackTraceElement trace : stackTrace) { output.accept(trace.toString()); } output.accept(""); } public ModCollection getMods() { return mods; } }