diff --git a/core/src/mindustry/maps/Maps.java b/core/src/mindustry/maps/Maps.java index d9d6be3b54..01bbde4b20 100644 --- a/core/src/mindustry/maps/Maps.java +++ b/core/src/mindustry/maps/Maps.java @@ -44,7 +44,9 @@ public class Maps{ /** All maps stored in an ordered array. */ private Seq maps = new Seq<>(); private ShuffleMode shuffleMode = ShuffleMode.all; + private @Nullable MapProvider shuffler; + private @Nullable Map nextMapOverride; private ObjectSet previewList = new ObjectSet<>(); @@ -61,8 +63,19 @@ public class Maps{ this.shuffler = provider; } + /** Set the map that will override the next selected map. */ + public void setNextMapOverride(Map nextMapOverride){ + this.nextMapOverride = nextMapOverride; + } + /** @return the next map to shuffle to. May be null, in which case the server should be stopped. */ public @Nullable Map getNextMap(Gamemode mode, @Nullable Map previous){ + if(nextMapOverride != null){ + Map next = nextMapOverride; + nextMapOverride = null; + return next; + } + if(shuffler != null) return shuffler.next(mode, previous); return shuffleMode.next(mode, previous); } diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index 9f4d14846b..71c65878ef 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -513,7 +513,9 @@ public class Administration{ autosaveSpacing = new Config("autosaveSpacing", "Spacing between autosaves in seconds.", 60 * 5), debug = new Config("debug", "Enable debug logging.", false, () -> Log.level = debug() ? LogLevel.debug : LogLevel.info), snapshotInterval = new Config("snapshotInterval", "Client entity snapshot interval in ms.", 200), - autoPause = new Config("autoPause", "Whether the game should pause when nobody is online.", false); + autoPause = new Config("autoPause", "Whether the game should pause when nobody is online.", false), + roundExtraTime = new Config("roundExtraTime", "Time before loading a new map after the gameover, in seconds.", 12), + maxLogLength = new Config("maxLogLength", "The Maximum log file size, in bytes.", 1024 * 1024 * 5); public final Object defaultValue; public final String name, key, description; diff --git a/server/src/mindustry/server/ServerControl.java b/server/src/mindustry/server/ServerControl.java index e946ec1946..31e9868084 100644 --- a/server/src/mindustry/server/ServerControl.java +++ b/server/src/mindustry/server/ServerControl.java @@ -2,6 +2,7 @@ package mindustry.server; import arc.*; import arc.files.*; +import arc.func.Cons; import arc.struct.*; import arc.util.*; import arc.util.Timer; @@ -35,9 +36,6 @@ import static arc.util.Log.*; import static mindustry.Vars.*; public class ServerControl implements ApplicationListener{ - private static final int roundExtraTime = 12; - private static final int maxLogLength = 1024 * 1024 * 5; - protected static String[] tags = {"&lc&fb[D]&fr", "&lb&fb[I]&fr", "&ly&fb[W]&fr", "&lr&fb[E]", ""}; protected static DateTimeFormatter dateTime = DateTimeFormatter.ofPattern("MM-dd-yyyy HH:mm:ss"), autosaveDate = DateTimeFormatter.ofPattern("MM-dd-yyyy_HH-mm-ss"); @@ -48,6 +46,8 @@ public class ServerControl implements ApplicationListener{ public final CommandHandler handler = new CommandHandler(""); public final Fi logFolder = Core.settings.getDataDirectory().child("logs/"); + private final Interval autosaveCount = new Interval(); + public Runnable serverInput = () -> { Scanner scan = new Scanner(System.in); while(scan.hasNext()){ @@ -56,19 +56,51 @@ public class ServerControl implements ApplicationListener{ } }; - private Fi currentLogFile; - private boolean inGameOverWait; - private Task lastTask; - private Gamemode lastMode; - private @Nullable Map nextMapOverride; - private Interval autosaveCount = new Interval(); + /** The file to which the logs are currently being written. */ + public Fi currentLogFile; + /** Whether the server is currently waiting for the next map to be loaded. */ + public boolean inGameOverWait; + + /** The last gamemode loaded on this server. */ + public Gamemode lastMode; + + private Task lastTask; private Thread socketThread; private ServerSocket serverSocket; private PrintWriter socketOutput; private String suggested; private boolean autoPaused = false; + public Cons gameOverListener = event -> { + if(state.rules.waves){ + info("Game over! Reached wave @ with @ players online on map @.", state.wave, Groups.player.size(), Strings.capitalize(state.map.plainName())); + }else{ + info("Game over! Team @ is victorious with @ players online on map @.", event.winner.name, Groups.player.size(), Strings.capitalize(state.map.plainName())); + } + + //set the next map to be played + Map map = maps.getNextMap(lastMode, state.map); + if(map != null){ + Call.infoMessage((state.rules.pvp + ? "[accent]The " + event.winner.coloredName() + " team is victorious![]\n" : "[scarlet]Game over![]\n") + + "\nNext selected map: [accent]" + map.name() + "[white]" + + (map.hasTag("author") ? " by[accent] " + map.author() + "[white]" : "") + "." + + "\nNew game begins in " + Config.roundExtraTime.num() + " seconds."); + + state.gameOver = true; + Call.updateGameOver(event.winner); + + info("Selected next map to be @.", map.plainName()); + + play(() -> world.loadMap(map, map.applyRules(lastMode))); + }else{ + netServer.kickAll(KickReason.gameover); + state.set(State.menu); + net.closeServer(); + } + }; + public ServerControl(String[] args){ setup(args); instance = this; @@ -173,33 +205,8 @@ public class ServerControl implements ApplicationListener{ } Events.on(GameOverEvent.class, event -> { - if(inGameOverWait) return; - if(state.rules.waves){ - info("Game over! Reached wave @ with @ players online on map @.", state.wave, Groups.player.size(), Strings.capitalize(state.map.plainName())); - }else{ - info("Game over! Team @ is victorious with @ players online on map @.", event.winner.name, Groups.player.size(), Strings.capitalize(state.map.plainName())); - } - - //set next map to be played - Map map = nextMapOverride != null ? nextMapOverride : maps.getNextMap(lastMode, state.map); - nextMapOverride = null; - if(map != null){ - Call.infoMessage((state.rules.pvp - ? "[accent]The " + event.winner.coloredName() + " team is victorious![]\n" : "[scarlet]Game over![]\n") - + "\nNext selected map: [accent]" + map.name() + "[white]" - + (map.hasTag("author") ? " by[accent] " + map.author() + "[white]" : "") + "." + - "\nNew game begins in " + roundExtraTime + " seconds."); - - state.gameOver = true; - Call.updateGameOver(event.winner); - - info("Selected next map to be @.", map.plainName()); - - play(true, () -> world.loadMap(map, map.applyRules(lastMode))); - }else{ - netServer.kickAll(KickReason.gameover); - state.set(State.menu); - net.closeServer(); + if(!inGameOverWait && gameOverListener != null){ + gameOverListener.get(event); } }); @@ -248,7 +255,6 @@ public class ServerControl implements ApplicationListener{ }); Events.on(PlayEvent.class, e -> { - try{ JsonValue value = JsonIO.json.fromJson(null, Core.settings.getString("globalrules")); JsonIO.json.readFields(state.rules, value); @@ -280,9 +286,11 @@ public class ServerControl implements ApplicationListener{ toggleSocket(Config.socketInput.bool()); Events.on(ServerLoadEvent.class, e -> { - Thread thread = new Thread(serverInput, "Server Controls"); - thread.setDaemon(true); - thread.start(); + if(serverInput != null){ + Thread thread = new Thread(serverInput, "Server Controls"); + thread.setDaemon(true); + thread.start(); + } info("Server loaded. Type @ for help.", "'help'"); }); @@ -302,7 +310,6 @@ public class ServerControl implements ApplicationListener{ autoPaused = true; } }); - } protected void registerCommands(){ @@ -392,7 +399,7 @@ public class ServerControl implements ApplicationListener{ autoPaused = true; } }catch(MapException e){ - err(e.map.plainName() + ": " + e.getMessage()); + err("@: @", e.map.plainName(), e.getMessage()); } }); @@ -735,7 +742,7 @@ public class ServerControl implements ApplicationListener{ handler.register("nextmap", "", "Set the next map to be played after a game-over. Overrides shuffling.", arg -> { Map res = maps.all().find(map -> map.plainName().replace('_', ' ').equalsIgnoreCase(Strings.stripColors(arg[0]).replace('_', ' '))); if(res != null){ - nextMapOverride = res; + maps.setNextMapOverride(res); info("Next map set to '@'.", res.plainName()); }else{ err("No map '@' found.", arg[0]); @@ -889,8 +896,7 @@ public class ServerControl implements ApplicationListener{ }else{ info("Players: @", Groups.player.size()); for(Player user : Groups.player){ - PlayerInfo userInfo = user.getInfo(); - info(" @&lm @ / ID: @ / IP: @", userInfo.admin ? "&r[A]&c" : "&b[P]&c", userInfo.plainLastName(), userInfo.id, userInfo.lastIP, userInfo.admin); + info(" @&lm @ / ID: @ / IP: @", user.admin ? "&r[A]&c" : "&b[P]&c", user.plainName(), user.uuid(), user.ip()); } } }); @@ -1048,12 +1054,32 @@ public class ServerControl implements ApplicationListener{ } } + /** + * @deprecated + * Use {@link Maps#setNextMapOverride(Map)} instead. + */ + @Deprecated public void setNextMap(Map map){ - nextMapOverride = map; + maps.setNextMapOverride(map); } - private void play(boolean wait, Runnable run){ + /** + * Resets the world state, starts a new game. + * @param run What task to run to load a new world. + */ + public void play(Runnable run){ + play(true, run); + } + + /** + * Resets the world state, starts a new game. + * @param wait Whether to wait for {@link Config#roundExtraTime} seconds before starting a new game. + * @param run What task to run to load a new world. + */ + public void play(boolean wait, Runnable run){ inGameOverWait = true; + if(lastTask != null) lastTask.cancel(); + Runnable r = () -> { WorldReloader reloader = new WorldReloader(); @@ -1075,20 +1101,20 @@ public class ServerControl implements ApplicationListener{ try{ r.run(); }catch(MapException e){ - err(e.map.plainName() + ": " + e.getMessage()); + err("@: @", e.map.plainName(), e.getMessage()); net.closeServer(); } } }; - Timer.schedule(lastTask, roundExtraTime); + Timer.schedule(lastTask, Config.roundExtraTime.num()); }else{ r.run(); } } - private void logToFile(String text){ - if(currentLogFile != null && currentLogFile.length() > maxLogLength){ + public void logToFile(String text){ + if(currentLogFile != null && currentLogFile.length() > Config.maxLogLength.num()){ currentLogFile.writeString("[End of log file. Date: " + dateTime.format(LocalDateTime.now()) + "]\n", true); currentLogFile = null; } @@ -1099,7 +1125,7 @@ public class ServerControl implements ApplicationListener{ if(currentLogFile == null){ int i = 0; - while(logFolder.child("log-" + i + ".txt").length() >= maxLogLength){ + while(logFolder.child("log-" + i + ".txt").length() >= Config.maxLogLength.num()){ i++; } @@ -1109,7 +1135,7 @@ public class ServerControl implements ApplicationListener{ currentLogFile.writeString(text + "\n", true); } - private void toggleSocket(boolean on){ + public void toggleSocket(boolean on){ if(on && socketThread == null){ socketThread = new Thread(() -> { try{