Java tutorial
/** * This file is part of Ogar. * * Ogar 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. * * Ogar 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 Ogar. If not, see <http://www.gnu.org/licenses/>. */ package ru.calypso.ogar.server; import java.io.IOException; import java.net.ServerSocket; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import java.util.function.Supplier; import org.apache.commons.lang3.math.NumberUtils; import org.apache.log4j.Logger; import ru.calypso.ogar.server.config.Config; import ru.calypso.ogar.server.entity.Entity; import ru.calypso.ogar.server.events.PlayerEventHandler; import ru.calypso.ogar.server.gamemode.GameMode; import ru.calypso.ogar.server.gamemode.GameModesHolder; import ru.calypso.ogar.server.handler.commands.admin.AdminCommandHandler; import ru.calypso.ogar.server.handler.commands.user.UserCommandHandler; import ru.calypso.ogar.server.holders.FoodList; import ru.calypso.ogar.server.holders.MassList; import ru.calypso.ogar.server.holders.PlayerList; import ru.calypso.ogar.server.holders.VirusList; import ru.calypso.ogar.server.net.NetworkManager; import ru.calypso.ogar.server.tasks.FoodSpawnTask; import ru.calypso.ogar.server.tasks.LeaderBoardSendTask; import ru.calypso.ogar.server.tasks.StatSendTask; import ru.calypso.ogar.server.tasks.VirusSpawnTask; import ru.calypso.ogar.server.tick.TickWorker; import ru.calypso.ogar.server.tick.Tickable; import ru.calypso.ogar.server.tick.TickableSupplier; import ru.calypso.ogar.server.util.BanList; import ru.calypso.ogar.server.util.Log; import ru.calypso.ogar.server.util.ScriptsLoader; import ru.calypso.ogar.server.util.StatsUtils; import ru.calypso.ogar.server.util.listeners.ConsoleListener; import ru.calypso.ogar.server.util.threads.RunnableImpl; import ru.calypso.ogar.server.util.threads.ThreadPoolManager; import ru.calypso.ogar.server.world.Player; import ru.calypso.ogar.server.world.World; /** * @author OgarProject, modify by Calypso - Freya Project team */ public class OgarServer { private static OgarServer instance; private static Logger _log = Logger.getLogger(OgarServer.class); private final PlayerList playerList = new PlayerList(this); private final VirusList virusList = new VirusList(); private final FoodList foodList = new FoodList(); private final MassList massList = new MassList(); private final Set<TickWorker> tickWorkers = new HashSet<>(); private int tickThreads = Integer.getInteger("tickThreads", 1); // TODO learn about this private NetworkManager networkManager; private World world; private GameMode gamemode; private long tick = 0, startTime; public static void main(String[] args) throws Throwable { OgarServer.instance = new OgarServer(); OgarServer.instance.run(); } public static OgarServer getInstance() { return instance; } public PlayerList getPlayerList() { return playerList; } public MassList getMassList() { return massList; } public FoodList getFoodList() { return foodList; } public VirusList getVirusList() { return virusList; } public World getWorld() { return world; } public GameMode getGameMode() { return gamemode; } public void setGameMode(GameMode mode) { gamemode = mode; } public long getTick() { return tick; } private void run() { startTime = System.currentTimeMillis(); ThreadPoolManager.getInstance(); Runtime.getRuntime().addShutdownHook(Shutdown.getInstance()); _log.info("================================================="); _log.info("Server based on Ogar2 of OgarProject"); _log.info("Reworked by Calypso #Freya Project team."); _log.info(" more bugs, enjoy!"); _log.info("================================================="); _log.info("Server starting."); // Create the tick workers if (tickThreads < 1) { tickThreads = 1; } _log.info("Running server with " + tickThreads + " tick thread(s)."); if (tickThreads > 1) { _log.warn("Use of multiple tick threads is experimental and may be unstable!"); } for (int i = 0; i < tickThreads; i++) { tickWorkers.add(new TickWorker()); } _log.info("=[Loading config]================================"); Config.loadAll(); _log.info("Loaded!"); _log.info("================================================="); Parsers.parseAll(); // ? ??, ? ?, ??? checkPort(); world = new World(this); _log.info("=[Scripts]======================================="); ScriptsLoader.getInstance().init(); GameModesHolder.getInstance().log(); gamemode = GameModesHolder.getInstance().getGameModeByID(Config.Server.GAMEMODE_ID); if (gamemode == null) { _log.warn("Current GameMode is NULL! Check configs! And restart server!"); try { Thread.sleep(10000L); System.exit(0); } catch (InterruptedException e2) { } } UserCommandHandler.getInstance().log(); AdminCommandHandler.getInstance().log(); PlayerEventHandler.getInstance().log(); _log.info("================================================="); _log.info("=[Ban list]======================================"); BanList.loadBanList(); _log.info("================================================="); /* log.info("Loading plugins."); try { File pluginDirectory = new File("plugins"); if (!pluginDirectory.exists()) { pluginDirectory.mkdirs(); } pluginManager.loadPlugins(pluginDirectory); } catch (Throwable t) { log.log(Level.SEVERE, "Failed to load plugins", t); } log.info("Enabling plugins."); pluginManager.enablePlugins(); */ networkManager = new NetworkManager(this); try { networkManager.start(); } catch (IOException | InterruptedException ex) { _log.error("Failed to start server!", ex); System.exit(1); } // ? ? leaderboard ThreadPoolManager.getInstance().scheduleAtFixedDelay(new LeaderBoardSendTask(), Config.Server.LB_SEND_INTERVAL, Config.Server.LB_SEND_INTERVAL); // ? ? ?? if (Config.Other.STAT_SEND_DELAY > 0L) ThreadPoolManager.getInstance().scheduleAtFixedDelay(new StatSendTask(), Config.Other.STAT_SEND_DELAY, Config.Other.STAT_SEND_DELAY); // ? ? ? ThreadPoolManager.getInstance().schedule(new FoodSpawnTask(this, Config.Food.SPAWN_ONSTART), 10L); ThreadPoolManager.getInstance().scheduleAtFixedDelay(new FoodSpawnTask(this, Config.Food.SPAWN_PER_TASK), 1000L, Config.Food.SPAWN_TASK_DELAY); // ? ? ? ThreadPoolManager.getInstance().schedule(new VirusSpawnTask(this, Config.Virus.SPAWN_ONSTART), 10L); ThreadPoolManager.getInstance().scheduleAtFixedDelay(new VirusSpawnTask(this, Config.Virus.SPAWN_PER_TASK), 1000L, Config.Virus.SPAWN_TASK_DELAY); // Start the tick workers tickWorkers.forEach(TickWorker::start); _log.info("Server loaded at " + (int) (System.currentTimeMillis() - startTime) / 1000 % 60 + " seconds!\n"); _log.info("Current GameMode: " + gamemode.getName()); if (Config.Server.AUTORESTART_DELAY > 0) Shutdown.getInstance().schedule(Config.Server.AUTORESTART_DELAY, Shutdown.RESTART); if (Config.Server.INFO_PRINT_TASK_DELAY > 0) { ThreadPoolManager.getInstance().scheduleAtFixedRate(new RunnableImpl() { @Override public void runImpl() { if (Config.Server.PRINT_MEM_USAGE) printMemUsage(); if (Config.Server.PRINT_UPTIME) printUptime(); if (Config.Server.PRINT_ONLINE) printOnline(); } }, Config.Server.INFO_PRINT_TASK_DELAY, Config.Server.INFO_PRINT_TASK_DELAY); } Thread thread = new Thread(new ConsoleListener(this), "Console Command Handler"); thread.setDaemon(true); thread.start(); printMemUsage(); while (true) { try { // To make the tick loop adaptive, we measure the start and end times. // This allows us to ensure that there is around 20 ticks per second. long startTime = System.currentTimeMillis(); tick++; // Entity ticking for (Iterator<Entity> it = world.getRawEntities().iterator(); it.hasNext();) { tick(it.next()); } // Update nodes for (Iterator<Player> it = playerList.getAllPlayers().iterator(); it.hasNext();) { it.next().getTracker().updateNodes(false); } // Wait for the tick workers to finish tickWorkers.forEach(TickWorker::waitForCompletion); long tickDuration = System.currentTimeMillis() - startTime; if (tickDuration < 50) { // We can sleep for at least 1ms Log.logDebug("Tick took " + tickDuration + "ms, sleeping for a bit"); Thread.sleep(50 - tickDuration); } else { // No sleep allowed, move on to the next tick Log.logDebug("Tick took " + tickDuration + "ms (which is >=50ms), no time for sleep"); } } catch (InterruptedException ex) { break; } } } private void printOnline() { _log.info("=[Server Online]================================="); _log.info("Connected: " + playerList.getAllPlayers().size() + "/" + Config.Server.MAX_PLAYERS); _log.info("Playing: " + playerList.getPlayersWithCells().size()); // TODO SPECTATORS _log.info("================================================="); } private void printUptime() { int diff = (int) (System.currentTimeMillis() - startTime) / 1000; _log.info("=[Server Uptime]================================="); _log.info("Uptime is " + StatsUtils.formatTime(diff, false)); _log.info("================================================="); } private void printMemUsage() { _log.info("=[Memory Usage]=================================="); String memUsage = new StringBuilder().append(StatsUtils.getMemUsage()).toString(); for (String line : memUsage.split("\n")) _log.info(line); _log.info("================================================="); } public long getStartTime() { return startTime; } private void checkPort() { boolean binded = false; while (!binded) try { ServerSocket ss = new ServerSocket(Config.Server.PORT); ss.close(); binded = true; } catch (Exception e) { _log.warn("Port " + Config.Server.PORT + " is allready binded. Please free it!"); binded = false; try { Thread.sleep(1000); } catch (InterruptedException e2) { } } } public void shutdown() { // Shut down tick workers // We initiate all shutdowns before waiting on them to reduce shutdown time _log.info("Shutting down tick workers..."); tickWorkers.forEach(TickWorker::shutdownGracefully); tickWorkers.forEach(TickWorker::waitForShutdown); // Shut down network manager _log.info("Shutting down network manager..."); if (networkManager != null) networkManager.shutdown(); // Disable plugins //log.info("Disabling plugins..."); // pluginManager.disablePlugins(); } private void tick(Tickable... tickables) { for (Tickable t : tickables) { TickWorker bestWorker = null; for (TickWorker w : tickWorkers) { if (bestWorker == null) { bestWorker = w; continue; } if (w.getObjectsRemaining() < bestWorker.getObjectsRemaining()) { bestWorker = w; } } bestWorker.tick(t); } } @SuppressWarnings({ "unused", "rawtypes" }) private void tick(Supplier... suppliers) { for (Supplier s : suppliers) { tick(new TickableSupplier(s)); } } public void handleCommand(String line) { line = line.trim(); if (line.isEmpty()) return; String command = line.split("\\s+")[0]; String args = line.substring(command.length()).trim(); StringTokenizer st = new StringTokenizer(args); switch (command.toLowerCase()) { case "help": _log.info("=[Commands Help]================================="); _log.info("\"help\" - show this text"); _log.info("\"cancel/restart\" - cancel restart/shutdown"); _log.info("\"shutdown\" - schedule shutdown"); _log.info("\"shutdown n*\" - schedule shutdown after n seconds"); _log.info("\"restart\" - schedule restart"); _log.info("\"restart n*\" - schedule restart after n seconds"); _log.info("================================================="); break; case "abort": case "cancel": Shutdown.getInstance().cancel(); break; case "restart": if (st.hasMoreTokens()) Shutdown.getInstance().schedule(NumberUtils.toInt(st.nextToken(), -1), Shutdown.RESTART); else Shutdown.getInstance().schedule(1, Shutdown.RESTART); break; case "shutdown": if (st.hasMoreTokens()) Shutdown.getInstance().schedule(NumberUtils.toInt(st.nextToken(), -1), Shutdown.SHUTDOWN); else Shutdown.getInstance().schedule(1, Shutdown.SHUTDOWN); break; case "kick": try { Player player = getPlayerList().getPlayerByName(st.nextToken()); if (player != null) { player.getConnection().getChannel().disconnect(); _log.info("Kick success!"); } else _log.info("Player with this name not found!"); } catch (Exception e) { _log.info("Command syntax: kick playername"); } break; default: _log.info("Unknown command, use \"help\" for help."); break; } } }