diff --git a/.classpath b/.classpath index aa54b47..def0e4f 100644 --- a/.classpath +++ b/.classpath @@ -6,6 +6,7 @@ + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 1426f45..242ac95 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -7,6 +7,15 @@ org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*.launch org.eclipse.jdt.core.circularClasspath=error org.eclipse.jdt.core.classpath.exclusionPatterns=enabled org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve @@ -15,6 +24,7 @@ org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.maxProblemPerUnit=100 +org.eclipse.jdt.core.compiler.problem.APILeak=warning org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore @@ -26,9 +36,10 @@ org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod= org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.fieldHiding=info org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=error @@ -39,7 +50,9 @@ org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled @@ -48,12 +61,20 @@ org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warni org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning @@ -63,19 +84,27 @@ org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=enabled org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=warning org.eclipse.jdt.core.compiler.problem.unusedLabel=warning org.eclipse.jdt.core.compiler.problem.unusedLocal=warning @@ -85,6 +114,7 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference= org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.release=disabled diff --git a/pom.xml b/pom.xml index 3a87875..8d4de58 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ commons-logging commons-logging - 1.1.1 + 1.2 @@ -48,6 +48,11 @@ log4j-core 2.11.1 + + org.apache.logging.log4j + log4j-jcl + 2.11.1 + org.joml diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java b/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java index 1fe6e86..21d7227 100644 --- a/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java +++ b/src/main/java/morlok8k/MinecraftLandGenerator/CommandLineMain.java @@ -1,6 +1,6 @@ package morlok8k.MinecraftLandGenerator; -import java.nio.file.Files; +import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -9,47 +9,33 @@ import java.util.stream.IntStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configurator; import org.joml.Vector2i; +import morlok8k.MinecraftLandGenerator.CommandLineMain.AutoSpawnpoints; +import morlok8k.MinecraftLandGenerator.CommandLineMain.ManualSpawnpoints; 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; - -@Command(name = "mlg", subcommands = { HelpCommand.class }) +import picocli.CommandLine.ParentCommand; +import picocli.CommandLine.RunAll; +@Command(name = "MinecraftLandGenerator", + subcommands = { HelpCommand.class, ManualSpawnpoints.class, AutoSpawnpoints.class }) public class CommandLineMain implements Runnable { 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") - private boolean regionFile = false; - @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) - private int increment = 380; - - @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") - private int zOffset = 0; - - @Parameters(index = "0", description = "X-coordinate") - private int X; - - @Parameters(index = "1", description = "Z-coordinate") - private int Z; + @Option(names = { "--debug-server" }, + description = "Print the Minecraft server log to stdout for debugging") + private boolean debugServer = false; @Option(names = { "-s", "--serverFile" }, description = "Path to the server's jar file.", defaultValue = "server.jar", showDefaultValue = Visibility.ALWAYS) @@ -63,37 +49,117 @@ public class CommandLineMain implements Runnable { description = "Java command to launch the server. Defaults to `java -jar`.") private String[] javaOpts; + @Command(name = "auto-spawnpoints") + public static class AutoSpawnpoints implements Runnable { + + @ParentCommand + private CommandLineMain parent; + + @Option(names = "-i", description = "override the iteration spawn offset increment", + defaultValue = "380", showDefaultValue = CommandLine.Help.Visibility.ALWAYS) + private int increment = 380; + + @Parameters(index = "0", description = "X-coordinate") + private int x; + @Parameters(index = "1", description = "Z-coordinate") + private int z; + @Parameters(index = "2", description = "Width") + private int w; + @Parameters(index = "3", description = "Height") + private int h; + + public AutoSpawnpoints() { + } + + @Override + public void run() { + Server server = new Server(parent.debugServer, parent.javaOpts, parent.serverFile); + World world; + try { + world = server.initWorld(parent.worldPath); + } catch (IOException | InterruptedException e) { + log.error("Could not initialize world", e); + return; + } + + log.info("Generating world"); + server.runMinecraft(world, generateSpawnpoints(x, z, w, h, increment, 12)); + log.info("Cleaning up temporary files"); + try { + world.resetSpawn(); + server.restoreWorld(); + } catch (IOException e) { + log.warn( + "Could not delete backup files (server.properties.bak and level.dat.bak). Please delete them manually", + e); + } + log.info("Done."); + } + + } + + @Command(name = "manual-spawnpoints") + public static class ManualSpawnpoints implements Runnable { + + @ParentCommand + private CommandLineMain parent; + +// @Option(names = { "-s", "--customspawn" }, description = "Customized SpawnPoints") +// private String[] customSpawnPoints; + + public ManualSpawnpoints() { + } + + @Override + public void run() { + Server server = new Server(parent.debugServer, parent.javaOpts, parent.serverFile); + World world; + try { + world = server.initWorld(parent.worldPath); + } catch (IOException | InterruptedException e) { + log.error("Could not initialize world", e); + return; + } + List spawnpoints = new ArrayList<>(); + spawnpoints.add(new Vector2i(100, 100)); + spawnpoints.add(new Vector2i(200, 100)); + spawnpoints.add(new Vector2i(300, 100)); + spawnpoints.add(new Vector2i(400, 100)); + log.info("Generating world"); + server.runMinecraft(world, spawnpoints); + log.info("Cleaning up temporary files"); + try { + world.resetSpawn(); + server.restoreWorld(); + } catch (IOException e) { + log.warn( + "Could not delete backup files (server.properties.bak and level.dat.bak). Please delete them manually", + e); + } + log.info("Done."); + } + } + public CommandLineMain() { } @Override public void run() { - 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 (verbose) { + Configurator.setRootLevel(Level.DEBUG); } - 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) { + /* Without this, JOML will print vectors out in scientific notation which isn't the most human readable thing in the world */ + System.setProperty("joml.format", "false"); + + args = new String[] { "-v", "--debug-server", "-s", + "/home/piegames/Documents/GitHub/MinecraftLandGenerator/testserver/server.jar", + "auto-spawnpoints", "200", "100", "200", "100" }; CommandLine cli = new CommandLine(new CommandLineMain()); - cli.parseWithHandler(new RunLast(), args); + cli.parseWithHandler(new RunAll(), args); } /** diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/Server.java b/src/main/java/morlok8k/MinecraftLandGenerator/Server.java index 8772764..83ba8c8 100644 --- a/src/main/java/morlok8k/MinecraftLandGenerator/Server.java +++ b/src/main/java/morlok8k/MinecraftLandGenerator/Server.java @@ -8,35 +8,23 @@ 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.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; 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.joml.Vector2i; -import org.joml.Vector3i; - -import com.flowpowered.nbt.CompoundMap; -import com.flowpowered.nbt.CompoundTag; -import com.flowpowered.nbt.IntTag; -import com.flowpowered.nbt.Tag; -import com.flowpowered.nbt.stream.NBTInputStream; -import com.flowpowered.nbt.stream.NBTOutputStream; /** * @@ -48,8 +36,10 @@ public class Server { protected final ProcessBuilder builder; protected final Path workDir; + protected final boolean debugServer; - public Server(String[] javaOpts, Path serverFile) { + public Server(boolean debugServer, String[] javaOpts, Path serverFile) { + this.debugServer = debugServer; List opts = new ArrayList<>( Arrays.asList(javaOpts != null ? javaOpts : new String[] { "java", "-jar" })); opts.add(serverFile.toString()); @@ -60,6 +50,23 @@ public class Server { builder.directory(workDir.toFile()); } + public World initWorld(Path worldPath) throws IOException, InterruptedException { + if (worldPath != null) + setWorld(worldPath); + else worldPath = 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..."); + runMinecraft(); + worldPath = getWorld(); + if (!worldPath.isAbsolute()) worldPath = workDir.resolve(worldPath); + } + if (worldPath == null || !Files.exists(worldPath)) + throw new NoSuchFileException(String.valueOf(worldPath)); + log.debug("Using world path " + worldPath); + return new World(worldPath); + } + public void setWorld(Path worldPath) throws IOException { Path propsFile = workDir.resolve("server.properties"); if (!Files.exists(propsFile)) { @@ -79,83 +86,32 @@ public class Server { Properties props = new Properties(); if (!Files.exists(propsFile)) return null; props.load(Files.newInputStream(propsFile)); - return Paths.get(props.getProperty("level-name")); + if (!props.containsKey("level-name")) return null; + log.info("Found level in server.properties: " + props.getProperty("level-name")); + Path p = Paths.get(props.getProperty("level-name")); + if (!p.isAbsolute()) p = workDir.resolve(p); + return p; } public void restoreWorld() throws IOException { Path propsFile = workDir.resolve("server.properties"); - Files.move(propsFile.resolveSibling("server.properties.bak"), propsFile, - StandardCopyOption.REPLACE_EXISTING); + if (Files.exists(propsFile.resolveSibling("server.properties.bak"))) + 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 (NBTInputStream input = new NBTInputStream(new FileInputStream(level));) { - final CompoundTag levelTag = (CompoundTag) input.readTag(); - final Map> levelData = - ((CompoundTag) levelTag.getValue().get("Data")).getValue(); - - final IntTag spawnX = (IntTag) levelData.get("SpawnX"); - final IntTag spawnY = (IntTag) levelData.get("SpawnY"); - final IntTag spawnZ = (IntTag) levelData.get("SpawnZ"); - - return new Vector3i(spawnX.getValue(), spawnY.getValue(), spawnZ.getValue()); - } catch (final ClassCastException | 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 { - // TODO clean this up even more - try (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); - - 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", new CompoundMap(newData)); - final Map> newTopLevelMap = new HashMap<>(1); - newTopLevelMap.put("Data", newDataTag); - final CompoundTag newTopLevelTag = new CompoundTag("", new CompoundMap(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."); + public void runMinecraft(World world, List spawnpoints) { + log.debug("All spawn points: " + spawnpoints); + for (int i = 0; i < spawnpoints.size(); i++) { + Vector2i spawn = spawnpoints.get(i); + try { + log.info("Processing " + i + "/" + spawnpoints.size() + ", spawn point " + spawn); + world.setSpawn(spawn); + runMinecraft(); + } catch (IOException | InterruptedException e) { + log.warn("Could not process spawn point " + spawn + + " this part of the world don't be generated", e); + } } } @@ -168,6 +124,9 @@ public class Server { * @author Corrodias, Morlok8k, piegames */ public void runMinecraft() throws IOException, InterruptedException { + log.debug("Setting EULA"); + Files.write(workDir.resolve("eula.txt"), "eula=true".getBytes(), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); log.info("Starting server"); final Process process = builder.start(); @@ -175,7 +134,7 @@ public class Server { new BufferedReader(new InputStreamReader(process.getInputStream())); for (String line = pOut.readLine(); line != null; line = pOut.readLine()) { line = line.trim(); - if (log.isDebugEnabled()) log.debug(line); + if (debugServer) System.out.println(line); if (line.contains("Done")) { PrintStream out = new PrintStream(process.getOutputStream()); diff --git a/src/main/java/morlok8k/MinecraftLandGenerator/World.java b/src/main/java/morlok8k/MinecraftLandGenerator/World.java new file mode 100644 index 0000000..2217f87 --- /dev/null +++ b/src/main/java/morlok8k/MinecraftLandGenerator/World.java @@ -0,0 +1,90 @@ +package morlok8k.MinecraftLandGenerator; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joml.Vector2i; +import org.joml.Vector3i; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.IntTag; +import com.flowpowered.nbt.Tag; +import com.flowpowered.nbt.stream.NBTInputStream; +import com.flowpowered.nbt.stream.NBTOutputStream; + +public class World { + private static Log log = LogFactory.getLog(World.class); + + public final Path world; + + public World(Path world) throws IOException { + this.world = Objects.requireNonNull(world); + Files.copy(world.resolve("level.dat"), world.resolve("level.dat.bak")); + } + + public void resetSpawn() throws IOException { + if (Files.exists(world.resolve("level.dat.bak"))) Files.move(world.resolve("level.dat.bak"), + world.resolve("level.dat"), StandardCopyOption.REPLACE_EXISTING); + } + + public void setSpawn(Vector2i chunkSpawn) throws IOException { + setSpawn(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 + */ + public void setSpawn(final Vector3i xyz) throws IOException { + log.debug("Setting spawn to " + xyz); + // TODO clean this up even more + try (NBTInputStream input = + new NBTInputStream(Files.newInputStream(world.resolve("level.dat")));) { + 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); + + 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", new CompoundMap(newData)); + final Map> newTopLevelMap = new HashMap<>(1); + newTopLevelMap.put("Data", newDataTag); + final CompoundTag newTopLevelTag = new CompoundTag("", new CompoundMap(newTopLevelMap)); + + try (NBTOutputStream output = + new NBTOutputStream(Files.newOutputStream(world.resolve("level.dat")));) { + 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."); + } + } +} diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties new file mode 100644 index 0000000..3554fbb --- /dev/null +++ b/src/main/resources/log4j2.properties @@ -0,0 +1,13 @@ +appenders=xyz + +appender.xyz.type = Console +appender.xyz.name = myOutput +appender.xyz.layout.type = PatternLayout +appender.xyz.layout.pattern = %d{yy-MM-dd HH:mm:ss} %-5p %c{1} [%L] - %m%n + +rootLogger.level = info + +rootLogger.appenderRefs = abc + +rootLogger.appenderRef.abc.ref = myOutput +