Initial 1.2.0 Commit (Created by Corrodias)

This commit is contained in:
Morlok8k 2011-03-01 04:20:12 -08:00
parent 4ffa4ecbc4
commit 72bd37811c

View File

@ -0,0 +1,535 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package corrodias.minecraft.landgenerator;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jnbt.CompoundTag;
import org.jnbt.IntTag;
import org.jnbt.NBTInputStream;
import org.jnbt.NBTOutputStream;
import org.jnbt.Tag;
/**
*
* @author Corrodias
*/
public class Main {
private static final String separator = System.getProperty("file.separator");
//private static final String classpath = System.getProperty("java.class.path");
//private static final String javaPath = System.getProperty("java.home") + separator + "bin" + separator + "java";
private static final String VERSION = "1.2.0";
private int increment = 300;
private ProcessBuilder minecraft = null;
private String javaLine = null;
private String serverPath = null;
private String worldPath = null;
private int xRange = 0;
private int yRange = 0;
private Integer xOffset = null;
private Integer yOffset = null;
private boolean verbose = false;
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
(new Main()).run(args);
}
private void run(String[] args) {
System.out.println("Minecraft Land Generator version " + VERSION);
System.out.println("Uses a Minecraft server to generate square land of a specified size.");
System.out.println("");
// =====================================================================
// INSTRUCTIONS
// =====================================================================
if (args.length == 0 || args[0].equals("-version") || args[0].equals("-help") || args[0].equals("/?")) {
System.out.println("Usage: java -jar MinecraftLandGenerator.jar x y [serverpath] [switches]");
System.out.println("");
System.out.println("Arguments:");
System.out.println(" x : X range to generate");
System.out.println(" y : Y range to generate");
System.out.println(" serverpath : the path to the directory in which the server runs (takes precedence over the config file setting)");
System.out.println("");
System.out.println("Switches:");
System.out.println(" -verbose : causes the application to output the server's messages to the console");
System.out.println(" -v : same as -verbose");
System.out.println(" -i# : override the iteration spawn offset increment (default 300) (example: -i100)");
System.out.println(" -x# : set the X offset to generate land around (example: -x0)");
System.out.println(" -y# : set the X offset to generate land around (example: -y0)");
System.out.println("");
System.out.println("Other options:");
System.out.println(" java -jar MinecraftLandGenerator.jar -printspawn");
System.out.println(" java -jar MinecraftLandGenerator.jar -ps");
System.out.println(" Outputs the current world's spawn point coordinates.");
System.out.println("");
System.out.println(" java -jar MinecraftLandGenerator.jar -conf");
System.out.println(" Generates a MinecraftLandGenerator.conf file.");
System.out.println("");
System.out.println(" java -jar MinecraftLandGenerator.jar -version");
System.out.println(" java -jar MinecraftLandGenerator.jar -help");
System.out.println(" java -jar MinecraftLandGenerator.jar /?");
System.out.println(" Prints this message.");
System.out.println("");
System.out.println("When launched with the -conf switch, this application creates a MinecraftLandGenerator.conf file that contains configuration options.");
System.out.println("If this file does not exist or does not contain all required properties, the application will not run.");
System.out.println("");
System.out.println("MinecraftLandGenerator.conf properties:");
System.out.println(" java : the command line to use to launch the server");
System.out.println(" serverpath : the path to the directory in which the server runs (can be overridden by the serverpath argument)");
return;
}
// =====================================================================
// STARTUP AND CONFIG
// =====================================================================
// the arguments are apparently okay so far. parse the conf file.
if (args[0].equalsIgnoreCase("-conf")) {
try {
File config = new File("MinecraftLandGenerator.conf");
BufferedWriter out = new BufferedWriter(new FileWriter(config));
out.write("java=java -Xms1024m -Xmx1024m -jar minecraft_server.jar nogui");
out.newLine();
out.write("serverpath=.");
out.newLine();
out.close();
System.out.println("MinecraftLandGenerator.conf file created.");
return;
} catch (IOException ex) {
System.err.println("Could not create MinecraftLandGenerator.conf.");
return;
}
} else if (args[0].equalsIgnoreCase("-ps") || args[0].equalsIgnoreCase("-printspawn")) {
// okay, sorry, this is an ugly hack, but it's just a last-minute feature.
printSpawn();
return;
} else if (args.length == 1) {
System.out.println("For help, use java -jar MinecraftLandGenerator.jar -help");
return;
}
try {
File config = new File("MinecraftLandGenerator.conf");
BufferedReader in = new BufferedReader(new FileReader(config));
String line;
while ((line = in.readLine()) != null) {
int pos = line.indexOf('=');
if (pos != -1) {
if (line.substring(0, pos).toLowerCase().equals("serverpath")) {
serverPath = line.substring(pos + 1);
} else if (line.substring(0, pos).toLowerCase().equals("java")) {
javaLine = line.substring(pos + 1);
}
}
}
in.close();
if (serverPath == null || javaLine == null) {
System.err.println("MinecraftLandGenerator.conf does not contain all requird properties. Please recreate it by running this application with no arguments.");
return;
}
} catch (FileNotFoundException ex) {
System.out.println("Could not find MinecraftLandGenerator.conf. It is recommended that you run the application with the -conf option to create it.");
return;
} catch (IOException ex) {
System.err.println("Could not read MinecraftLandGenerator.conf");
return;
}
// ARGUMENTS
try {
xRange = Integer.parseInt(args[0]);
yRange = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
System.err.println("Invalid X or Y argument.");
return;
}
// This is embarrassing. Don't look.
try {
for (int i = 0; i < args.length - 2; i++) {
String nextSwitch = args[i + 2].toLowerCase();
if (nextSwitch.equals("-verbose") || nextSwitch.equals("-v")) {
verbose = true;
} else if (nextSwitch.startsWith("-i")) {
increment = Integer.parseInt(args[i + 2].substring(2));
} else if (nextSwitch.startsWith("-x")) {
xOffset = Integer.valueOf(args[i + 2].substring(2));
} else if (nextSwitch.startsWith("-y")) {
yOffset = Integer.valueOf(args[i + 2].substring(2));
} else {
serverPath = args[i + 2];
}
}
} catch (NumberFormatException ex) {
System.err.println("Invalid -i switch value.");
return;
}
{
// verify that we ended up with a good server path, either from the file or from an argument.
File file = new File(serverPath);
if (!file.exists() || !file.isDirectory()) {
System.err.println("The server directory is invalid: " + serverPath);
return;
}
}
try {
// read the name of the current world from the server.properties file
BufferedReader props = new BufferedReader(new FileReader(new File(serverPath + separator + "server.properties")));
String line;
while ((line = props.readLine()) != null) {
int pos = line.indexOf('=');
if (pos != -1) {
if (line.substring(0, pos).toLowerCase().equals("level-name")) {
worldPath = serverPath + separator + line.substring(pos + 1);
}
}
}
} catch (FileNotFoundException ex) {
System.err.println("Could not open " + serverPath + separator + "server.properties");
return;
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
return;
}
{
File backupLevel = new File(worldPath + separator + "level_backup.dat");
if (backupLevel.exists()) {
System.err.println("There is a level_backup.dat file left over from a previous attempt that failed. You should go determine whether to keep the current level.dat"
+ " or restore the backup.");
return;
}
}
// =====================================================================
// PROCESSING
// =====================================================================
System.out.println("Processing world \"" + worldPath + "\", in " + increment + " block increments, with: " + javaLine);
System.out.println("");
// prepare our two ProcessBuilders
//minecraft = new ProcessBuilder(javaLine, "-Xms1024m", "-Xmx1024m", "-jar", jarFile, "nogui");
minecraft = new ProcessBuilder(javaLine.split("\\s")); // is this always going to work? i don't know.
minecraft.directory(new File(serverPath));
minecraft.redirectErrorStream(true);
try {
System.out.println("Launching server once to make sure there is a world.");
runMinecraft(minecraft, verbose);
System.out.println("");
File serverLevel = new File(worldPath + separator + "level.dat");
File backupLevel = new File(worldPath + separator + "level_backup.dat");
System.out.println("Backing up level.dat to level_backup.dat.");
copyFile(serverLevel, backupLevel);
System.out.println("");
Integer[] spawn = getSpawn(serverLevel);
System.out.println("Spawn point detected: [" + spawn[0] + ", " + spawn[2] + "]");
{
boolean overridden = false;
if (xOffset == null) {
xOffset = spawn[0];
} else {
overridden = true;
}
if (yOffset == null) {
yOffset = spawn[2];
} else {
overridden = true;
}
if (overridden) {
System.out.println("Centering land generation on [" + xOffset + ", " + yOffset + "] due to switches.");
}
}
System.out.println("");
int totalIterations = (xRange / increment + 1) * (yRange / increment + 1);
int currentIteration = 0;
long differenceTime = System.currentTimeMillis();
Long[] timeTracking = new Long[]{differenceTime, differenceTime, differenceTime, differenceTime};
for (int currentX = 0 - xRange / 2; currentX <= xRange / 2; currentX += increment) {
for (int currentY = 0 - yRange / 2; currentY <= yRange / 2; currentY += increment) {
currentIteration++;
System.out.println("Setting spawn to [" + Integer.toString(currentX + xOffset) + ", " + Integer.toString(currentY + yOffset) + "] (" + currentIteration + "/" + totalIterations + ")");
// Time Remaining estimate
timeTracking[0] = timeTracking[1];
timeTracking[1] = timeTracking[2];
timeTracking[2] = timeTracking[3];
timeTracking[3] = System.currentTimeMillis();
if (currentIteration >= 4) {
differenceTime = (timeTracking[3] - timeTracking[0]) / 3; // well, this is what it boils down to
differenceTime *= 1 + (totalIterations - currentIteration);
System.out.println(String.format("Estimated time remaining: %dh%dm%ds",
differenceTime / (1000 * 60 * 60), (differenceTime % (1000 * 60 * 60)) / (1000 * 60), ((differenceTime % (1000 * 60 * 60)) % (1000 * 60)) / 1000));
}
// Set the spawn point
setSpawn(serverLevel, currentX + xOffset, 128, currentY + yOffset);
// Launch the server
runMinecraft(minecraft, verbose);
System.out.println("");
}
}
System.out.println("Finished generating chunks.");
copyFile(backupLevel, serverLevel);
backupLevel.delete();
System.out.println("Restored original level.dat.");
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
protected static Integer[] getSpawn(File level) throws IOException {
try {
NBTInputStream input = new NBTInputStream(new FileInputStream(level));
CompoundTag originalTopLevelTag = (CompoundTag) input.readTag();
input.close();
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.
Map<String, Tag> newData = new LinkedHashMap<String, Tag>(originalData);
// .get() a couple of values, just to make sure we're dealing with a valid level file, here. Good for debugging, too.
IntTag spawnX = (IntTag) newData.get("SpawnX");
IntTag spawnY = (IntTag) newData.get("SpawnY");
IntTag spawnZ = (IntTag) newData.get("SpawnZ");
Integer[] ret = new Integer[]{spawnX.getValue(), spawnY.getValue(), spawnZ.getValue()};
return ret;
} catch (ClassCastException ex) {
throw new IOException("Invalid level format.");
} catch (NullPointerException ex) {
throw new IOException("Invalid level format.");
}
}
/**
* Changes the spawn point in the given Alpha level to the given coordinates.
* Note that, in Minecraft levels, the Y coordinate is height, while Z is
* what may normally be thought of as Y.
* @param level the level file to change the spawn point in
* @param x the new X value
* @param z the new Y value
* @param y the new Z value
* @throws IOException if there are any problems reading/writing the file
*/
protected static void setSpawn(File level, Integer x, Integer y, Integer z) throws IOException {
try {
NBTInputStream input = new NBTInputStream(new FileInputStream(level));
CompoundTag originalTopLevelTag = (CompoundTag) input.readTag();
input.close();
// <editor-fold defaultstate="collapsed" desc="structure">
// 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.
// </editor-fold>
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.
Map<String, Tag> newData = new LinkedHashMap<String, Tag>(originalData);
// .get() a couple of values, just to make sure we're dealing with a valid level file, here. Good for debugging, too.
IntTag spawnX = (IntTag) newData.get("SpawnX");
IntTag spawnY = (IntTag) newData.get("SpawnY");
IntTag spawnZ = (IntTag) newData.get("SpawnZ");
newData.put("SpawnX", new IntTag("SpawnX", x));
newData.put("SpawnY", new IntTag("SpawnY", y));
newData.put("SpawnZ", new IntTag("SpawnZ", z));
// Again, we can't modify the data map in the old Tag, so we have to make a new one.
CompoundTag newDataTag = new CompoundTag("Data", newData);
Map<String, Tag> newTopLevelMap = new HashMap<String, Tag>(1);
newTopLevelMap.put("Data", newDataTag);
CompoundTag newTopLevelTag = new CompoundTag("", newTopLevelMap);
NBTOutputStream output = new NBTOutputStream(new FileOutputStream(level));
output.writeTag(newTopLevelTag);
output.close();
} catch (ClassCastException ex) {
throw new IOException("Invalid level format.");
} catch (NullPointerException ex) {
throw new IOException("Invalid level format.");
}
}
/**
* Starts the process in the given ProcessBuilder, monitors its output for a
* "[INFO] Done!" message, and sends it a "stop\r\n" message. One message is printed
* to the console before launching and one is printed to the console when the
* Done! message is detected. If "verbose" is true, the process's output will
* also be printed to the console.
* @param minecraft
* @param verbose
* @throws IOException
*/
protected static void runMinecraft(ProcessBuilder minecraft, boolean verbose) throws IOException {
System.out.println("Starting server.");
Process process = minecraft.start();
// monitor output and print to console where required.
// STOP the server when it's done.
BufferedReader pOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = pOut.readLine()) != null) {
if (verbose) {
System.out.println(line);
}
if (line.contains("[INFO] Done!")) {
System.out.println("Stopping server.");
byte[] stop = {'s', 't', 'o', 'p', '\r', '\n'};
OutputStream outputStream = process.getOutputStream();
outputStream.write(stop);
outputStream.flush();
}
}
// readLine() returns null when the process exits
}
// I'd love to use nio, but it requires Java 7.
// I could use Apache Commons, but i don't want to include a library for one little thing.
// Copies src file to dst file.
// If the dst file does not exist, it is created
public static void copyFile(File src, File dst) throws IOException {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) >= 0) {
if (len > 0) {
out.write(buf, 0, len);
}
}
in.close();
out.flush();
out.close();
}
private boolean printSpawn() {
// ugh, sorry, this is an ugly hack, but it's a last-minute feature.
// this is a lot of duplicated code.
try {
File config = new File("MinecraftLandGenerator.conf");
BufferedReader in = new BufferedReader(new FileReader(config));
String line;
while ((line = in.readLine()) != null) {
int pos = line.indexOf('=');
if (pos != -1) {
if (line.substring(0, pos).toLowerCase().equals("serverpath")) {
serverPath = line.substring(pos + 1);
} else if (line.substring(0, pos).toLowerCase().equals("java")) {
javaLine = line.substring(pos + 1);
}
}
}
in.close();
if (serverPath == null || javaLine == null) {
System.err.println("MinecraftLandGenerator.conf does not contain all requird properties. Please recreate it by running this application with no arguments.");
return false;
}
} catch (FileNotFoundException ex) {
System.out.println("Could not find MinecraftLandGenerator.conf. It is recommended that you run the application with the -conf option to create it.");
return false;
} catch (IOException ex) {
System.err.println("Could not read MinecraftLandGenerator.conf");
return false;
}
{
// verify that we ended up with a good server path, either from the file or from an argument.
File file = new File(serverPath);
if (!file.exists() || !file.isDirectory()) {
System.err.println("The server directory is invalid: " + serverPath);
return false;
}
}
try {
// read the name of the current world from the server.properties file
BufferedReader props = new BufferedReader(new FileReader(new File(serverPath + separator + "server.properties")));
String line;
while ((line = props.readLine()) != null) {
int pos = line.indexOf('=');
if (pos != -1) {
if (line.substring(0, pos).toLowerCase().equals("level-name")) {
worldPath = serverPath + separator + line.substring(pos + 1);
}
}
}
} catch (FileNotFoundException ex) {
System.err.println("Could not open " + serverPath + separator + "server.properties");
return false;
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
File level = new File(worldPath + separator + "level.dat");
if (!level.exists() || !level.isFile()) {
System.err.println("The currently-configured world does not exist. Please launch the server once, first.");
return false;
}
try {
Integer[] spawn = getSpawn(level);
System.out.println("The current spawn point is: [" + spawn[0] + ", " + spawn[2] + "]");
return true;
} catch (IOException ex) {
System.err.println("Error while reading " + level.getPath());
return false;
}
}
}