[Refactor] Use proper folder structure
parent
72406cd513
commit
9a745dfacc
41
.classpath
41
.classpath
|
@ -1,21 +1,20 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="src" output="target/classes" path="src">
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
</attributes>
|
||||||
</attributes>
|
</classpathentry>
|
||||||
</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-12">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="module" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
</attributes>
|
||||||
</attributes>
|
</classpathentry>
|
||||||
</classpathentry>
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
<attributes>
|
||||||
<attributes>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
</attributes>
|
||||||
</attributes>
|
</classpathentry>
|
||||||
</classpathentry>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
</classpath>
|
||||||
</classpath>
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
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.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.lineNumber=generate
|
||||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
org.eclipse.jdt.core.compiler.debug.sourceFile=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.forbiddenReference=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||||
org.eclipse.jdt.core.compiler.release=enabled
|
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 />
|
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:
|
#### Goals:
|
||||||
- Learn the (old) Minecraft codebase inside and out
|
- 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
|
- Create a clone of Minecraft with as little original code and as many open-source
|
||||||
libraries as possible
|
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.)
|
- 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
|
#### Planned Feature Changes from Vanilla Minecraft
|
||||||
- No more multiplayer. (It adds a lot of lines of code, as well as requiring an entirely
|
- No more multiplayer. (It adds a lot of lines of code, as well as requiring an entirely
|
||||||
different app for a server.)
|
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
|
- Pretty much any creature, monster, item, block, dimension that's unique to Minecraft will not
|
||||||
be present either.
|
be present either.
|
||||||
- These features will be replaced by our own original ideas, or some more generic ones, instead.
|
- 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">
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
<modelVersion>4.0.0</modelVersion>
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
<groupId>LiteCraft</groupId>
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<artifactId>LiteCraft</artifactId>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<groupId>LiteCraft</groupId>
|
||||||
<name>LiteCraft</name>
|
<artifactId>LiteCraft</artifactId>
|
||||||
<description>Lightweight Minecraft Clone Engine</description>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<build>
|
<name>LiteCraft</name>
|
||||||
<sourceDirectory>src</sourceDirectory>
|
<description>Lightweight Minecraft Clone Engine</description>
|
||||||
<plugins>
|
<build>
|
||||||
<plugin>
|
<sourceDirectory>src</sourceDirectory>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<plugins>
|
||||||
<version>3.8.0</version>
|
<plugin>
|
||||||
<configuration>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<release>11</release>
|
<version>3.8.0</version>
|
||||||
</configuration>
|
<configuration>
|
||||||
</plugin>
|
<release>11</release>
|
||||||
</plugins>
|
</configuration>
|
||||||
</build>
|
</plugin>
|
||||||
<properties>
|
</plugins>
|
||||||
<lwjgl.version>3.2.3</lwjgl.version>
|
</build>
|
||||||
<joml.version>1.9.17</joml.version>
|
<properties>
|
||||||
<gson.version>2.8.5</gson.version>
|
<gson.version>2.8.5</gson.version>
|
||||||
<lwjgl.natives>natives-windows</lwjgl.natives>
|
<lwjgl.version>3.2.4-SNAPSHOT</lwjgl.version>
|
||||||
</properties>
|
<joml.version>1.9.20</joml.version>
|
||||||
|
<lwjgl.natives>natives-linux</lwjgl.natives>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<repositories>
|
||||||
<dependencies>
|
<repository>
|
||||||
<dependency>
|
<id>sonatype-snapshots</id>
|
||||||
<groupId>org.lwjgl</groupId>
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
<artifactId>lwjgl-bom</artifactId>
|
<releases>
|
||||||
<version>${lwjgl.version}</version>
|
<enabled>false</enabled>
|
||||||
<scope>import</scope>
|
</releases>
|
||||||
<type>pom</type>
|
<snapshots>
|
||||||
</dependency>
|
<enabled>true</enabled>
|
||||||
</dependencies>
|
</snapshots>
|
||||||
</dependencyManagement>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencyManagement>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId></dependency>
|
<dependencies>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId></dependency>
|
<dependency>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openal</artifactId></dependency>
|
<groupId>org.lwjgl</groupId>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId></dependency>
|
<artifactId>lwjgl-bom</artifactId>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
<version>${lwjgl.version}</version>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-glfw</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
<scope>import</scope>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-openal</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
<type>pom</type>
|
||||||
<dependency><groupId>org.lwjgl</groupId><artifactId>lwjgl-opengl</artifactId><classifier>${lwjgl.natives}</classifier></dependency>
|
</dependency>
|
||||||
<dependency><groupId>org.joml</groupId><artifactId>joml</artifactId><version>${joml.version}</version></dependency>
|
</dependencies>
|
||||||
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency>
|
</dependencyManagement>
|
||||||
<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>
|
||||||
</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>
|
</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;
|
package com.github.halotroop.litecraft;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.*;
|
||||||
import org.lwjgl.*;
|
import org.lwjgl.*;
|
||||||
import org.lwjgl.glfw.*;
|
import org.lwjgl.glfw.*;
|
||||||
import org.lwjgl.opengl.*;
|
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;
|
||||||
|
import com.github.halotroop.litecraft.logic.Timer.TickListener;
|
||||||
import com.github.halotroop.litecraft.render.Renderer;
|
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;
|
protected Timer timer;
|
||||||
private int fps, ups, tps;
|
private int fps, ups, tps;
|
||||||
public static int maxFPS = 100;
|
|
||||||
private long frameTimer;
|
private long frameTimer;
|
||||||
private Renderer renderer;
|
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 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()
|
protected TickListener tickListener = new TickListener()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onTick(float deltaTime)
|
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();
|
// 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()
|
||||||
// Run the rendering loop until the player has attempted to close the window
|
{
|
||||||
while (!GLFW.glfwWindowShouldClose(window.getWindowLong()))
|
if (debug) System.out.println("Closing game...");
|
||||||
{
|
renderer.cleanUp();
|
||||||
loop();
|
window.destroy();
|
||||||
}
|
// Terminate GLFW and free the error callback
|
||||||
|
GLFW.glfwTerminate();
|
||||||
destroy();
|
GLFW.glfwSetErrorCallback(null).free();
|
||||||
|
if (debug) System.out.println("Game closed successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init()
|
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.
|
// Setup an error callback. The default implementation will print the error message in System.err.
|
||||||
GLFWErrorCallback.createPrint(System.err).set();
|
GLFWErrorCallback.createPrint(System.err).set();
|
||||||
// Initialize GLFW. Most GLFW functions will not work before doing this.
|
// Initialize GLFW. Most GLFW functions will not work before doing this.
|
||||||
if (!GLFW.glfwInit()) throw new IllegalStateException("Unable to initialize GLFW");
|
if (!GLFW.glfwInit()) throw new IllegalStateException("Unable to initialize GLFW");
|
||||||
// Configure GLFW
|
// Configure GLFW
|
||||||
window = new Window(width, height);
|
window = new Window(width, height);
|
||||||
|
|
||||||
timer = new Timer(20);
|
timer = new Timer(20);
|
||||||
timer.addTickListener(tickListener);
|
timer.addTickListener(tickListener);
|
||||||
|
GL.createCapabilities(); // This line is critical for LWJGL's interoperation with GLFW.
|
||||||
GL.createCapabilities(); // This line is critical for LWJGL's interoperation with GLFW.
|
renderer = new Renderer();
|
||||||
renderer = new Renderer();
|
if (splashText == "")
|
||||||
|
{
|
||||||
window.setWindowTitle("LiteCraft - " + "INSERT SPLASH TEXT HERE!");
|
window.setWindowTitle("LiteCraft - " + "INSERT SPLASH TEXT HERE!");
|
||||||
|
}
|
||||||
input();
|
input();
|
||||||
|
if (debug) System.out.println("Initialization complete.");
|
||||||
System.out.println("Initialization complete.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up the key inputs for the game (currently just esc for closing the game)
|
// 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
|
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
|
// Things that the game should do over and over and over again until it is closed
|
||||||
private void loop()
|
private void loop()
|
||||||
{
|
{
|
||||||
ups++;
|
ups++;
|
||||||
// Poll for window events. The key callback above will only be invoked during this call.
|
// Poll for window events. The key callback above will only be invoked during this call.
|
||||||
GLFW.glfwPollEvents();
|
GLFW.glfwPollEvents();
|
||||||
|
|
||||||
timer.tick();
|
timer.tick();
|
||||||
|
|
||||||
if (fps < maxFPS) render();
|
if (fps < maxFPS) render();
|
||||||
|
|
||||||
if (System.currentTimeMillis() > frameTimer + 1000) // wait for one second
|
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;
|
fps = 0;
|
||||||
ups = 0;
|
ups = 0;
|
||||||
tps = 0;
|
tps = 0;
|
||||||
|
@ -113,25 +128,21 @@ public class LiteCraftMain
|
||||||
public void render()
|
public void render()
|
||||||
{
|
{
|
||||||
if (spamLog) System.out.println("rendering " + fps);
|
if (spamLog) System.out.println("rendering " + fps);
|
||||||
|
|
||||||
renderer.render();
|
renderer.render();
|
||||||
window.render();
|
window.render();
|
||||||
fps++; // After a successful frame render, increase the frame counter.
|
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...)
|
public void run()
|
||||||
private void destroy()
|
|
||||||
{
|
{
|
||||||
System.out.println("Closing game...");
|
System.out.println("Starting game...");
|
||||||
|
System.out.println("LWJGL version: " + Version.getVersion());
|
||||||
renderer.cleanUp();
|
System.out.println("Resolution: " + width + 'x' + height);
|
||||||
window.destroy();
|
init();
|
||||||
|
frameTimer = System.currentTimeMillis();
|
||||||
|
// Run the rendering loop until the player has attempted to close the window
|
||||||
// Terminate GLFW and free the error callback
|
while (!GLFW.glfwWindowShouldClose(window.getWindowLong()))
|
||||||
GLFW.glfwTerminate();
|
{ loop(); }
|
||||||
GLFW.glfwSetErrorCallback(null).free();
|
destroy();
|
||||||
System.out.println("Game closed successfully.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -10,16 +10,12 @@ public class Window
|
||||||
private long windowLong = 0;
|
private long windowLong = 0;
|
||||||
|
|
||||||
public long getWindowLong()
|
public long getWindowLong()
|
||||||
{
|
{ return windowLong; }
|
||||||
return windowLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
public String getWindowTitle()
|
public String getWindowTitle()
|
||||||
{
|
{ return title; }
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setWindowTitle(String title)
|
protected void setWindowTitle(String title)
|
||||||
{
|
{
|
||||||
|
@ -31,14 +27,10 @@ public class Window
|
||||||
private int width, height;
|
private int width, height;
|
||||||
|
|
||||||
public int getHeight()
|
public int getHeight()
|
||||||
{
|
{ return height; }
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWidth()
|
public int getWidth()
|
||||||
{
|
{ return width; }
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeight(int height)
|
public void setHeight(int height)
|
||||||
{
|
{
|
||||||
|
@ -63,20 +55,18 @@ public class Window
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldClose()
|
public boolean shouldClose()
|
||||||
{
|
{ return GLFW.glfwWindowShouldClose(windowLong); }
|
||||||
return GLFW.glfwWindowShouldClose(windowLong);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeWindow()
|
public void closeWindow()
|
||||||
{
|
{ GLFW.glfwSetWindowShouldClose(windowLong, true); }
|
||||||
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.
|
// (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()
|
public Window()
|
||||||
{this(1600, 900);}
|
{ this(1600, 900); }
|
||||||
|
|
||||||
public Window(int width, int height)
|
public Window(int width, int height)
|
||||||
{this(width, height, "LiteCraft");}
|
{ this(width, height, "LiteCraft"); }
|
||||||
|
|
||||||
public Window(int width, int height, String title)
|
public Window(int width, int height, String title)
|
||||||
{
|
{
|
||||||
// Keep these in this order!
|
// Keep these in this order!
|
||||||
|
@ -106,7 +96,7 @@ public class Window
|
||||||
GLFW.glfwShowWindow(windowLong); // Make the window visible
|
GLFW.glfwShowWindow(windowLong); // Make the window visible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy()
|
public void destroy()
|
||||||
{
|
{
|
||||||
Callbacks.glfwFreeCallbacks(windowLong);
|
Callbacks.glfwFreeCallbacks(windowLong);
|
||||||
|
@ -114,23 +104,14 @@ public class Window
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render()
|
public void render()
|
||||||
{
|
{ swapDisplayBuffers(); }
|
||||||
swapDisplayBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hide()
|
public void hide()
|
||||||
{
|
{ GLFW.glfwHideWindow(windowLong); }
|
||||||
GLFW.glfwHideWindow(windowLong);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show()
|
public void show()
|
||||||
{
|
{ GLFW.glfwShowWindow(windowLong); }
|
||||||
GLFW.glfwShowWindow(windowLong);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void swapDisplayBuffers()
|
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.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @author Jack Wilsdon (Stack Exchange)
|
* @author Jack Wilsdon (Stack Exchange)
|
||||||
* https://codereview.stackexchange.com/questions/111855/ticker-for-game-timing
|
* https://codereview.stackexchange.com/questions/111855/ticker-for-game-timing
|
||||||
|
@ -15,31 +13,21 @@ public class Timer
|
||||||
private double nextTick;
|
private double nextTick;
|
||||||
private int tickRate;
|
private int tickRate;
|
||||||
private Set<TickListener> tickListeners = new HashSet<>();
|
private Set<TickListener> tickListeners = new HashSet<>();
|
||||||
|
|
||||||
public Timer(int tickRate)
|
public Timer(int tickRate)
|
||||||
{
|
{ this.tickRate = tickRate; }
|
||||||
this.tickRate = tickRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTickListener(TickListener listener)
|
public void addTickListener(TickListener listener)
|
||||||
{
|
{ tickListeners.add(listener); }
|
||||||
tickListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeTickListener(TickListener listener)
|
public void removeTickListener(TickListener listener)
|
||||||
{
|
{ tickListeners.remove(listener); }
|
||||||
tickListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTickRate(int tickRate)
|
public void setTickRate(int tickRate)
|
||||||
{
|
{ this.tickRate = tickRate; }
|
||||||
this.tickRate = tickRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTickRate()
|
public int getTickRate()
|
||||||
{
|
{ return tickRate; }
|
||||||
return tickRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
@ -60,13 +48,16 @@ public class Timer
|
||||||
}
|
}
|
||||||
float deltaTime = (float) (currentTime - lastTick) / targetTimeDelta;
|
float deltaTime = (float) (currentTime - lastTick) / targetTimeDelta;
|
||||||
for (TickListener listener : tickListeners)
|
for (TickListener listener : tickListeners)
|
||||||
{
|
{ listener.onTick(deltaTime); }
|
||||||
listener.onTick(deltaTime);
|
|
||||||
}
|
|
||||||
lastTick = currentTime;
|
lastTick = currentTime;
|
||||||
nextTick = currentTime + targetTimeDelta;
|
nextTick = currentTime + targetTimeDelta;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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
|
public class Renderer
|
||||||
{
|
{
|
||||||
private Model model;
|
private Model model;
|
||||||
|
|
||||||
public Renderer()
|
public Renderer()
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
|
||||||
model = new Model();
|
model = new Model();
|
||||||
|
|
||||||
Vertex[] vertices =
|
Vertex[] vertices =
|
||||||
{
|
{
|
||||||
new Vertex(-1, -1, 0),
|
new Vertex(-1, -1, 0),
|
||||||
new Vertex(1, -1, 0),
|
new Vertex(1, -1, 0),
|
||||||
new Vertex(0, 1, 0)
|
new Vertex(0, 1, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
model.bufferVertices(vertices);
|
model.bufferVertices(vertices);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render()
|
public void render()
|
||||||
{
|
{
|
||||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, model.getVBO());
|
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, model.getVBO());
|
||||||
|
@ -38,19 +35,14 @@ public class Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{ prepare(); }
|
||||||
prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void 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
|
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // clear the framebuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void cleanUp()
|
public void cleanUp()
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -8,32 +8,26 @@ import org.lwjgl.opengl.GL15;
|
||||||
public class Model
|
public class Model
|
||||||
{
|
{
|
||||||
private int vbo, size;
|
private int vbo, size;
|
||||||
|
|
||||||
public Model()
|
public Model()
|
||||||
{
|
{
|
||||||
vbo = GL15.glGenBuffers();
|
vbo = GL15.glGenBuffers();
|
||||||
|
|
||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bufferVertices(Vertex[] verts)
|
public void bufferVertices(Vertex[] verts)
|
||||||
{
|
{
|
||||||
FloatBuffer buffer = BufferUtils.createFloatBuffer(verts.length * Vertex.SIZE);
|
FloatBuffer buffer = BufferUtils.createFloatBuffer(verts.length * Vertex.SIZE);
|
||||||
|
|
||||||
for (Vertex vertex : verts)
|
for (Vertex vertex : verts)
|
||||||
{
|
{
|
||||||
buffer.put(vertex.x);
|
buffer.put(vertex.x);
|
||||||
buffer.put(vertex.y);
|
buffer.put(vertex.y);
|
||||||
buffer.put(vertex.z);
|
buffer.put(vertex.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
|
|
||||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
|
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
|
||||||
|
|
||||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||||
|
|
||||||
size = verts.length;
|
size = verts.length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import org.joml.Vector3i;
|
||||||
public class Vertex extends Vector3i
|
public class Vertex extends Vector3i
|
||||||
{
|
{
|
||||||
public static final int SIZE = 3;
|
public static final int SIZE = 3;
|
||||||
|
|
||||||
public Vertex(int x, int y, int z)
|
public Vertex(int x, int y, int z)
|
||||||
{
|
{
|
||||||
this.x = x;
|
this.x = x;
|
|
@ -3,7 +3,5 @@ package com.github.halotroop.litecraft.types.gui;
|
||||||
public class MainMenu extends Menu
|
public class MainMenu extends Menu
|
||||||
{
|
{
|
||||||
public MainMenu()
|
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