More library friendly + forceload generator
This commit is contained in:
parent
d79061f722
commit
1a212bc9aa
@ -0,0 +1,40 @@
|
||||
package morlok8k.MinecraftLandGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
public class BackupHandler {
|
||||
|
||||
private static Log log = LogFactory.getLog(BackupHandler.class);
|
||||
|
||||
public final Path file, backup;
|
||||
protected volatile boolean hasBackup;
|
||||
|
||||
public BackupHandler(Path file) throws FileAlreadyExistsException {
|
||||
this.file = file;
|
||||
this.backup = file.resolveSibling(file.getFileName().toString() + ".bak");
|
||||
if (Files.exists(backup)) throw new FileAlreadyExistsException(backup.toString());
|
||||
}
|
||||
|
||||
public synchronized void backup() throws IOException {
|
||||
if (!hasBackup && !Files.exists(backup)) {
|
||||
log.debug("Creating backup " + file + " --> " + backup);
|
||||
if (Files.exists(file)) Files.copy(file, backup);
|
||||
hasBackup = true;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void restore() throws IOException {
|
||||
log.debug("Restoring backup " + file + " <-- " + backup);
|
||||
if (Files.exists(backup))
|
||||
Files.move(backup, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
else Files.deleteIfExists(file);
|
||||
hasBackup = false;
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package morlok8k.MinecraftLandGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
@ -14,19 +15,22 @@ import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.joml.Vector2i;
|
||||
|
||||
import morlok8k.MinecraftLandGenerator.CommandLineMain.AutoSpawnpoints;
|
||||
import morlok8k.MinecraftLandGenerator.CommandLineMain.ForceloadChunks;
|
||||
import morlok8k.MinecraftLandGenerator.CommandLineMain.ManualSpawnpoints;
|
||||
import morlok8k.MinecraftLandGenerator.World.Dimension;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Help.Visibility;
|
||||
import picocli.CommandLine.HelpCommand;
|
||||
import picocli.CommandLine.ITypeConverter;
|
||||
import picocli.CommandLine.Mixin;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
import picocli.CommandLine.ParentCommand;
|
||||
import picocli.CommandLine.RunAll;
|
||||
|
||||
@Command(name = "MinecraftLandGenerator",
|
||||
subcommands = { HelpCommand.class, ManualSpawnpoints.class, AutoSpawnpoints.class })
|
||||
@Command(name = "MinecraftLandGenerator", subcommands = { HelpCommand.class,
|
||||
ManualSpawnpoints.class, AutoSpawnpoints.class, ForceloadChunks.class })
|
||||
public class CommandLineMain implements Runnable {
|
||||
|
||||
private static Log log = LogFactory.getLog(CommandLineMain.class);
|
||||
@ -50,104 +54,162 @@ 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 {
|
||||
@Override
|
||||
public void run() {
|
||||
if (verbose) {
|
||||
Configurator.setRootLevel(Level.DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class RectangleMixin {
|
||||
@Parameters(index = "0", description = "X-coordinate")
|
||||
int x;
|
||||
@Parameters(index = "1", description = "Z-coordinate")
|
||||
int z;
|
||||
@Parameters(index = "2", description = "Width")
|
||||
int w;
|
||||
@Parameters(index = "3", description = "Height")
|
||||
int h;
|
||||
}
|
||||
|
||||
protected static abstract class CommandLineHelper implements Runnable {
|
||||
@ParentCommand
|
||||
private CommandLineMain parent;
|
||||
protected CommandLineMain parent;
|
||||
|
||||
protected Server server;
|
||||
protected World world;
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
try {
|
||||
server = new Server(parent.serverFile, parent.debugServer, parent.javaOpts);
|
||||
} catch (FileAlreadyExistsException e1) {
|
||||
log.fatal(
|
||||
"Server backup file already exists. Please delete or restore it and then start again",
|
||||
e1);
|
||||
return;
|
||||
} catch (NoSuchFileException e1) {
|
||||
log.fatal(
|
||||
"Server file does not exist. Please download the minecraft server and provide the path to it",
|
||||
e1);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
world = server.initWorld(parent.worldPath);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.fatal("Could not initialize world", e);
|
||||
return;
|
||||
}
|
||||
|
||||
runGenerate();
|
||||
|
||||
log.info("Cleaning up temporary files");
|
||||
try {
|
||||
world.resetChanges();
|
||||
server.resetChanges();
|
||||
} 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.");
|
||||
}
|
||||
|
||||
protected abstract void runGenerate();
|
||||
|
||||
}
|
||||
|
||||
@Command(name = "auto-spawnpoints")
|
||||
public static class AutoSpawnpoints extends CommandLineHelper {
|
||||
|
||||
@Mixin
|
||||
private RectangleMixin bounds;
|
||||
|
||||
@Option(names = "-i", description = "override the iteration spawn offset increment",
|
||||
defaultValue = "25", showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||
hidden = true)
|
||||
private int increment = 25;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
public void runGenerate() {
|
||||
log.info("Generating world");
|
||||
server.runMinecraft(world, generateSpawnpoints(x, z, w, h, increment));
|
||||
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);
|
||||
List<Vector2i> spawnpoints =
|
||||
World.generateSpawnpoints(bounds.x, bounds.z, bounds.w, bounds.h, increment);
|
||||
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);
|
||||
server.runMinecraft();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.warn("Could not process spawn point " + spawn
|
||||
+ " this part of the world won't be generated", e);
|
||||
}
|
||||
}
|
||||
log.info("Done.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Command(name = "manual-spawnpoints")
|
||||
public static class ManualSpawnpoints implements Runnable {
|
||||
|
||||
@ParentCommand
|
||||
private CommandLineMain parent;
|
||||
public static class ManualSpawnpoints extends CommandLineHelper {
|
||||
|
||||
@Parameters(index = "0..*")
|
||||
private Vector2i[] spawnPoints;
|
||||
// @Option(names = { "-s", "--customspawn" }, description = "Customized SpawnPoints")
|
||||
// private String[] customSpawnPoints;
|
||||
|
||||
public ManualSpawnpoints() {
|
||||
}
|
||||
private Vector2i[] spawnpoints;
|
||||
|
||||
@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<>();
|
||||
public void runGenerate() {
|
||||
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.debug("All spawn points: " + Arrays.toString(spawnpoints));
|
||||
for (int i = 0; i < spawnpoints.length; i++) {
|
||||
Vector2i spawn = spawnpoints[i];
|
||||
try {
|
||||
log.info("Processing " + i + "/" + spawnpoints.length + ", spawn point "
|
||||
+ spawn);
|
||||
world.setSpawn(spawn);
|
||||
server.runMinecraft();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.warn("Could not process spawn point " + spawn
|
||||
+ " this part of the world won't be generated", e);
|
||||
}
|
||||
}
|
||||
log.info("Done.");
|
||||
}
|
||||
}
|
||||
|
||||
public CommandLineMain() {
|
||||
@Command(name = "forceload-chunks")
|
||||
public static class ForceloadChunks extends CommandLineHelper {
|
||||
@Mixin
|
||||
private RectangleMixin bounds;
|
||||
|
||||
}
|
||||
@Option(names = "--dimension")
|
||||
private Dimension dimension;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (verbose) {
|
||||
Configurator.setRootLevel(Level.DEBUG);
|
||||
@Option(names = "--max-loaded", defaultValue = "16384")
|
||||
private int maxLoaded;
|
||||
|
||||
@Override
|
||||
protected void runGenerate() {
|
||||
ArrayList<Vector2i> loadedChunks = new ArrayList<>();
|
||||
for (int x = bounds.x; x < bounds.x + bounds.w; x++)
|
||||
for (int z = bounds.z; z < bounds.z + bounds.h; z++)
|
||||
loadedChunks.add(new Vector2i(x, z));
|
||||
log.info("Generating world");
|
||||
if (loadedChunks.size() < 5000)
|
||||
log.debug("Chunks to generate: " + loadedChunks);
|
||||
else log.debug(loadedChunks.size() + " chunks to generate");
|
||||
int stepCount = (int) Math.ceil((double) loadedChunks.size() / maxLoaded);
|
||||
for (int i = 0; i < stepCount; i++) {
|
||||
List<Vector2i> batch = loadedChunks.subList(i * maxLoaded,
|
||||
Math.min((i + 1) * maxLoaded, loadedChunks.size() - 1));
|
||||
log.info("Generating batch " + i + " / " + stepCount + " with " + batch.size()
|
||||
+ " chunks");
|
||||
try {
|
||||
world.setLoadedChunks(batch, dimension);
|
||||
server.runMinecraft();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("Could not force-load chunks", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,36 +230,4 @@ public class CommandLineMain implements Runnable {
|
||||
});
|
||||
cli.parseWithHandler(new RunAll(), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param increment
|
||||
* Maximum number of chunks between two spawn points, horizontally or vertically
|
||||
* @param margin
|
||||
* The radius to each side that will be generated by the server (Not the diameter!)
|
||||
*/
|
||||
public static List<Vector2i> generateSpawnpoints(int startX, int startZ, int width, int height,
|
||||
int increment) {
|
||||
int margin = increment / 2;
|
||||
if (width < margin || height < margin)
|
||||
throw new IllegalArgumentException("Width and height must both be at least " + increment
|
||||
+ ", but are " + width + " and " + height);
|
||||
List<Integer> xPoints =
|
||||
generateLinearSpawnpoints(startX + margin, width - increment, increment);
|
||||
log.debug("X grid: " + xPoints);
|
||||
List<Integer> zPoints =
|
||||
generateLinearSpawnpoints(startZ + margin, height - increment, increment);
|
||||
log.debug("Z grid: " + zPoints);
|
||||
List<Vector2i> 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<Integer> generateLinearSpawnpoints(int start, int length, int maxStep) {
|
||||
int stepCount = (int) Math.ceil((double) length / maxStep);
|
||||
double realStep = length / stepCount;
|
||||
return IntStream.rangeClosed(0, stepCount).mapToObj(i -> start + (int) (realStep * i))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
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;
|
||||
@ -24,7 +24,6 @@ import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.joml.Vector2i;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -37,18 +36,21 @@ public class Server {
|
||||
protected final ProcessBuilder builder;
|
||||
protected final Path workDir;
|
||||
protected final boolean debugServer;
|
||||
protected BackupHandler serverProperties;
|
||||
|
||||
public Server(boolean debugServer, String[] javaOpts, Path serverFile) {
|
||||
public Server(Path serverFile, boolean debugServer, String[] javaOpts)
|
||||
throws FileAlreadyExistsException, NoSuchFileException {
|
||||
this.debugServer = debugServer;
|
||||
if (!Files.exists(serverFile)) throw new IllegalArgumentException(serverFile.toString()
|
||||
+ " must be an existing file pointing to the minecraft server");
|
||||
if (!Files.exists(serverFile)) throw new NoSuchFileException(serverFile.toString());
|
||||
workDir = serverFile.getParent();
|
||||
serverProperties = new BackupHandler(workDir.resolve("server.properties"));
|
||||
|
||||
List<String> 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());
|
||||
}
|
||||
|
||||
@ -74,8 +76,8 @@ public class Server {
|
||||
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"));
|
||||
serverProperties.backup();
|
||||
|
||||
Properties props = new Properties();
|
||||
props.load(Files.newInputStream(propsFile));
|
||||
props.put("level-name", worldPath.toString());
|
||||
@ -95,26 +97,8 @@ public class Server {
|
||||
return p;
|
||||
}
|
||||
|
||||
public void restoreWorld() throws IOException {
|
||||
Path propsFile = workDir.resolve("server.properties");
|
||||
if (Files.exists(propsFile.resolveSibling("server.properties.bak")))
|
||||
Files.move(propsFile.resolveSibling("server.properties.bak"), propsFile,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
public void resetChanges() throws IOException {
|
||||
serverProperties.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,12 +119,18 @@ public class Server {
|
||||
final BufferedReader pOut =
|
||||
new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
for (String line = pOut.readLine(); line != null; line = pOut.readLine()) {
|
||||
if (Thread.interrupted()) {
|
||||
log.warn("Got interrupted by other process, stopping");
|
||||
process.destroy();
|
||||
break;
|
||||
}
|
||||
line = line.trim();
|
||||
if (debugServer) System.out.println(line);
|
||||
|
||||
if (line.contains("Done")) {
|
||||
PrintStream out = new PrintStream(process.getOutputStream());
|
||||
|
||||
out.println("forceload query");
|
||||
log.info("Stopping server...");
|
||||
out.println("save-all");
|
||||
out.flush();
|
||||
|
@ -1,13 +1,18 @@
|
||||
package morlok8k.MinecraftLandGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@ -17,29 +22,64 @@ import org.joml.Vector3i;
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import com.flowpowered.nbt.CompoundTag;
|
||||
import com.flowpowered.nbt.IntTag;
|
||||
import com.flowpowered.nbt.LongArrayTag;
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import com.flowpowered.nbt.stream.NBTInputStream;
|
||||
import com.flowpowered.nbt.stream.NBTOutputStream;
|
||||
|
||||
public class World {
|
||||
|
||||
public static enum Dimension {
|
||||
OVERWORLD("."), NETHER("DIM-1"), END("DIM1");
|
||||
public final Path path;
|
||||
|
||||
Dimension(String path) {
|
||||
this.path = Paths.get(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static Log log = LogFactory.getLog(World.class);
|
||||
|
||||
public final Path world;
|
||||
protected BackupHandler level, chunksOverworld, chunksNether, chunksEnd;
|
||||
protected BackupHandler[] chunks;
|
||||
|
||||
public World(Path world) throws IOException {
|
||||
public World(Path world) throws FileAlreadyExistsException {
|
||||
this.world = Objects.requireNonNull(world);
|
||||
Files.copy(world.resolve("level.dat"), world.resolve("level.dat.bak"));
|
||||
level = new BackupHandler(world.resolve("level.dat"));
|
||||
chunksOverworld = new BackupHandler(
|
||||
world.resolve(Dimension.OVERWORLD.path).resolve("data/chunks.dat"));
|
||||
chunksNether =
|
||||
new BackupHandler(world.resolve(Dimension.NETHER.path).resolve("data/chunks.dat"));
|
||||
chunksEnd = new BackupHandler(world.resolve(Dimension.END.path).resolve("data/chunks.dat"));
|
||||
chunks = new BackupHandler[] { chunksOverworld, chunksNether, chunksEnd };
|
||||
}
|
||||
|
||||
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 resetChanges() throws IOException {
|
||||
level.restore();
|
||||
chunksOverworld.restore();
|
||||
chunksNether.restore();
|
||||
chunksEnd.restore();
|
||||
}
|
||||
|
||||
public void setSpawn(Vector2i chunkSpawn) throws IOException {
|
||||
setSpawn(new Vector3i(chunkSpawn.x << 4 | 7, 64, chunkSpawn.y << 4 | 8));
|
||||
}
|
||||
|
||||
public void setLoadedChunks(List<Vector2i> chunks, Dimension dimension) throws IOException {
|
||||
BackupHandler handler = this.chunks[dimension.ordinal()];
|
||||
handler.backup();
|
||||
try (NBTOutputStream out = new NBTOutputStream(Files.newOutputStream(handler.file))) {
|
||||
CompoundMap dataMap = new CompoundMap();
|
||||
dataMap.put(new LongArrayTag("Forced",
|
||||
chunks.stream().mapToLong(v -> ((long) v.y << 32) | v.x).toArray()));
|
||||
CompoundMap rootMap = new CompoundMap();
|
||||
rootMap.put(new CompoundTag("data", dataMap));
|
||||
out.writeTag(new CompoundTag("", rootMap));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
@ -54,6 +94,7 @@ public class World {
|
||||
* @author Corrodias
|
||||
*/
|
||||
public void setSpawn(final Vector3i xyz) throws IOException {
|
||||
level.backup();
|
||||
log.debug("Setting spawn to " + xyz);
|
||||
// TODO clean this up even more
|
||||
try (NBTInputStream input =
|
||||
@ -87,4 +128,36 @@ public class World {
|
||||
throw new IOException("Invalid level format.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param increment
|
||||
* Maximum number of chunks between two spawn points, horizontally or vertically
|
||||
* @param margin
|
||||
* The radius to each side that will be generated by the server (Not the diameter!)
|
||||
*/
|
||||
public static List<Vector2i> generateSpawnpoints(int startX, int startZ, int width, int height,
|
||||
int increment) {
|
||||
int margin = increment / 2;
|
||||
if (width < margin || height < margin)
|
||||
throw new IllegalArgumentException("Width and height must both be at least " + increment
|
||||
+ ", but are " + width + " and " + height);
|
||||
List<Integer> xPoints =
|
||||
generateLinearSpawnpoints(startX + margin, width - increment, increment);
|
||||
log.debug("X grid: " + xPoints);
|
||||
List<Integer> zPoints =
|
||||
generateLinearSpawnpoints(startZ + margin, height - increment, increment);
|
||||
log.debug("Z grid: " + zPoints);
|
||||
List<Vector2i> 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<Integer> generateLinearSpawnpoints(int start, int length, int maxStep) {
|
||||
int stepCount = (int) Math.ceil((double) length / maxStep);
|
||||
double realStep = length / stepCount;
|
||||
return IntStream.rangeClosed(0, stepCount).mapToObj(i -> start + (int) (realStep * i))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user