manual-spawnpoints now works

This commit is contained in:
Piegames 2018-11-14 17:21:03 +01:00
parent 278c168036
commit 0be54d1fcc
7 changed files with 299 additions and 135 deletions

View File

@ -6,6 +6,7 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>

View File

@ -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

View File

@ -40,7 +40,7 @@
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<version>1.2</version>
</dependency>
<dependency>
@ -48,6 +48,11 @@
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.joml</groupId>

View File

@ -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<Vector2i> 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);
}
/**

View File

@ -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<String> 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<String, Tag<?>> 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.<br>
* Note that, in Minecraft levels, the Y coordinate is height.<br>
* (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<String, Tag<?>> 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<String, Tag<?>> 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<String, Tag<?>> 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<Vector2i> 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());

View File

@ -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.<br>
* Note that, in Minecraft levels, the Y coordinate is height.<br>
* (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<String, Tag<?>> 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<String, Tag<?>> 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<String, Tag<?>> 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.");
}
}
}

View File

@ -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