From 8d4b49f99ccbf3d44d0dab64bd0af9e6b9d0d2cc Mon Sep 17 00:00:00 2001 From: Piegames <14054505+piegamesde@users.noreply.github.com> Date: Wed, 14 Nov 2018 14:09:14 +0100 Subject: [PATCH] WIP --- .../CommandLineMain.java | 95 +++- .../morlok8k/MinecraftLandGenerator/Main.java | 480 ------------------ .../MinecraftLandGenerator/Server.java | 194 ++++++- .../MinecraftLandGenerator/Setup.java | 158 ------ 4 files changed, 267 insertions(+), 660 deletions(-) delete mode 100644 src/main/java/morlok8k/MinecraftLandGenerator/Main.java delete mode 100644 src/main/java/morlok8k/MinecraftLandGenerator/Setup.java diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java b/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java index 4ea2e8c..1fe6e86 100644 --- a/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java +++ b/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java @@ -1,36 +1,48 @@ package morlok8k.MinecraftLandGenerator; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joml.Vector2i; import picocli.CommandLine; import picocli.CommandLine.Command; +import picocli.CommandLine.Help.Visibility; import picocli.CommandLine.HelpCommand; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; import picocli.CommandLine.RunLast; -import java.nio.file.Path; - -@Command(name = "mlg", subcommands = { - HelpCommand.class}) +@Command(name = "mlg", subcommands = { HelpCommand.class }) public class CommandLineMain implements Runnable { - @Option(names = {"-v", "--verbose"}, description = "Be verbose.") + private static Log log = LogFactory.getLog(CommandLineMain.class); + + @Option(names = { "-v", "--verbose" }, description = "Be verbose.") private boolean verbose = false; - @Option(names = {"-r", "--region"}, description = "Regionfiles instead of chunks") + @Option(names = { "-r", "--region" }, description = "Regionfiles instead of chunks") private boolean regionFile = false; - @Option(names = {"-s", "--customspawn"}, description = "Customized SpawnPoints") + @Option(names = { "-s", "--customspawn" }, description = "Customized SpawnPoints") private String[] customSpawnPoints; - @Option(names = "-i", description = "override the iteration spawn offset increment", defaultValue = "380", showDefaultValue = CommandLine.Help.Visibility.ALWAYS) + @Option(names = "-i", description = "override the iteration spawn offset increment", + defaultValue = "380", showDefaultValue = CommandLine.Help.Visibility.ALWAYS) private int increment = 380; - @Option(names = {"--x-offset", "-x"}, description = "set the X offset to generate land around") + @Option(names = { "--x-offset", "-x" }, + description = "set the X offset to generate land around") private int xOffset = 0; - @Option(names = {"--y-offset", "-y"}, description = "set the Z offset to generate land around") + @Option(names = { "--y-offset", "-y" }, + description = "set the Z offset to generate land around") private int zOffset = 0; @Parameters(index = "0", description = "X-coordinate") @@ -39,15 +51,17 @@ public class CommandLineMain implements Runnable { @Parameters(index = "1", description = "Z-coordinate") private int Z; - @Option(names = {"-s","--serverFile"}, description = "path to the directory in which the server runs") - private Path serverPath; + @Option(names = { "-s", "--serverFile" }, description = "Path to the server's jar file.", + defaultValue = "server.jar", showDefaultValue = Visibility.ALWAYS) + private Path serverFile; - @Option(names= {"-w","--worldPath"}, description = "path to the to be generated world") + @Option(names = { "-w", "--worldPath" }, + description = "Path to the world that should be generated. Defaults to the value in server.properties") private Path worldPath; - @Option(names= {"-d","--workDir"}, description = "workDirectory") - private Path workDir; - + @Option(names = { "--java-cmd" }, + description = "Java command to launch the server. Defaults to `java -jar`.") + private String[] javaOpts; public CommandLineMain() { @@ -55,7 +69,26 @@ public class CommandLineMain implements Runnable { @Override public void run() { - CommandLine.usage(this, System.err); + Server server = new Server(javaOpts, serverFile); + if (worldPath != null) + server.setWorld(worldPath); + else worldPath = server.getWorld(); + if (worldPath == null || !Files.exists(worldPath)) { + log.warn( + "No world was specified or the world at the given path does not exist. Starting the server once to create one..."); + server.runMinecraft(); + worldPath = server.getWorld(); + } + if (worldPath == null || !Files.exists(worldPath)) { + log.fatal("There is still no world, we cannot continue without world"); + return; + } + for (Vector2i spawn : generateSpawnpoints(startX, startZ, width, height, maxInc, + generationRadius)) { + server.setSpawn(level, spawn); + server.runMinecraft(); + } + server.restoreWorld(); } public static void main(String[] args) { @@ -63,4 +96,32 @@ public class CommandLineMain implements Runnable { cli.parseWithHandler(new RunLast(), args); } + /** + * @param maxInc + * Maximum number of chunks between two spawn points, horizontally or vertically + * @param generationMargin + * The radius to each side that will be generated by the server (Not the diameter!) + */ + public static List generateSpawnpoints(int startX, int startZ, int width, int height, + int maxInc, int generationRadius) { + if (width < generationRadius || height < generationRadius) + throw new IllegalArgumentException("Width and height must both be at least " + + (generationRadius * 2 + 1) + ", but are " + width + " and " + height); + List xPoints = generateLinearSpawnpoints(startX + generationRadius, + width - generationRadius * 2, maxInc); + List zPoints = generateLinearSpawnpoints(startZ + generationRadius, + height - generationRadius * 2, maxInc); + List spawnPoints = new ArrayList<>(xPoints.size() * zPoints.size()); + for (int x : xPoints) + for (int z : zPoints) + spawnPoints.add(new Vector2i(x, z)); + return spawnPoints; + } + + private static List generateLinearSpawnpoints(int start, int length, int maxStep) { + int stepCount = (int) Math.ceil((double) length / maxStep); + double realStep = length / stepCount; + return IntStream.range(0, stepCount).mapToObj(i -> start + (int) (realStep * i)) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/Main.java b/src/main/java/morlok8k/MinecraftLandGenerator/Main.java deleted file mode 100644 index dc379b8..0000000 --- a/src/main/java/morlok8k/MinecraftLandGenerator/Main.java +++ /dev/null @@ -1,480 +0,0 @@ -/* -####################################################################### -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE # -# Version 2, December 2004 # -# # -# Copyright (C) 2004 Sam Hocevar # -# # -# Everyone is permitted to copy and distribute verbatim or modified # -# copies of this license document, and changing it is allowed as long # -# as the name is changed. # -# # -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE # -# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION # -# # -# 0. You just DO WHAT THE FUCK YOU WANT TO. # -# # -####################################################################### -*/ - -/* - * The Computer Programmer's Lament: Program complexity grows until it exceeds the capability of the programmer who must maintain it. - */ - -package morlok8k.MinecraftLandGenerator; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.joml.Vector3i; - -/** - * @author Corrodias, Morlok8k, pr0f1x - */ -public class Main { - - ////////////////////////////////////////////////////////// - // REMINDER: Because I always forget/mix up languages: // - // "static" in java means that there will only ever be ONE of it created, shared by all the instances of that class.// - // "final" means "constant" // - // public/private shows/hides between classes // - ////////////////////////////////////////////////////////// - - /** - * @param args - * the command line arguments - */ - private static Log log = LogFactory.getLog(Main.class); - - public static void main(String[] args) { - Main.runCLI(); - } - - /** - * Start MinecraftLandGenerator (Command Line Interface) - * - * @author Corrodias, Morlok8k - */ - private static void runCLI() { - final File backupLevel; - final File serverLevel; - - // Basic Program Initialization - Startup.initialStart(); - if (Startup.programArguments()) { - log.error("Error in program arguments."); - return; - } - if (Startup.confFile()) { - log.error("Error in conf file."); - return; - } - try { - if (Setup.doSetup()) { - log.error("Error in setup."); - return; - } - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } // Checks for server.properties, checks for world, creates world if needed, checks old data} - - // ===================================================================== - // PROCESSING - // ===================================================================== - - log.info("Processing world \"" + var.worldPath + "\", in " + var.increment - + " block increments, with: " + var.javaLine); - // out( MLG + "Processing \"" + worldName + "\"..."); - - log.info(""); - - try { - final long generationStartTimeTracking = System.currentTimeMillis(); //Start of time remaining calculations - - final boolean serverLaunch = Server.runMinecraft(); //run server once at spawn point to make sure everything works. - //final boolean serverLaunch = true; //testing only - if (!(serverLaunch)) { - System.exit(1); // we got a warning or severe error - } - - if ((var.xRange == 0) & (var.zRange == 0)) { //If the server is launched with an X and a Z of zero, then we just shutdown MLG after the initial launch. - return; - } - - FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile, - "# " + var.PROG_NAME + " " + var.VERSION + " - " + SelfAware.JVMinfo() - + var.newLine + "# " + var.MC_Server_Version + var.newLine - + "# Started: " + var.dateFormat.format(generationStartTimeTracking) - + var.newLine + "##Size: X" + var.xRange + "Z" + var.zRange - + var.newLine); - - log.info(""); - - serverLevel = new File(var.worldPath + var.fileSeparator + "level.dat"); - backupLevel = new File(var.worldPath + var.fileSeparator + "level_backup.dat"); - - log.info("Backing up level.dat to level_backup.dat.\n"); - Misc.copyFile(serverLevel, backupLevel); - - final Vector3i spawn = SpawnPoint.getSpawn(serverLevel); - log.info("Spawn point detected: [X,Y,Z] " + spawn); - - FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile, "# Seed: " - + var.randomSeed + var.newLine + "# Spawn: " + spawn.toString() + var.newLine); - - boolean overridden = false; - if (var.xOffset == null) { - var.xOffset = spawn.x; - } else { - overridden = true; - } - if (var.zOffset == null) { - var.zOffset = spawn.z; - } else { - overridden = true; - } - - double xR = 0, zR = 0, xO = 0, zO = 0; - - if (var.useChunks) { // use Chunks or Regions - - xR = var.xRange; //say xRange was entered as 1000. this changes it to be 1008, a multiple of 16. (the size of a chunk) - xR = xR / 16; - xR = Math.ceil(xR); - xR = xR * 16; - - zR = var.zRange; //say zRange was entered as 2000. there is no change, as it already is a multiple of 16. - zR = zR / 16; - zR = Math.ceil(zR); - zR = zR * 16; - - xO = var.xOffset; - xO = xO / 16; - xO = Math.round(xO); //round instead of Ceiling - xO = xO * 16; - - zO = var.zOffset; - zO = zO / 16; - zO = Math.round(zO); //round instead of Ceiling - zO = zO * 16; - - } else { - - xR = var.xRange; //say xRange was entered as 1000. this changes it to be 1024, a multiple of 512. (the size of a region) - xR = xR / 512; - xR = Math.ceil(xR); - xR = xR * 512; - - zR = var.zRange; //say zRange was entered as 2048. there is no change, as it already is a multiple of 512. - zR = zR / 512; - zR = Math.ceil(zR); - zR = zR * 512; - - xO = var.xOffset; - xO = xO / 512; - xO = Math.round(xO); //round instead of Ceiling - xO = xO * 512; - - zO = var.zOffset; - zO = zO / 512; - zO = Math.round(zO); //round instead of Ceiling - zO = zO * 512; - - } - - var.xRange = (int) Math.ceil(xR); - var.zRange = (int) Math.ceil(zR); - var.xOffset = (int) Math.ceil(xO); - var.zOffset = (int) Math.ceil(zO); - - if (overridden) { - log.info("Centering land generation on [" + var.xOffset + ", " + var.zOffset - + "] due to switches."); - } else { - log.info("Centering land generation on [" + var.xOffset + ", " + var.zOffset - + "]\n"); - } - - double xLoops, zLoops; - long curXloops = 0; - long curZloops = 0; - double xRangeAdj = 0; - double zRangeAdj = 0; - - // have main loop make an arraylist of spawnpoints - // read from a file if MLG has run before on this world. save to arraylist - // remove existing points from new list. - // run mlg on remaining list of spawn points. - - // X - xRangeAdj = var.xRange - var.incrementFull; //Adjusting our range of X - xRangeAdj = xRangeAdj / var.increment; - xRangeAdj = Math.ceil(xRangeAdj); //round up to find out! - xRangeAdj = xRangeAdj + 1; //add an additional increment, as we removed a full one above - xRangeAdj = xRangeAdj * var.increment; - - double inc = var.increment * 2; //increment*2, and casts to double. - - // thanks to e2pii (on reddit) for correcting my math here. - // http://www.reddit.com/r/learnmath/comments/2y14fq/what_am_i_overlooking_here/ - xLoops = (var.xRange - var.incrementFull) / inc; //How many loops do we need? - xLoops = Math.ceil(xLoops); - xLoops = xLoops + xLoops + 1; - - // Z - zRangeAdj = var.zRange - var.incrementFull; //Adjusting our range of Z - zRangeAdj = zRangeAdj / var.increment; - zRangeAdj = Math.ceil(zRangeAdj); //round up to find out! - zRangeAdj = zRangeAdj + 1; //add an additional increment, as we removed a full one above - zRangeAdj = zRangeAdj * var.increment; - - zLoops = (var.zRange - var.incrementFull) / inc; //How many loops do we need? - zLoops = Math.ceil(zLoops); - zLoops = zLoops + zLoops + 1; - - log.info("Calculating Spawn Points..."); - - // Perfect Squares Code: - - /* - if (xLoops > 3) { - xLoops = xLoops + 1; - } - - if (zLoops > 3) { - zLoops = zLoops + 1; - } - */ - - long totalIterations = (long) (xLoops * zLoops); - - log.info("Estimated Total Spawn Points: " + totalIterations); - - if (totalIterations > Integer.MAX_VALUE) { - log.error( - "TOO BIG! Please reduce the world size. World Size can't be larger than 17609200 x 17609200"); //or 17794560 using -i384 - backupLevel.delete(); - log.info("Removed backup file."); - System.exit(0); - } - - long currentIteration = 0; - - long differenceTime = System.currentTimeMillis(); - - Long timeTracking; - - ArrayList launchList = new ArrayList<>(0); - try { - launchList = new ArrayList<>((int) totalIterations); - } catch (Exception e1) { - e1.printStackTrace(); - log.error( - "TOO BIG! Your computer can't handle such a large map. The size is dependant on 32/64 bit and Memory."); - backupLevel.delete(); - log.info("Removed backup file."); - System.exit(0); - } - - // X - West to East - for (long currentX = 0; currentX <= (xRangeAdj / 2); currentX += var.increment) { - curXloops++; - boolean eastEdgeReached = false; - - if (curXloops == 1) { - currentX = (((0 - var.xRange) / 2) + (var.incrementFull / 2)); // West Edge of map - - } else if (currentX >= ((xRangeAdj / 2) - (var.increment / 2))) { - currentX = ((var.xRange / 2) - (var.incrementFull / 2)); // East Edge of map - eastEdgeReached = true; - } - - // Z - North to South - for (long currentZ = 0; currentZ <= (zRangeAdj / 2); currentZ += var.increment) { - currentIteration++; - - curZloops++; - boolean southEdgeReached = false; - if (curZloops == 1) { - currentZ = (((0 - var.zRange) / 2) + (var.incrementFull / 2)); // North Edge of map - } else if (currentZ >= ((zRangeAdj / 2) - (var.increment / 2))) { - currentZ = ((var.zRange / 2) - (var.incrementFull / 2)); // South Edge of map - southEdgeReached = true; - } - - { // Middle of Loop - - if (currentIteration % 10000000 == 0) { //for long calculations, we output an update every 10,000,000 points - String percentDone = Double.toString( - (((double) currentIteration) / totalIterations) * 100); - final int percentIndex = - ((percentDone.indexOf(".") + 3) > percentDone.length()) - ? percentDone.length() : (percentDone.indexOf(".") + 3); //fix index on numbers like 12.3 - percentDone = percentDone.substring(0, (percentDone.indexOf(".") == -1 - ? percentDone.length() : percentIndex)); //Trim output, unless whole number - log.info("Calculated: " + currentIteration + "/" + totalIterations - + " spawn points. (" + percentDone + "% Done)"); - } - - // add Coordinates to arraylist here - final Vector3i tempCoords = new Vector3i((int) currentX + var.xOffset, 64, - (int) currentZ + var.zOffset); - launchList.add(tempCoords); - - // Write the current Coordinates to log file! - //FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator - // + "MLG_coordinate_list.log", tempCoords.toString() + var.newLine); - - if (var.testing) { - System.out.println(tempCoords); - } - - } // End of the Middle of Loop - - if (curZloops == 1) { // We are at the North edge. We have special code for the North edge, so we need to change currentZ to be normal again. - currentZ = (long) ((Math.ceil((((0 - zRangeAdj) / 2) / var.increment))) - * var.increment); - } - if (southEdgeReached) { - currentZ = (long) zRangeAdj; // We reached the South edge, so we make sure that we exit the "for loop", bypassing the "1152 bug" - } - - } // End Z - curZloops = 0; - if (curXloops == 1) { // We are at the West edge. We have special code for the West edge, so we need to change currentX to be normal again. - currentX = (long) ((Math.ceil((((0 - xRangeAdj) / 2) / var.increment))) - * var.increment); - } - if (eastEdgeReached) { - currentX = (long) xRangeAdj; // We reached the East edge, so we make sure that we exit the "for loop", bypassing the "1152 bug" - } - - } // End X - - String pD = Double.toString((((double) currentIteration) / totalIterations) * 100); - final int pI = - ((pD.indexOf(".") + 3) > pD.length()) ? pD.length() : (pD.indexOf(".") + 3); //fix index on numbers like 12.3 - pD = pD.substring(0, (pD.indexOf(".") == -1 ? pD.length() : pI)); //Trim output, unless whole number - log.info("Calculated: " + currentIteration + "/" + totalIterations + " spawn points. (" - + pD + "% Done)"); - - //get existing list, and remove this list from launchList - final ArrayList removeList = - FileRead.readArrayListCoordLog(var.worldPath + var.fileSeparator + var.logFile); - - log.info("Removing known generated areas..."); - if (!removeList.isEmpty()) { - launchList.removeAll(removeList); - } - - removeList.clear(); // we are done with this now. - - System.gc(); //run the garbage collector - hopefully free up some memory! - - currentIteration = 0; - totalIterations = launchList.size(); - Vector3i xyz = null; - final Iterator coordArrayIterator = launchList.iterator(); - while (coordArrayIterator.hasNext()) { - currentIteration++; - xyz = coordArrayIterator.next(); - - //////// Start server launch code - - String percentDone = - Double.toString((((double) currentIteration - 1) / totalIterations) * 100); - final int percentIndex = ((percentDone.indexOf(".") + 3) > percentDone.length()) - ? percentDone.length() : (percentDone.indexOf(".") + 3); //fix index on numbers like 12.3 - percentDone = percentDone.substring(0, - (percentDone.indexOf(".") == -1 ? percentDone.length() : percentIndex)); //Trim output, unless whole number - - log.info("Setting spawn to [X,Y,Z]: " + xyz + " (" + currentIteration + " of " - + totalIterations + ") " + percentDone + "% Done"); // Time Remaining estimate - - timeTracking = System.currentTimeMillis(); - - //NEW CODE: - differenceTime = - (timeTracking - generationStartTimeTracking) / (currentIteration + 1); // Updated. we now count all runs, instead of the last 4. - differenceTime *= 1 + (totalIterations - currentIteration); // this should provide a more accurate result. - log.info("Estimated time remaining: " + String.format("%02d:%02d", - (differenceTime / 1000) / 60, (differenceTime / 1000) % 60)); // I've noticed it gets pretty accurate after about 8 launches! - - // Set the spawn point - SpawnPoint.setSpawn(serverLevel, xyz); - - // Launch the server - boolean serverSuccess = false; - - serverSuccess = Server.runMinecraft(); - log.info(""); - - //////// End server launch code - - if (serverSuccess) { - // Write the current Coordinates to log file! - FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile, - xyz.toString() + var.newLine); - } else { - System.exit(1); // we got a warning or severe error - } - - } - - if (currentIteration == 0) { - log.info("Nothing to generate!"); - } else { - //TODO: write completion code to output. - // FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile, ####### + var.newLine); - // - //add xrange, zrange, region/chunk, xoffset, zoffset, increment //anything else? - // - log.info("Finished generating chunks."); - } - - Misc.copyFile(backupLevel, serverLevel); - backupLevel.delete(); - log.info("Restored original level.dat."); - long completeIn = var.startTime - System.currentTimeMillis(); - log.info("Generation complete in: " + String.format("%02d:%02d", - (completeIn / 1000) / 60, (completeIn / 1000) % 60)); - // Time.waitTenSec(false); - - if (var.webLaunch) { //if webLaunch is already false, don't check for these things - if (java.awt.GraphicsEnvironment.isHeadless()) { - var.webLaunch = false; //headless enviroment - cant bring up webpage! - } - final File web1 = new File("web"); - final File web2 = new File("web.txt"); //user has put in the magical file to not launch the webpage - final File web3 = new File("web.txt.txt"); - if (web2.exists() || (web1.exists() || web3.exists())) { //check for "web.txt", if not found, check for "web", and if still not found, check for "web.txt.txt" - var.webLaunch = false; - } - } - - if (var.webLaunch && java.awt.Desktop.isDesktopSupported()) { - final URI splashPage = URI.create("http://adf.ly/520855/splashbanner"); - try { - java.awt.Desktop.getDesktop().browse(splashPage); - } catch (final IOException e) { - log.error("Error displaying webpage... " + e.getLocalizedMessage()); - } - } else { - log.info("Please Visit: http://adf.ly/520855/mlg"); - log.info("Or: " + var.WEBSITE); - log.info("Thanks!"); - } - - } catch (final IOException ex) { - Logger.getLogger(Main.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); - } - } -} diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/Server.java b/src/main/java/morlok8k/MinecraftLandGenerator/Server.java index aa00597..128c7f5 100644 --- a/src/main/java/morlok8k/MinecraftLandGenerator/Server.java +++ b/src/main/java/morlok8k/MinecraftLandGenerator/Server.java @@ -8,12 +8,33 @@ package morlok8k.MinecraftLandGenerator; import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jnbt.CompoundTag; +import org.jnbt.IntTag; +import org.jnbt.NBTInputStream; +import org.jnbt.NBTOutputStream; +import org.jnbt.Tag; +import org.joml.Vector2i; +import org.joml.Vector3i; /** * @@ -21,7 +42,170 @@ import org.apache.commons.logging.LogFactory; */ public class Server { - private static Log log = LogFactory.getLog(Main.class); + private static Log log = LogFactory.getLog(Server.class); + + protected final ProcessBuilder builder; + protected final Path workDir; + + public Server(String[] javaOpts, Path serverFile) { + List opts = new ArrayList<>( + Arrays.asList(javaOpts != null ? javaOpts : new String[] { "java", "-jar" })); + opts.add(serverFile.toString()); + opts.add("nogui"); + builder = new ProcessBuilder(opts); + builder.redirectErrorStream(true); + workDir = serverFile.getParent(); + builder.directory(workDir.toFile()); + } + + public void setWorld(Path worldPath) throws IOException { + Path propsFile = workDir.resolve("server.properties"); + if (!Files.exists(propsFile)) { + Files.write(propsFile, "level-name=".concat(propsFile.toString()).getBytes()); + } else { + /* Make a backup first*/ + Files.copy(propsFile, propsFile.resolveSibling("server.properties.bak")); + Properties props = new Properties(); + props.load(Files.newInputStream(propsFile)); + props.put("level-name", worldPath.toString()); + props.store(Files.newOutputStream(propsFile), null); + } + } + + public Path getWorld() throws IOException { + Path propsFile = workDir.resolve("server.properties"); + Properties props = new Properties(); + if (!Files.exists(propsFile)) return null; + props.load(Files.newInputStream(propsFile)); + return Paths.get(props.getProperty("level-name")); + } + + public void restoreWorld() throws IOException { + Path propsFile = workDir.resolve("server.properties"); + Files.move(propsFile.resolveSibling("server.properties.bak"), propsFile, + StandardCopyOption.REPLACE_EXISTING); + } + + /** + * @param level + * @return + * @throws IOException + * @author Corrodias + */ + protected static Vector3i getSpawn(final File level) throws IOException { + try { + final NBTInputStream input = new NBTInputStream(new FileInputStream(level)); + final CompoundTag originalTopLevelTag = (CompoundTag) input.readTag(); + input.close(); + + final Map originalData = + ((CompoundTag) originalTopLevelTag.getValue().get("Data")).getValue(); + // This is our map of data. It is an unmodifiable map, for some + // reason, so we have to make a copy. + final Map newData = new LinkedHashMap<>(originalData); + // .get() a couple of values, just to make sure we're dealing with a + // valid level file, here. Good for debugging, too. + final IntTag spawnX = (IntTag) newData.get("SpawnX"); + final IntTag spawnY = (IntTag) newData.get("SpawnY"); + final IntTag spawnZ = (IntTag) newData.get("SpawnZ"); + + final Vector3i ret = + new Vector3i(spawnX.getValue(), spawnY.getValue(), spawnZ.getValue()); + return ret; + } catch (final ClassCastException ex) { + throw new IOException("Invalid level format."); + } catch (final NullPointerException ex) { + throw new IOException("Invalid level format."); + } + } + + protected static void setSpawn(Path world, Vector2i chunkSpawn) throws IOException { + setSpawn(world.resolve("level.dat").toFile(), + new Vector3i(chunkSpawn.x << 4 | 7, 64, chunkSpawn.y << 4 | 8)); + } + + /** + * Changes the spawn point in the given Alpha/Beta level to the given coordinates.
+ * Note that, in Minecraft levels, the Y coordinate is height.
+ * (We picture maps from above, but the game was made from a different perspective) + * + * @param level + * the level file to change the spawn point in + * @param xyz + * the Coordinates of the spawn point + * @throws IOException + * if there are any problems reading/writing the file + * @author Corrodias + */ + protected static void setSpawn(final File level, final Vector3i xyz) throws IOException { + + try { + final NBTInputStream input = new NBTInputStream(new FileInputStream(level)); + final CompoundTag originalTopLevelTag = (CompoundTag) input.readTag(); + input.close(); + + //@formatter:off + + //Note: The Following Information is Old (from 2010), compared to the Data inside a current "level.dat". + //However, What we look at (SpawnX,Y,Z and RandomSeed) have not changed. + + /* + * Structure: + * + *TAG_Compound("Data"): World data. + * * TAG_Long("Time"): Stores the current "time of day" in ticks. There are 20 ticks per real-life second, and 24000 ticks per Minecraft day, making the day length 20 minutes. 0 appears to be sunrise, 12000 sunset and 24000 sunrise again. + * * TAG_Long("LastPlayed"): Stores the Unix time stamp (in milliseconds) when the player saved the game. + * * TAG_Compound("Player"): Player entity information. See Entity Format and Mob Entity Format for details. Has additional elements: + * o TAG_List("Inventory"): Each TAG_Compound in this list defines an item the player is carrying, holding, or wearing as armor. + * + TAG_Compound: Inventory item data + * # TAG_Short("id"): Item or Block ID. + * # TAG_Short("Damage"): The amount of wear each item has suffered. 0 means undamaged. When the Damage exceeds the item's durability, it breaks and disappears. Only tools and armor accumulate damage normally. + * # TAG_Byte("Count"): Number of items stacked in this inventory slot. Any item can be stacked, including tools, armor, and vehicles. Range is 1-255. Values above 127 are not displayed in-game. + * # TAG_Byte("Slot"): Indicates which inventory slot this item is in. + * o TAG_Int("Score"): Current score, doesn't appear to be implemented yet. Always 0. + * * TAG_Int("SpawnX"): X coordinate of the player's spawn position. Default is 0. + * * TAG_Int("SpawnY"): Y coordinate of the player's spawn position. Default is 64. + * * TAG_Int("SpawnZ"): Z coordinate of the player's spawn position. Default is 0. + * * TAG_Byte("SnowCovered"): 1 enables, 0 disables, see Winter Mode + * * TAG_Long("SizeOnDisk"): Estimated size of the entire world in bytes. + * * TAG_Long("RandomSeed"): Random number providing the Random Seed for the terrain. + * + */ + + //@formatter:on + + final Map originalData = + ((CompoundTag) originalTopLevelTag.getValue().get("Data")).getValue(); + // This is our map of data. It is an unmodifiable map, for some reason, so we have to make a copy. + final Map newData = new LinkedHashMap<>(originalData); + + // .get() a couple of values, just to make sure we're dealing with a valid level file, here. Good for debugging, too. + @SuppressWarnings("unused") + final IntTag spawnX = (IntTag) newData.get("SpawnX"); // we never use these... Its only here for potential debugging. + @SuppressWarnings("unused") + final IntTag spawnY = (IntTag) newData.get("SpawnY"); // but whatever... so I (Morlok8k) suppressed these warnings. + @SuppressWarnings("unused") + final IntTag spawnZ = (IntTag) newData.get("SpawnZ"); // I don't want to remove existing code, either by myself (Morlok8k) or Corrodias + + newData.put("SpawnX", new IntTag("SpawnX", xyz.x)); // pulling the data out of the Coordinates, + newData.put("SpawnY", new IntTag("SpawnY", xyz.y)); // and putting it into our IntTag's + newData.put("SpawnZ", new IntTag("SpawnZ", xyz.z)); + + // Again, we can't modify the data map in the old Tag, so we have to make a new one. + final CompoundTag newDataTag = new CompoundTag("Data", newData); + final Map newTopLevelMap = new HashMap<>(1); + newTopLevelMap.put("Data", newDataTag); + final CompoundTag newTopLevelTag = new CompoundTag("", newTopLevelMap); + + final NBTOutputStream output = new NBTOutputStream(new FileOutputStream(level)); + output.writeTag(newTopLevelTag); + output.close(); + } catch (final ClassCastException ex) { + throw new IOException("Invalid level format."); + } catch (final NullPointerException ex) { + throw new IOException("Invalid level format."); + } + } /** * Starts the process in the given ProcessBuilder, monitors its output for a "Done" message, and sends it a "stop" message. @@ -31,9 +215,9 @@ public class Server { * @throws InterruptedException * @author Corrodias, Morlok8k, piegames */ - protected static void runMinecraft() throws IOException, InterruptedException { - log.info("Starting server."); - final Process process = var.minecraft.start(); + public void runMinecraft() throws IOException, InterruptedException { + log.info("Starting server"); + final Process process = builder.start(); final BufferedReader pOut = new BufferedReader(new InputStreamReader(process.getInputStream())); @@ -44,7 +228,7 @@ public class Server { if (line.contains("Done")) { PrintStream out = new PrintStream(process.getOutputStream()); - log.info("Stopping server... "); + log.info("Stopping server..."); out.println("save-all"); out.flush(); out.println("stop"); diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/Setup.java b/src/main/java/morlok8k/MinecraftLandGenerator/Setup.java deleted file mode 100644 index c3f5b81..0000000 --- a/src/main/java/morlok8k/MinecraftLandGenerator/Setup.java +++ /dev/null @@ -1,158 +0,0 @@ -/* -####################################################################### -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE # -# Version 2, December 2004 # -# # -# Copyright (C) 2004 Sam Hocevar # -# # -# Everyone is permitted to copy and distribute verbatim or modified # -# copies of this license document, and changing it is allowed as long # -# as the name is changed. # -# # -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE # -# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION # -# # -# 0. You just DO WHAT THE FUCK YOU WANT TO. # -# # -####################################################################### -*/ - -package morlok8k.MinecraftLandGenerator; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * - * @author morlok8k - */ - -//TODO : Remove var.worldPath entirely because storing that is useless. - -public class Setup { - - private static Log log = LogFactory.getLog(Main.class); - - static boolean doSetup() throws IOException { - final File serverPathFile; - final BufferedReader serverPropertiesFile; - final File levelDat; - final File backupLevel; - String line; - - //---------- Verify server path - serverPathFile = new File(var.serverPath); - - if (!serverPathFile.exists() || !serverPathFile.isDirectory()) { - /*FileNotFoundException fileException = - new FileNotFoundException("The server directory is invalid: " + var.serverPath); - throw fileException;*/ - log.error("The server directory is invalid: " + var.serverPath); - return true; - } - - //---------- Verify server.properties - try { - serverPropertiesFile = new BufferedReader(new FileReader( - new File(var.serverPath + var.fileSeparator + "server.properties"))); - } catch (IOException e) { - log.error("Could not open the server.properties file."); - return true; - } - - //---------- Set world name - try { - line = serverPropertiesFile.readLine(); - } catch (IOException e) { - serverPropertiesFile.close(); - return true; - } - - while (line != null) { - if (line.contains("level-name")) { // Yep, that line contains it - if (line.contains("#") && line.indexOf('=') < line.indexOf('#')) { - // Apparently, "#slf dghdsf #gdcfggh" is a perfectly valid world name. - // In the other cases, the line is commented or invalid. - var.worldName = line.substring(line.indexOf('=') + 1); - var.worldPath = var.serverPath + var.fileSeparator + var.worldName; - } else { // There is no comment on the line - var.worldName = line.substring(line.indexOf('=') + 1); - var.worldPath = var.serverPath + var.fileSeparator + var.worldName; - } - } - try { - line = serverPropertiesFile.readLine(); - } catch (IOException e) { - serverPropertiesFile.close(); - return true; - } - - } - - serverPropertiesFile.close(); - - if (var.worldName == null) { // If after all this we still don't have a proper world name, stop everything and throw an exception - /*NullPointerException noNameException = new NullPointerException("There is no world name defined in the server.properties file!"); - throw noNameException;*/ - log.error("There is no world name defined in the server.properties file!"); - return true; - } - - //---------- Verify that the world exists and restore level_backup.dat if it exists. If not, start server once to create the world. - levelDat = new File(var.worldPath + var.fileSeparator + "level.dat"); - backupLevel = new File(var.worldPath + var.fileSeparator + "level_backup.dat"); - - // prepare our two ProcessBuilders - // minecraft = new ProcessBuilder(javaLine, "-Xms1024m", "-Xmx1024m", "-jar", jarFile, "nogui"); - var.minecraft = new ProcessBuilder(var.javaLine.split("\\s")); // is this always going to work? i don't know. (most likely yes) - var.minecraft.directory(new File(var.serverPath)); - var.minecraft.redirectErrorStream(true); - - if (levelDat.exists() && levelDat.isFile()) { - if (backupLevel.exists()) { - log.error( - "There is a level_backup.dat file left over from a previous attempt that failed."); - log.info("Resuming..."); - - //use resume data - final File serverLevel = new File(var.worldPath + var.fileSeparator + "level.dat"); - try { - Misc.copyFile(backupLevel, serverLevel); - } catch (final IOException e) { - e.printStackTrace(); - } - backupLevel.delete(); - - //return; - - FileRead.readArrayListCoordLog(var.worldPath + var.fileSeparator + var.logFile); // we read the .log just for any resume data, if any. - - System.gc(); //run the garbage collector - hopefully free up some memory! - - var.xRange = var.resumeX; - var.zRange = var.resumeZ; - - } - } else { - /*FileNotFoundException fileException = - new FileNotFoundException("The currently configured world does not exist.");*/ - log.error( - "The currently configured world does not exist! Launching the server once to create it..."); - try { - var.minecraft = new ProcessBuilder(var.javaLine.split("\\s")); // is this always going to work? i don't know. (most likely yes) - var.minecraft.directory(new File(var.serverPath)); - var.minecraft.redirectErrorStream(true); - Server.runMinecraft(); - } catch (IOException | InterruptedException e) { - return true; - } - log.error("World created! Starting world generation..."); - } - return false; - } -}