[Refactor] Use proper folder structure
parent
72406cd513
commit
9a745dfacc
41
.classpath
41
.classpath
|
@ -1,21 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-12">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src/main/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=12
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=12
|
||||
org.eclipse.jdt.core.compiler.compliance=11
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
|
@ -12,4 +12,4 @@ org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
|||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=12
|
||||
org.eclipse.jdt.core.compiler.source=11
|
||||
|
|
13
Readme.MD
13
Readme.MD
|
@ -1,6 +1,7 @@
|
|||
### LiteCraft
|
||||
![GPLv3 or later](https://www.gnu.org/graphics/gplv3-or-later.png "GPLv3-plus Logo")
|
||||
|
||||
# LiteCraft
|
||||
A lightweight, singleplayer only Minecraft clone <br />
|
||||
Aims to be on-par with Minecraft 1.2.5 (Just before they started using integrated servers), in terms of quality, with thousands of lines less code, and quite a few less bugs. <br />
|
||||
|
||||
#### Goals:
|
||||
- Learn the (old) Minecraft codebase inside and out
|
||||
|
@ -8,7 +9,7 @@ Aims to be on-par with Minecraft 1.2.5 (Just before they started using integrate
|
|||
- Create a clone of Minecraft with as little original code and as many open-source
|
||||
libraries as possible
|
||||
- Use a mainly data-driven structure, allowing for easier additions (and removals) of features, saving custom classes for only really unique features. (So all farmable mobs would share the same class, for instance.)
|
||||
|
||||
|
||||
#### Planned Feature Changes from Vanilla Minecraft
|
||||
- No more multiplayer. (It adds a lot of lines of code, as well as requiring an entirely
|
||||
different app for a server.)
|
||||
|
@ -17,4 +18,8 @@ Aims to be on-par with Minecraft 1.2.5 (Just before they started using integrate
|
|||
- Pretty much any creature, monster, item, block, dimension that's unique to Minecraft will not
|
||||
be present either.
|
||||
- These features will be replaced by our own original ideas, or some more generic ones, instead.
|
||||
- Only built-in texture packs. This allows us to write fewer lines of code to process custom resources.
|
||||
- Only built-in texture packs. This allows us to write fewer lines of code to process custom resources
|
||||
|
||||
# LICENSE
|
||||
This project is licensed under the GNU General Public License Version 3.0<br />
|
||||
For details see [LICENSE](https://github.com/halotroop/LiteCraft/blob/master/LICENSE)
|
||||
|
|
173
pom.xml
173
pom.xml
|
@ -1,53 +1,126 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>LiteCraft</groupId>
|
||||
<artifactId>LiteCraft</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>LiteCraft</name>
|
||||
<description>Lightweight Minecraft Clone Engine</description>
|
||||
<build>
|
||||
<sourceDirectory>src</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<release>11</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<lwjgl.version>3.2.3</lwjgl.version>
|
||||
<joml.version>1.9.17</joml.version>
|
||||
<gson.version>2.8.5</gson.version>
|
||||
<lwjgl.natives>natives-windows</lwjgl.natives>
|
||||
</properties>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>LiteCraft</groupId>
|
||||
<artifactId>LiteCraft</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>LiteCraft</name>
|
||||
<description>Lightweight Minecraft Clone Engine</description>
|
||||
<build>
|
||||
<sourceDirectory>src</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<release>11</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<gson.version>2.8.5</gson.version>
|
||||
<lwjgl.version>3.2.4-SNAPSHOT</lwjgl.version>
|
||||
<joml.version>1.9.20</joml.version>
|
||||
<lwjgl.natives>natives-linux</lwjgl.natives>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-bom</artifactId>
|
||||
<version>${lwjgl.version}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>sonatype-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openal</artifactId></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openal</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
||||
<dependency><groupId>org.joml</groupId><artifactId>joml</artifactId><version>${joml.version}</version></dependency>
|
||||
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency>
|
||||
<dependency><groupId>net.sourceforge.argo</groupId><artifactId>argo</artifactId><version>5.5</version></dependency>
|
||||
<dependency><groupId>org.spongepowered</groupId><artifactId>noise</artifactId><version>2.0.0-SNAPSHOT</version></dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-bom</artifactId>
|
||||
<version>${lwjgl.version}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.Spoutcraft</groupId>
|
||||
<artifactId>soundsystem</artifactId>
|
||||
<version>master-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aeonbits.owner</groupId>
|
||||
<artifactId>owner</artifactId>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-glfw</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-openal</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-opengl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl</artifactId>
|
||||
<classifier>${lwjgl.natives}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-glfw</artifactId>
|
||||
<classifier>${lwjgl.natives}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-openal</artifactId>
|
||||
<classifier>${lwjgl.natives}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl-opengl</artifactId>
|
||||
<classifier>${lwjgl.natives}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.joml</groupId>
|
||||
<artifactId>joml</artifactId>
|
||||
<version>${joml.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>${gson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.argo</groupId>
|
||||
<artifactId>argo</artifactId>
|
||||
<version>5.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
package com.github.halotroop.litecraft.logic;
|
||||
|
||||
public interface TickListener
|
||||
{
|
||||
void onTick(float deltaTime);
|
||||
}
|
|
@ -1,81 +1,99 @@
|
|||
package com.github.halotroop.litecraft;
|
||||
|
||||
import org.apache.commons.cli.*;
|
||||
import org.lwjgl.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
import org.lwjgl.opengl.*;
|
||||
|
||||
import com.github.halotroop.litecraft.logic.TickListener;
|
||||
import com.github.halotroop.litecraft.logic.Timer;
|
||||
import com.github.halotroop.litecraft.logic.Timer.TickListener;
|
||||
import com.github.halotroop.litecraft.render.Renderer;
|
||||
|
||||
public class LiteCraftMain
|
||||
public class LiteCraftMain implements Runnable
|
||||
{
|
||||
public static int maxFPS = 60;
|
||||
public static int width = 640, height = 480; // Don't change these values. They just initialize it in case we forget to set them later.
|
||||
public static boolean spamLog, debug;
|
||||
public String splashText = "";
|
||||
|
||||
protected Timer timer;
|
||||
private int fps, ups, tps;
|
||||
public static int maxFPS = 100;
|
||||
private long frameTimer;
|
||||
private Renderer renderer;
|
||||
|
||||
public static int width = 400, height = 300; // Don't change these values. They just initialize it in case we forget to set them later.
|
||||
|
||||
private Window window;
|
||||
private static boolean spamLog;
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Options options = new Options();
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
CommandLine cmd;
|
||||
options.addOption(new Option("w", "width", true, "Screen width"));
|
||||
options.addOption(new Option("h", "height", true, "Screen height"));
|
||||
options.addOption(new Option("debug", "debug", true, "Use debug features"));
|
||||
options.addOption(new Option("spam_log","spam_log", true, "Log sanity checks"));
|
||||
options.addOption(new Option("limit_fps", "limit_fps", true, "Use the FPS limiter"));
|
||||
options.addOption(new Option("max_fps", "max_fps", true, "The maximum amount of FPS"));
|
||||
|
||||
try
|
||||
{
|
||||
cmd = parser.parse(options, args);
|
||||
width = Integer.parseInt(cmd.getOptionValue("width", "640"));
|
||||
height = Integer.parseInt(cmd.getOptionValue("height", "480"));
|
||||
debug = Boolean.parseBoolean(cmd.getOptionValue("debug", "false"));
|
||||
spamLog = Boolean.parseBoolean(cmd.getOptionValue("spam_log", "false"));
|
||||
maxFPS = Integer.parseInt(cmd.getOptionValue("max_fps", "60"));
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
System.out.println(e.getLocalizedMessage());
|
||||
formatter.printHelp("utility-name", options);
|
||||
}
|
||||
|
||||
|
||||
new LiteCraftMain().run();
|
||||
}
|
||||
|
||||
protected TickListener tickListener = new TickListener()
|
||||
{
|
||||
@Override
|
||||
public void onTick(float deltaTime)
|
||||
{
|
||||
tps++;
|
||||
}
|
||||
{ tps++; }
|
||||
};
|
||||
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
width = 1600;
|
||||
height = 900;
|
||||
spamLog = false;
|
||||
new LiteCraftMain().run();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
System.out.println("Running program.");
|
||||
System.out.println("LWJGL version: " + Version.getVersion());
|
||||
|
||||
init();
|
||||
|
||||
frameTimer = System.currentTimeMillis();
|
||||
|
||||
// Run the rendering loop until the player has attempted to close the window
|
||||
while (!GLFW.glfwWindowShouldClose(window.getWindowLong()))
|
||||
{
|
||||
loop();
|
||||
}
|
||||
|
||||
destroy();
|
||||
// Shuts down the game and destroys all the things that are using RAM (so the user doesn't have to restart their computer afterward...)
|
||||
private void destroy()
|
||||
{
|
||||
if (debug) System.out.println("Closing game...");
|
||||
renderer.cleanUp();
|
||||
window.destroy();
|
||||
// Terminate GLFW and free the error callback
|
||||
GLFW.glfwTerminate();
|
||||
GLFW.glfwSetErrorCallback(null).free();
|
||||
if (debug) System.out.println("Game closed successfully.");
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
System.out.println("Initializing game...");
|
||||
if (debug) System.out.println("Initializing game...");
|
||||
// Setup an error callback. The default implementation will print the error message in System.err.
|
||||
GLFWErrorCallback.createPrint(System.err).set();
|
||||
// Initialize GLFW. Most GLFW functions will not work before doing this.
|
||||
if (!GLFW.glfwInit()) throw new IllegalStateException("Unable to initialize GLFW");
|
||||
// Configure GLFW
|
||||
window = new Window(width, height);
|
||||
|
||||
timer = new Timer(20);
|
||||
timer.addTickListener(tickListener);
|
||||
|
||||
GL.createCapabilities(); // This line is critical for LWJGL's interoperation with GLFW.
|
||||
renderer = new Renderer();
|
||||
|
||||
window.setWindowTitle("LiteCraft - " + "INSERT SPLASH TEXT HERE!");
|
||||
GL.createCapabilities(); // This line is critical for LWJGL's interoperation with GLFW.
|
||||
renderer = new Renderer();
|
||||
if (splashText == "")
|
||||
{
|
||||
window.setWindowTitle("LiteCraft - " + "INSERT SPLASH TEXT HERE!");
|
||||
}
|
||||
input();
|
||||
|
||||
System.out.println("Initialization complete.");
|
||||
if (debug) System.out.println("Initialization complete.");
|
||||
}
|
||||
|
||||
// Sets up the key inputs for the game (currently just esc for closing the game)
|
||||
|
@ -88,21 +106,18 @@ public class LiteCraftMain
|
|||
GLFW.glfwSetWindowShouldClose(window, true); // We will detect this in the game loop
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Things that the game should do over and over and over again until it is closed
|
||||
private void loop()
|
||||
{
|
||||
ups++;
|
||||
// Poll for window events. The key callback above will only be invoked during this call.
|
||||
GLFW.glfwPollEvents();
|
||||
|
||||
timer.tick();
|
||||
|
||||
if (fps < maxFPS) render();
|
||||
|
||||
if (System.currentTimeMillis() > frameTimer + 1000) // wait for one second
|
||||
{
|
||||
window.setWindowTitle("LiteCraft | FPS: " + fps + " | TPS: " + tps + " | UPS: " + ups);
|
||||
if (debug) window.setWindowTitle("LiteCraft | FPS: " + fps + " | TPS: " + tps + " | UPS: " + ups);
|
||||
fps = 0;
|
||||
ups = 0;
|
||||
tps = 0;
|
||||
|
@ -113,25 +128,21 @@ public class LiteCraftMain
|
|||
public void render()
|
||||
{
|
||||
if (spamLog) System.out.println("rendering " + fps);
|
||||
|
||||
renderer.render();
|
||||
window.render();
|
||||
fps++; // After a successful frame render, increase the frame counter.
|
||||
}
|
||||
|
||||
// Shuts down the game and destroys all the things that are using RAM (so the user doesn't have to restart their computer afterward...)
|
||||
private void destroy()
|
||||
|
||||
public void run()
|
||||
{
|
||||
System.out.println("Closing game...");
|
||||
|
||||
renderer.cleanUp();
|
||||
window.destroy();
|
||||
|
||||
|
||||
// Terminate GLFW and free the error callback
|
||||
GLFW.glfwTerminate();
|
||||
GLFW.glfwSetErrorCallback(null).free();
|
||||
System.out.println("Game closed successfully.");
|
||||
System.out.println("Starting game...");
|
||||
System.out.println("LWJGL version: " + Version.getVersion());
|
||||
System.out.println("Resolution: " + width + 'x' + height);
|
||||
init();
|
||||
frameTimer = System.currentTimeMillis();
|
||||
// Run the rendering loop until the player has attempted to close the window
|
||||
while (!GLFW.glfwWindowShouldClose(window.getWindowLong()))
|
||||
{ loop(); }
|
||||
destroy();
|
||||
}
|
||||
|
||||
}
|
|
@ -10,16 +10,12 @@ public class Window
|
|||
private long windowLong = 0;
|
||||
|
||||
public long getWindowLong()
|
||||
{
|
||||
return windowLong;
|
||||
}
|
||||
{ return windowLong; }
|
||||
|
||||
private String title;
|
||||
|
||||
public String getWindowTitle()
|
||||
{
|
||||
return title;
|
||||
}
|
||||
{ return title; }
|
||||
|
||||
protected void setWindowTitle(String title)
|
||||
{
|
||||
|
@ -31,14 +27,10 @@ public class Window
|
|||
private int width, height;
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return height;
|
||||
}
|
||||
{ return height; }
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return width;
|
||||
}
|
||||
{ return width; }
|
||||
|
||||
public void setHeight(int height)
|
||||
{
|
||||
|
@ -63,20 +55,18 @@ public class Window
|
|||
}
|
||||
|
||||
public boolean shouldClose()
|
||||
{
|
||||
return GLFW.glfwWindowShouldClose(windowLong);
|
||||
}
|
||||
{ return GLFW.glfwWindowShouldClose(windowLong); }
|
||||
|
||||
private void closeWindow()
|
||||
{
|
||||
GLFW.glfwSetWindowShouldClose(windowLong, true);
|
||||
}
|
||||
public void closeWindow()
|
||||
{ GLFW.glfwSetWindowShouldClose(windowLong, true); }
|
||||
|
||||
// (Always useful to have simpler inputs, even if you only ever plan on using these once. Can be great for debugging, or just making life easier.
|
||||
public Window()
|
||||
{this(1600, 900);}
|
||||
{ this(1600, 900); }
|
||||
|
||||
public Window(int width, int height)
|
||||
{this(width, height, "LiteCraft");}
|
||||
{ this(width, height, "LiteCraft"); }
|
||||
|
||||
public Window(int width, int height, String title)
|
||||
{
|
||||
// Keep these in this order!
|
||||
|
@ -106,7 +96,7 @@ public class Window
|
|||
GLFW.glfwShowWindow(windowLong); // Make the window visible
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void destroy()
|
||||
{
|
||||
Callbacks.glfwFreeCallbacks(windowLong);
|
||||
|
@ -114,23 +104,14 @@ public class Window
|
|||
}
|
||||
|
||||
public void render()
|
||||
{
|
||||
swapDisplayBuffers();
|
||||
}
|
||||
{ swapDisplayBuffers(); }
|
||||
|
||||
public void hide()
|
||||
{
|
||||
GLFW.glfwHideWindow(windowLong);
|
||||
}
|
||||
{ GLFW.glfwHideWindow(windowLong); }
|
||||
|
||||
public void show()
|
||||
{
|
||||
GLFW.glfwShowWindow(windowLong);
|
||||
}
|
||||
{ GLFW.glfwShowWindow(windowLong); }
|
||||
|
||||
private void swapDisplayBuffers()
|
||||
{
|
||||
GLFW.glfwSwapBuffers(windowLong);
|
||||
}
|
||||
|
||||
{ GLFW.glfwSwapBuffers(windowLong); }
|
||||
}
|
|
@ -3,8 +3,6 @@ package com.github.halotroop.litecraft.logic;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/*
|
||||
* @author Jack Wilsdon (Stack Exchange)
|
||||
* https://codereview.stackexchange.com/questions/111855/ticker-for-game-timing
|
||||
|
@ -15,31 +13,21 @@ public class Timer
|
|||
private double nextTick;
|
||||
private int tickRate;
|
||||
private Set<TickListener> tickListeners = new HashSet<>();
|
||||
|
||||
|
||||
public Timer(int tickRate)
|
||||
{
|
||||
this.tickRate = tickRate;
|
||||
}
|
||||
{ this.tickRate = tickRate; }
|
||||
|
||||
public void addTickListener(TickListener listener)
|
||||
{
|
||||
tickListeners.add(listener);
|
||||
}
|
||||
{ tickListeners.add(listener); }
|
||||
|
||||
public void removeTickListener(TickListener listener)
|
||||
{
|
||||
tickListeners.remove(listener);
|
||||
}
|
||||
{ tickListeners.remove(listener); }
|
||||
|
||||
public void setTickRate(int tickRate)
|
||||
{
|
||||
this.tickRate = tickRate;
|
||||
}
|
||||
{ this.tickRate = tickRate; }
|
||||
|
||||
public int getTickRate()
|
||||
{
|
||||
return tickRate;
|
||||
}
|
||||
{ return tickRate; }
|
||||
|
||||
public void reset()
|
||||
{
|
||||
|
@ -60,13 +48,16 @@ public class Timer
|
|||
}
|
||||
float deltaTime = (float) (currentTime - lastTick) / targetTimeDelta;
|
||||
for (TickListener listener : tickListeners)
|
||||
{
|
||||
listener.onTick(deltaTime);
|
||||
}
|
||||
{ listener.onTick(deltaTime); }
|
||||
lastTick = currentTime;
|
||||
nextTick = currentTime + targetTimeDelta;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface TickListener
|
||||
{
|
||||
void onTick(float deltaTime);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.github.halotroop.litecraft.options;
|
||||
|
||||
import org.aeonbits.owner.Config;
|
||||
|
||||
@Config.Sources("file:~/Documents/LiteCraft.config")
|
||||
public interface SettingsConfig extends Config
|
||||
{
|
||||
@Key("screen_width")
|
||||
@DefaultValue("640")
|
||||
public int screenWidth();
|
||||
@Key("screen_height")
|
||||
@DefaultValue("480")
|
||||
public int screenHeight();
|
||||
@Key("debug_mode")
|
||||
@DefaultValue("false")
|
||||
public boolean debugMode();
|
||||
@Key("spam_log")
|
||||
@DefaultValue("false")
|
||||
public boolean spamLog();
|
||||
}
|
|
@ -10,23 +10,20 @@ import com.github.halotroop.litecraft.render.model.Vertex;
|
|||
public class Renderer
|
||||
{
|
||||
private Model model;
|
||||
|
||||
public Renderer()
|
||||
{
|
||||
init();
|
||||
|
||||
|
||||
model = new Model();
|
||||
|
||||
Vertex[] vertices =
|
||||
{
|
||||
new Vertex(-1, -1, 0),
|
||||
new Vertex(1, -1, 0),
|
||||
new Vertex(0, 1, 0)
|
||||
};
|
||||
|
||||
model.bufferVertices(vertices);
|
||||
}
|
||||
|
||||
|
||||
public void render()
|
||||
{
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, model.getVBO());
|
||||
|
@ -38,19 +35,14 @@ public class Renderer
|
|||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
|
||||
{ prepare(); }
|
||||
|
||||
private void prepare()
|
||||
{
|
||||
GL11.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set the background color
|
||||
GL11.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set the background color
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // clear the framebuffer
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void cleanUp()
|
||||
{
|
||||
|
||||
}
|
||||
{}
|
||||
}
|
|
@ -8,32 +8,26 @@ import org.lwjgl.opengl.GL15;
|
|||
public class Model
|
||||
{
|
||||
private int vbo, size;
|
||||
|
||||
|
||||
public Model()
|
||||
{
|
||||
vbo = GL15.glGenBuffers();
|
||||
|
||||
size = 0;
|
||||
}
|
||||
|
||||
|
||||
public void bufferVertices(Vertex[] verts)
|
||||
{
|
||||
FloatBuffer buffer = BufferUtils.createFloatBuffer(verts.length * Vertex.SIZE);
|
||||
|
||||
for (Vertex vertex : verts)
|
||||
{
|
||||
buffer.put(vertex.x);
|
||||
buffer.put(vertex.y);
|
||||
buffer.put(vertex.z);
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
size = verts.length;
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import org.joml.Vector3i;
|
|||
public class Vertex extends Vector3i
|
||||
{
|
||||
public static final int SIZE = 3;
|
||||
|
||||
|
||||
public Vertex(int x, int y, int z)
|
||||
{
|
||||
this.x = x;
|
|
@ -3,7 +3,5 @@ package com.github.halotroop.litecraft.types.gui;
|
|||
public class MainMenu extends Menu
|
||||
{
|
||||
public MainMenu()
|
||||
{
|
||||
super();
|
||||
}
|
||||
{ super(); }
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
package scaveleous.mcregion;
|
||||
/*
|
||||
** Author: Scaveleous (Minecraft Forum)
|
||||
** (Public domain)
|
||||
**/
|
||||
// Interfaces with region files on the disk
|
||||
/*
|
||||
|
||||
Region File Format
|
||||
|
||||
Each region file represents a 32x32 group of chunks. The conversion from
|
||||
chunk number to region number is floor(coord / 32): a chunk at (30, -3)
|
||||
would be in region (0, -1), and one at (70, -30) would be at (3, -1).
|
||||
Region files are named "r.x.z.data", where x and z are the region coordinates.
|
||||
|
||||
A region file begins with a 4KB header that describes where chunks are stored
|
||||
in the file. A 4-byte big-endian integer represents sector offsets and sector
|
||||
counts. The chunk offset for a chunk (x, z) begins at byte 4*(x+z*32) in the
|
||||
file. The bottom byte of the chunk offset indicates the number of sectors the
|
||||
chunk takes up, and the top 3 bytes represent the sector number of the chunk.
|
||||
Given a chunk offset o, the chunk data begins at byte 4096*(o/256) and takes up
|
||||
at most 4096*(o%256) bytes. A chunk cannot exceed 1MB in size. If a chunk
|
||||
offset is 0, the corresponding chunk is not stored in the region file.
|
||||
|
||||
Chunk data begins with a 4-byte big-endian integer representing the chunk data
|
||||
length in bytes, not counting the length field. The length must be smaller than
|
||||
4096 times the number of sectors. The next byte is a version field, to allow
|
||||
backwards-compatible updates to how chunks are encoded.
|
||||
|
||||
A version of 1 represents a gzipped NBT file. The gzipped data is the chunk
|
||||
length - 1.
|
||||
|
||||
A version of 2 represents a deflated (zlib compressed) NBT file. The deflated
|
||||
data is the chunk length - 1.
|
||||
*/
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public class RegionFile
|
||||
{
|
||||
/* lets chunk writing be multithreaded by not locking the whole file as a
|
||||
chunk is serializing -- only writes when serialization is over */
|
||||
class ChunkBuffer extends ByteArrayOutputStream
|
||||
{
|
||||
private int x, z;
|
||||
|
||||
public ChunkBuffer(int x, int z)
|
||||
{
|
||||
super(8192); // initialize to 8KB
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{ RegionFile.this.write(x, z, buf, count); }
|
||||
}
|
||||
|
||||
static final int CHUNK_HEADER_SIZE = 5;
|
||||
private static final byte emptySector[] = new byte[4096];
|
||||
private final File fileName;
|
||||
private RandomAccessFile file;
|
||||
private final int offsets[];
|
||||
private ArrayList<Boolean> sectorFree;
|
||||
private int sizeDelta;
|
||||
private long lastModified = 0;
|
||||
|
||||
public RegionFile(File path)
|
||||
{
|
||||
offsets = new int[1024];
|
||||
fileName = path;
|
||||
debugln("REGION LOAD " + fileName);
|
||||
sizeDelta = 0;
|
||||
try
|
||||
{
|
||||
if (path.exists())
|
||||
lastModified = path.lastModified();
|
||||
file = new RandomAccessFile(path, "rw");
|
||||
if (file.length() < 4096)
|
||||
{
|
||||
/* we need to write the chunk offset table */
|
||||
for (int i = 0; i < 1024; ++i)
|
||||
file.writeInt(0);
|
||||
sizeDelta += 4096;
|
||||
}
|
||||
if ((file.length() & 0xfff) != 0)
|
||||
{
|
||||
/* the file size is not a multiple of 4KB, grow it */
|
||||
for (int i = 0; i < (file.length() & 0xfff); ++i)
|
||||
file.write((byte) 0);
|
||||
}
|
||||
/* set up the available sector map */
|
||||
int nSectors = (int) file.length() / 4096;
|
||||
sectorFree = new ArrayList<Boolean>(nSectors);
|
||||
for (int i = 0; i < nSectors; ++i)
|
||||
{ sectorFree.add(true); }
|
||||
sectorFree.set(0, false); // chunk offset table
|
||||
file.seek(0);
|
||||
for (int i = 0; i < 1024; ++i)
|
||||
{
|
||||
int offset = file.readInt();
|
||||
offsets[i] = offset;
|
||||
if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.size())
|
||||
{
|
||||
for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum)
|
||||
{ sectorFree.set((offset >> 8) + sectorNum, false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{ file.close(); }
|
||||
|
||||
// various small debug printing helpers
|
||||
private void debug(String in)
|
||||
{
|
||||
//System.out.print(in);
|
||||
}
|
||||
|
||||
private void debug(String mode, int x, int z, int count, String in)
|
||||
{ debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] " + count + "B = " + in); }
|
||||
|
||||
private void debug(String mode, int x, int z, String in)
|
||||
{ debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] = " + in); }
|
||||
|
||||
private void debugln(String in)
|
||||
{ debug(in + "\n"); }
|
||||
|
||||
private void debugln(String mode, int x, int z, String in)
|
||||
{ debug(mode, x, z, in + "\n"); }
|
||||
|
||||
/* gets an (uncompressed) stream representing the chunk data
|
||||
returns null if the chunk is not found or an error occurs */
|
||||
public synchronized DataInputStream getChunkDataInputStream(int x, int z)
|
||||
{
|
||||
if (outOfBounds(x, z))
|
||||
{
|
||||
debugln("READ", x, z, "out of bounds");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
int offset = getOffset(x, z);
|
||||
if (offset == 0)
|
||||
{
|
||||
// debugln("READ", x, z, "miss");
|
||||
return null;
|
||||
}
|
||||
int sectorNumber = offset >> 8;
|
||||
int numSectors = offset & 0xFF;
|
||||
if (sectorNumber + numSectors > sectorFree.size())
|
||||
{
|
||||
debugln("READ", x, z, "invalid sector");
|
||||
return null;
|
||||
}
|
||||
file.seek(sectorNumber * 4096);
|
||||
int length = file.readInt();
|
||||
if (length > 4096 * numSectors)
|
||||
{
|
||||
debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors);
|
||||
return null;
|
||||
}
|
||||
byte version = file.readByte();
|
||||
if (version == 1)
|
||||
{
|
||||
byte[] data = new byte[length - 1];
|
||||
file.read(data);
|
||||
DataInputStream ret = new DataInputStream(new GZIPInputStream(
|
||||
new ByteArrayInputStream(data)));
|
||||
// debug("READ", x, z, " = found");
|
||||
return ret;
|
||||
}
|
||||
else if (version == 2)
|
||||
{
|
||||
byte[] data = new byte[length - 1];
|
||||
file.read(data);
|
||||
DataInputStream ret = new DataInputStream(new InflaterInputStream(
|
||||
new ByteArrayInputStream(data)));
|
||||
// debug("READ", x, z, " = found");
|
||||
return ret;
|
||||
}
|
||||
debugln("READ", x, z, "unknown version " + version);
|
||||
return null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
debugln("READ", x, z, "exception");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public DataOutputStream getChunkDataOutputStream(int x, int z)
|
||||
{
|
||||
if (outOfBounds(x, z))
|
||||
return null;
|
||||
return new DataOutputStream(new DeflaterOutputStream(
|
||||
new ChunkBuffer(x, z)));
|
||||
}
|
||||
|
||||
private int getOffset(int x, int z) throws IOException
|
||||
{ return offsets[x + z * 32]; }
|
||||
|
||||
/* gets how much the region file has grown since it was last checked */
|
||||
public synchronized int getSizeDelta()
|
||||
{
|
||||
int ret = sizeDelta;
|
||||
sizeDelta = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* the modification date of the region file when it was first opened */
|
||||
public long lastModified()
|
||||
{ return lastModified; }
|
||||
|
||||
/* is this an invalid chunk coordinate? */
|
||||
private boolean outOfBounds(int x, int z)
|
||||
{ return x < 0 || x >= 32 || z < 0 || z >= 32; }
|
||||
|
||||
private void setOffset(int x, int z, int offset) throws IOException
|
||||
{
|
||||
offsets[x + z * 32] = offset;
|
||||
file.seek((x + z * 32) * 4);
|
||||
file.writeInt(offset);
|
||||
}
|
||||
|
||||
/* write a chunk data to the region file at specified sector number */
|
||||
private void write(int sectorNumber, byte[] data, int length) throws IOException
|
||||
{
|
||||
debugln(" " + sectorNumber);
|
||||
file.seek(sectorNumber * 4096);
|
||||
file.writeInt(length + 1); // chunk length
|
||||
file.writeByte(2); // chunk version number
|
||||
file.write(data, 0, length); // chunk data
|
||||
}
|
||||
|
||||
/* write a chunk at (x,z) with length bytes of data to disk */
|
||||
protected synchronized void write(int x, int z, byte[] data, int length)
|
||||
{
|
||||
try
|
||||
{
|
||||
int offset = getOffset(x, z);
|
||||
int sectorNumber = offset >> 8;
|
||||
int sectorsAllocated = offset & 0xFF;
|
||||
int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / 4096 + 1;
|
||||
if (sectorsNeeded >= 256) // maximum chunk size is 1MB
|
||||
return;
|
||||
if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded)
|
||||
{
|
||||
/* we can simply overwrite the old sectors */
|
||||
debug("SAVE", x, z, length, "rewrite");
|
||||
write(sectorNumber, data, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* we need to allocate new sectors */
|
||||
/* mark the sectors previously used for this chunk as free */
|
||||
for (int i = 0; i < sectorsAllocated; ++i)
|
||||
sectorFree.set(sectorNumber + i, true);
|
||||
/* scan for a free space large enough to store this chunk */
|
||||
int runStart = sectorFree.indexOf(true);
|
||||
int runLength = 0;
|
||||
if (runStart != -1)
|
||||
{
|
||||
for (int i = runStart; i < sectorFree.size(); ++i)
|
||||
{
|
||||
if (runLength != 0)
|
||||
{
|
||||
if (sectorFree.get(i))
|
||||
runLength++;
|
||||
else
|
||||
runLength = 0;
|
||||
}
|
||||
else if (sectorFree.get(i))
|
||||
{
|
||||
runStart = i;
|
||||
runLength = 1;
|
||||
}
|
||||
if (runLength >= sectorsNeeded)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (runLength >= sectorsNeeded)
|
||||
{
|
||||
/* we found a free space large enough */
|
||||
debug("SAVE", x, z, length, "reuse");
|
||||
sectorNumber = runStart;
|
||||
setOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
|
||||
for (int i = 0; i < sectorsNeeded; ++i)
|
||||
sectorFree.set(sectorNumber + i, false);
|
||||
write(sectorNumber, data, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no free space large enough found -- we need to grow the file */
|
||||
debug("SAVE", x, z, length, "grow");
|
||||
file.seek(file.length());
|
||||
sectorNumber = sectorFree.size();
|
||||
for (int i = 0; i < sectorsNeeded; ++i)
|
||||
{
|
||||
file.write(emptySector);
|
||||
sectorFree.add(false);
|
||||
}
|
||||
sizeDelta += 4096 * sectorsNeeded;
|
||||
write(sectorNumber, data, length);
|
||||
setOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{e.printStackTrace();}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package scaveleous.mcregion;
|
||||
|
||||
|
||||
/*
|
||||
** Author: Scaveleous (Minecraft Forum)
|
||||
** (Public domain)
|
||||
**/
|
||||
// A simple cache and wrapper for efficiently multiple RegionFiles simultaneously.
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RegionFileCache
|
||||
{
|
||||
private static final Map<File, Reference<RegionFile>> cache = new HashMap<File, Reference<RegionFile>>();
|
||||
|
||||
public static synchronized void clear()
|
||||
{
|
||||
for (Reference<RegionFile> ref : cache.values())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ref.get() != null)
|
||||
ref.get().close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
public static DataInputStream getChunkDataInputStream(File basePath, int x, int z)
|
||||
{
|
||||
RegionFile r = getRegionFile(basePath, x, z);
|
||||
return r.getChunkDataInputStream(x & 31, z & 31);
|
||||
}
|
||||
|
||||
public static DataOutputStream getChunkDataOutputStream(File basePath, int x, int z)
|
||||
{
|
||||
RegionFile r = getRegionFile(basePath, x, z);
|
||||
return r.getChunkDataOutputStream(x & 31, z & 31);
|
||||
}
|
||||
|
||||
public static synchronized RegionFile getRegionFile(File basePath, int x, int z)
|
||||
{
|
||||
File regionDir = new File(basePath, "region");
|
||||
File file = new File(regionDir, "r." + (x >> 5) + "." + (z >> 5) + ".data");
|
||||
Reference<RegionFile> ref = cache.get(file);
|
||||
if (ref != null && ref.get() != null)
|
||||
return ref.get();
|
||||
if (!regionDir.exists())
|
||||
regionDir.mkdirs();
|
||||
RegionFile reg = new RegionFile(file);
|
||||
cache.put(file, new SoftReference<RegionFile>(reg));
|
||||
return reg;
|
||||
}
|
||||
|
||||
public static int getSizeDelta(File basePath, int x, int z)
|
||||
{
|
||||
RegionFile r = getRegionFile(basePath, x, z);
|
||||
return r.getSizeDelta();
|
||||
}
|
||||
|
||||
private RegionFileCache()
|
||||
{}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
package scaveleous.mcregion;
|
||||
|
||||
/*
|
||||
** Author: Scaveleous (Minecraft Forum)
|
||||
** (Public domain)
|
||||
**/
|
||||
// A tool to convert to and from chunk/region files
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
class RegionTool
|
||||
{
|
||||
static private boolean isConsole = false;
|
||||
|
||||
/* copies all files from one directory to another, except for files in the skip set
|
||||
does not copy empty directories */
|
||||
private static void copyDir(File srcDir, File dstDir, Set<File> skip)
|
||||
{
|
||||
byte buf[] = new byte[4096];
|
||||
for (File child : srcDir.listFiles())
|
||||
{
|
||||
if (child.isDirectory())
|
||||
copyDir(child, new File(dstDir, child.getName()), skip);
|
||||
else
|
||||
{
|
||||
if (!skip.contains(child))
|
||||
{
|
||||
try
|
||||
{
|
||||
File dstfile = new File(dstDir, child.getName());
|
||||
dstDir.mkdirs();
|
||||
FileOutputStream out = new FileOutputStream(dstfile);
|
||||
FileInputStream in = new FileInputStream(child);
|
||||
int len = 0;
|
||||
while (len != -1)
|
||||
{
|
||||
out.write(buf, 0, len);
|
||||
len = in.read(buf);
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void exit(String message)
|
||||
{
|
||||
System.err.println(message);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static void exitUsage()
|
||||
{ exit("regionTool: converts between chunks and regions\n" +
|
||||
"usage: java -jar RegionTool.jar [un]pack <world directory> [target directory]"); }
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
if (args.length != 2 && args.length != 3)
|
||||
exitUsage();
|
||||
if (System.console() != null)
|
||||
isConsole = true;
|
||||
int mode = 0;
|
||||
if (args[0].equalsIgnoreCase("unpack"))
|
||||
mode = 1;
|
||||
else if (args[0].equalsIgnoreCase("pack"))
|
||||
mode = 2;
|
||||
if (mode == 0)
|
||||
exitUsage();
|
||||
File worldDir = new File(args[1]);
|
||||
if (!worldDir.exists() || !worldDir.isDirectory())
|
||||
exit("error: " + worldDir.getPath() + " is not a directory");
|
||||
File targetDir = worldDir;
|
||||
if (args.length == 3)
|
||||
{
|
||||
targetDir = new File(args[2]);
|
||||
if (!targetDir.isDirectory())
|
||||
{ targetDir.mkdirs(); }
|
||||
}
|
||||
if (mode == 1)
|
||||
unpack(worldDir, targetDir);
|
||||
else if (mode == 2)
|
||||
pack(worldDir, targetDir);
|
||||
}
|
||||
|
||||
private static void pack(File worldDir, File targetDir)
|
||||
{
|
||||
Set<File> processedFiles = null;
|
||||
if (worldDir != targetDir)
|
||||
processedFiles = new HashSet<File>();
|
||||
Pattern chunkFilePattern = Pattern.compile("c\\.(-?[0-9a-z]+)\\.(-?[0-9a-z]+).dat");
|
||||
Pattern chunkFolderPattern = Pattern.compile("[0-9a-z]|1[0-9a-r]");
|
||||
int chunksPacked = 0;
|
||||
int chunksSkipped = 0;
|
||||
for (File dir1 : worldDir.listFiles())
|
||||
{
|
||||
if (!dir1.isDirectory())
|
||||
continue;
|
||||
if (chunkFolderPattern.matcher(dir1.getName()).matches())
|
||||
{
|
||||
for (File dir2 : dir1.listFiles())
|
||||
{
|
||||
if (!dir2.isDirectory())
|
||||
continue;
|
||||
if (chunkFolderPattern.matcher(dir2.getName()).matches())
|
||||
{
|
||||
for (File chunkFile : dir2.listFiles())
|
||||
{
|
||||
Matcher m = chunkFilePattern.matcher(chunkFile.getName());
|
||||
if (m.matches())
|
||||
{
|
||||
if (packChunk(targetDir, chunkFile, m))
|
||||
chunksPacked++;
|
||||
else
|
||||
chunksSkipped++;
|
||||
if (processedFiles != null)
|
||||
processedFiles.add(chunkFile);
|
||||
}
|
||||
if (isConsole)
|
||||
System.out.print("\rpacked " + chunksPacked + " chunks" +
|
||||
(chunksSkipped > 0 ? ", skipped " + chunksSkipped + " older ones" : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isConsole)
|
||||
System.out.print("\r");
|
||||
System.out.println("packed " + chunksPacked + " chunks" +
|
||||
(chunksSkipped > 0 ? ", skipped " + chunksSkipped + " older ones" : ""));
|
||||
if (processedFiles != null)
|
||||
copyDir(worldDir, targetDir, processedFiles);
|
||||
}
|
||||
|
||||
private static boolean packChunk(File worldDir, File chunkFile, Matcher m)
|
||||
{
|
||||
int x = Integer.parseInt(m.group(1), 36);
|
||||
int z = Integer.parseInt(m.group(2), 36);
|
||||
RegionFile region = RegionFileCache.getRegionFile(worldDir, x, z);
|
||||
if (region.lastModified() > chunkFile.lastModified())
|
||||
return false;
|
||||
byte buf[] = new byte[4096];
|
||||
int len = 0;
|
||||
try
|
||||
{
|
||||
DataInputStream istream = new DataInputStream(
|
||||
new GZIPInputStream(new FileInputStream(chunkFile)));
|
||||
DataOutputStream out = region.getChunkDataOutputStream(x & 31, z & 31);
|
||||
while (len != -1)
|
||||
{
|
||||
out.write(buf, 0, len);
|
||||
len = istream.read(buf);
|
||||
}
|
||||
out.close();
|
||||
istream.close();
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void unpack(File worldDir, File targetDir)
|
||||
{
|
||||
File regionDir = new File(worldDir, "region");
|
||||
if (!regionDir.exists())
|
||||
exit("error: region directory not found");
|
||||
Set<File> processedFiles = null;
|
||||
if (worldDir != targetDir)
|
||||
processedFiles = new HashSet<File>();
|
||||
Pattern regionFilePattern = Pattern.compile("r\\.(-?[0-9]+)\\.(-?[0-9]+).data");
|
||||
Matcher match;
|
||||
for (File file : regionDir.listFiles())
|
||||
{
|
||||
if (!file.isFile())
|
||||
continue;
|
||||
match = regionFilePattern.matcher(file.getName());
|
||||
if (match.matches())
|
||||
{
|
||||
unpackRegionFile(targetDir, file, match);
|
||||
if (processedFiles != null)
|
||||
processedFiles.add(file);
|
||||
}
|
||||
}
|
||||
if (processedFiles != null)
|
||||
copyDir(worldDir, targetDir, processedFiles);
|
||||
}
|
||||
|
||||
private static void unpackRegionFile(File worldDir, File file, Matcher match)
|
||||
{
|
||||
long regionModified = file.lastModified();
|
||||
RegionFile region = new RegionFile(file);
|
||||
String name = file.getName();
|
||||
int regionX = Integer.parseInt(match.group(1));
|
||||
int regionZ = Integer.parseInt(match.group(2));
|
||||
int nWritten = 0, nSkipped = 0;
|
||||
for (int x = 0; x < 32; ++x)
|
||||
{
|
||||
for (int z = 0; z < 32; ++z)
|
||||
{
|
||||
DataInputStream istream = region.getChunkDataInputStream(x, z);
|
||||
if (istream == null)
|
||||
continue;
|
||||
int chunkX = x + (regionX << 5);
|
||||
int chunkZ = z + (regionZ << 5);
|
||||
String chunkName = "c." + Integer.toString(chunkX, 36) + "." + Integer.toString(chunkZ, 36) + ".dat";
|
||||
File chunkFile = new File(worldDir, Integer.toString(chunkX & 63, 36));
|
||||
chunkFile = new File(chunkFile, Integer.toString(chunkZ & 63, 36));
|
||||
if (!chunkFile.exists())
|
||||
chunkFile.mkdirs();
|
||||
chunkFile = new File(chunkFile, chunkName);
|
||||
byte buf[] = new byte[4096];
|
||||
int len = 0;
|
||||
if (chunkFile.lastModified() > regionModified)
|
||||
{
|
||||
nSkipped++;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
new GZIPOutputStream(new FileOutputStream(chunkFile)));
|
||||
while (len != -1)
|
||||
{
|
||||
out.write(buf, 0, len);
|
||||
len = istream.read(buf);
|
||||
}
|
||||
out.close();
|
||||
nWritten++;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (isConsole)
|
||||
System.out.print("\r" + name + ": unpacked " + nWritten + " chunks" +
|
||||
(nSkipped > 0 ? ", skipped " + nSkipped + " newer ones" : ""));
|
||||
}
|
||||
}
|
||||
if (isConsole)
|
||||
System.out.print("\r");
|
||||
System.out.println(name + ": unpacked " + nWritten + " chunks" +
|
||||
(nSkipped > 0 ? ", skipped " + nSkipped + " newer ones" : ""));
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 608 B |
Loading…
Reference in New Issue