diff --git a/pom.xml b/pom.xml
index 09b43e7e..9edecc2a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,14 @@
9
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
@@ -70,14 +78,13 @@
3.7.02
+
- se.llbit
+ com.github.llbit
jo-nbt
- 1.3.0
+ 1baae2f49a
-
-
net.sourceforge.argparse4j
@@ -98,6 +105,28 @@
3.10
+
+
+ dnsjava
+ dnsjava
+ 3.3.0
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.0-alpha1
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.0-alpha0
+
+
+
diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java
index e3b4ff04..ec1a85b3 100644
--- a/src/main/java/Launcher.java
+++ b/src/main/java/Launcher.java
@@ -74,6 +74,10 @@ private static Namespace getArguments(String[] args) {
.setDefault(true)
.type(boolean.class)
.help("When false, set world type to a superflat void to prevent new chunks from being added.");
+ parser.addArgument("--enable-srv-lookup").dest("enable-srv-lookup")
+ .setDefault(true)
+ .type(boolean.class)
+ .help("When true, checks for true address using DNS service records");
Namespace ns = null;
try {
diff --git a/src/main/java/game/Game.java b/src/main/java/game/Game.java
index e2e51d9a..93cf4138 100644
--- a/src/main/java/game/Game.java
+++ b/src/main/java/game/Game.java
@@ -51,6 +51,7 @@ public abstract class Game {
private static EncryptionManager encryptionManager;
private static CompressionManager compressionManager;
private static boolean enableWorldGen;
+ private static boolean enableSrvLookup;
public static int getDataVersion() {
return dataVersion;
@@ -105,6 +106,7 @@ public static void init(Namespace args) {
GuiManager.showGui();
}
enableWorldGen = args.getBoolean("enable-world-gen");
+ enableSrvLookup = args.getBoolean("enable-srv-lookup");
versionHandler = VersionHandler.createVersionHandler();
}
@@ -239,4 +241,8 @@ public static int getRenderDistance() {
public static boolean isWorldGenEnabled() {
return enableWorldGen;
}
+
+ public static boolean isSrvLookupEnabled() {
+ return enableSrvLookup;
+ }
}
diff --git a/src/main/java/game/UnsupportedMinecraftVersionException.java b/src/main/java/game/UnsupportedMinecraftVersionException.java
new file mode 100644
index 00000000..47b88ca4
--- /dev/null
+++ b/src/main/java/game/UnsupportedMinecraftVersionException.java
@@ -0,0 +1,7 @@
+package game;
+
+public class UnsupportedMinecraftVersionException extends RuntimeException {
+ public UnsupportedMinecraftVersionException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/game/data/WorldManager.java b/src/main/java/game/data/WorldManager.java
index 14e520ff..2d9812a3 100644
--- a/src/main/java/game/data/WorldManager.java
+++ b/src/main/java/game/data/WorldManager.java
@@ -12,6 +12,7 @@
import game.data.region.McaFile;
import game.data.region.Region;
import gui.GuiManager;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import proxy.CompressionManager;
import se.llbit.nbt.ByteTag;
@@ -37,6 +38,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
@@ -66,6 +68,10 @@ public class WorldManager extends Thread {
private static boolean writeChunks;
+ private static boolean isPaused;
+
+ private static boolean isSaving;
+
private static ContainerManager containerManager;
private WorldManager() {
@@ -140,9 +146,8 @@ private static void saveLevelData() throws IOException {
File levelDat = Paths.get(Game.getExportDirectory(), "level.dat").toFile();
// if there is no level.dat yet, make one from the default
- // TODO: fix memory leak caused by duplicates in the NBT tags
InputStream fileInput;
- if (false && levelDat.isFile()) {
+ if (levelDat.isFile()) {
fileInput = new FileInputStream(levelDat);
} else {
fileInput = WorldManager.class.getClassLoader().getResourceAsStream("level.dat");
@@ -339,10 +344,18 @@ public void run() {
/**
* Save the world. Will tell all regions to save their chunks.
*/
- private static void save() {
+ public static void save() {
if (!writeChunks) {
return;
}
+
+ // make sure we can't have two saving calls at once (due to save & exit)
+ if (isSaving) {
+ return;
+ }
+ isSaving = true;
+
+
if (!regions.isEmpty()) {
// convert the values to an array first to prevent blocking any threads
Region[] r = regions.values().toArray(new Region[regions.size()]);
@@ -368,6 +381,8 @@ private static void save() {
// remove empty regions
regions.entrySet().removeIf(el -> el.getValue().isEmpty());
+
+ isSaving = false;
}
public static ContainerManager getContainerManager() {
@@ -377,6 +392,34 @@ public static ContainerManager getContainerManager() {
return containerManager;
}
+ public static void pauseSaving() {
+ isPaused = true;
+ }
+
+ public static void resumeSaving() {
+ isPaused = false;
+ }
+
+ public static void deleteAllExisting() {
+ regions = new HashMap<>();
+ ChunkFactory.getInstance().clear();
+
+ try {
+ File dir = Paths.get(Game.getExportDirectory(), Game.getDimension().getPath(), "region").toFile();
+
+ if (dir.isDirectory()) {
+ FileUtils.cleanDirectory(dir);
+ }
+ } catch (IOException ex) {
+ System.out.println("Could not delete region files. Reason: " + ex.getMessage());
+ }
+
+ GuiManager.clearChunks();
+ }
+
+ public static boolean isPaused() {
+ return isPaused;
+ }
}
diff --git a/src/main/java/game/data/chunk/ChunkFactory.java b/src/main/java/game/data/chunk/ChunkFactory.java
index 9d4a56a4..69b5710d 100644
--- a/src/main/java/game/data/chunk/ChunkFactory.java
+++ b/src/main/java/game/data/chunk/ChunkFactory.java
@@ -48,6 +48,10 @@ public static ChunkFactory getInstance() {
}
private ChunkFactory() {
+ clear();
+ }
+
+ public void clear() {
this.tileEntities = new ConcurrentHashMap<>();
this.chunkEntities = new ConcurrentHashMap<>();
this.entities = new ConcurrentHashMap<>();
@@ -126,6 +130,11 @@ public void updateTileEntity(Coordinate3D position, SpecificTag entityData) {
* Need a non-static method to do this as we cannot otherwise call notify
*/
public synchronized void addChunk(DataTypeProvider provider) {
+ // if the world manager is currently paused, discard this chunk
+ if (WorldManager.isPaused()) {
+ return;
+ }
+
unparsedChunks.add(new ChunkParserPair(provider, Game.getDimension()));
notify();
}
diff --git a/src/main/java/game/data/region/McaFile.java b/src/main/java/game/data/region/McaFile.java
index 467d6980..622edfa7 100644
--- a/src/main/java/game/data/region/McaFile.java
+++ b/src/main/java/game/data/region/McaFile.java
@@ -24,8 +24,8 @@
public class McaFile {
public final static int SECTOR_SIZE = 4096;
private Map chunkMap;
- private Path filePath;
- private Coordinate2D regionLocation;
+ private final Path filePath;
+ private final Coordinate2D regionLocation;
/**
* Parse MCA from a given file location.
diff --git a/src/main/java/game/data/registries/RegistryLoader.java b/src/main/java/game/data/registries/RegistryLoader.java
index 05cf4d7c..44dadac7 100644
--- a/src/main/java/game/data/registries/RegistryLoader.java
+++ b/src/main/java/game/data/registries/RegistryLoader.java
@@ -2,6 +2,7 @@
import com.google.gson.Gson;
+import game.UnsupportedMinecraftVersionException;
import game.data.chunk.entity.EntityNames;
import game.data.chunk.palette.GlobalPalette;
import game.data.container.ItemRegistry;
@@ -125,8 +126,13 @@ private void generateReports() throws IOException, InterruptedException {
*/
private void moveReports() throws IOException {
ensureExists(destinationPath);
- Files.move(REGISTRIES_GENERATED_PATH, registryPath, StandardCopyOption.REPLACE_EXISTING);
- Files.move(BLOCKS_GENERATED_PATH, blocksPath, StandardCopyOption.REPLACE_EXISTING);
+
+ if (versionSupportsGenerators()) {
+ Files.move(REGISTRIES_GENERATED_PATH, registryPath, StandardCopyOption.REPLACE_EXISTING);
+ }
+ if (versionSupportsBlockGenerator()) {
+ Files.move(BLOCKS_GENERATED_PATH, blocksPath, StandardCopyOption.REPLACE_EXISTING);
+ }
}
/**
@@ -150,36 +156,48 @@ private void ensureExists(Path folder) {
}
public EntityNames generateEntityNames() throws IOException {
- if (version.equals("1.12.2")) {
+ if (versionSupportsGenerators()) {
+ return EntityNames.fromRegistry(new FileInputStream(registryPath.toFile()));
+ } else if (version.equals("1.12.2")) {
return EntityNames.fromJson("1.12.2");
+ } else if (version.equals("1.13.2")) {
+ return EntityNames.fromJson("1.13.2");
} else {
- return EntityNames.fromRegistry(new FileInputStream(registryPath.toFile()));
+ throw new UnsupportedMinecraftVersionException(version);
}
}
public GlobalPalette generateGlobalPalette() throws IOException {
- if (version.equals("1.12.2")) {
- return new GlobalPalette("1.12.2");
- } else {
+ if (versionSupportsBlockGenerator()) {
return new GlobalPalette(new FileInputStream(blocksPath.toFile()));
+ } else {
+ return new GlobalPalette("1.12.2");
}
}
public MenuRegistry generateMenuRegistry() throws IOException {
- if (version.equals("1.12.2")) {
- return new MenuRegistry();
- } else {
+ if (versionSupportsGenerators()) {
return MenuRegistry.fromRegistry(new FileInputStream(registryPath.toFile()));
+ } else {
+ return new MenuRegistry();
}
}
public ItemRegistry generateItemRegistry() throws IOException {
- if (version.equals("1.12.2")) {
- return new ItemRegistry();
- } else {
+ if (versionSupportsGenerators()) {
return ItemRegistry.fromRegistry(new FileInputStream(registryPath.toFile()));
+ } else {
+ return new ItemRegistry();
}
}
+
+ public boolean versionSupportsBlockGenerator() {
+ return !version.startsWith("1.12");
+ }
+
+ public boolean versionSupportsGenerators() {
+ return versionSupportsBlockGenerator() && !version.startsWith("1.13");
+ }
}
class VersionMap extends HashMap { }
diff --git a/src/main/java/gui/CanvasHandler.java b/src/main/java/gui/CanvasHandler.java
index b6f5301d..cb0db4d2 100644
--- a/src/main/java/gui/CanvasHandler.java
+++ b/src/main/java/gui/CanvasHandler.java
@@ -57,6 +57,13 @@ public class CanvasHandler extends JPanel implements ActionListener {
bindScroll();
}
+ public void clearChunks() {
+ chunkMap.clear();
+ drawableChunks.clear();
+
+ hasChanged = true;
+ }
+
private void bindScroll() {
this.addMouseWheelListener(mouseWheelEvent -> {
this.renderDistance = this.renderDistance + (mouseWheelEvent.getWheelRotation() * 2);
diff --git a/src/main/java/gui/GuiManager.java b/src/main/java/gui/GuiManager.java
index bcf296f6..c6f47c72 100644
--- a/src/main/java/gui/GuiManager.java
+++ b/src/main/java/gui/GuiManager.java
@@ -75,5 +75,11 @@ public static void drawExistingChunks(List existing) {
existing.forEach(chunkGraphicsHandler::setChunkExists);
}
}
+
+ public static void clearChunks() {
+ if (chunkGraphicsHandler != null) {
+ chunkGraphicsHandler.clearChunks();
+ }
+ }
}
diff --git a/src/main/java/gui/RightClickMenu.java b/src/main/java/gui/RightClickMenu.java
index ea9f725b..b6efccb5 100644
--- a/src/main/java/gui/RightClickMenu.java
+++ b/src/main/java/gui/RightClickMenu.java
@@ -5,12 +5,44 @@
import java.awt.event.ActionEvent;
import java.io.IOException;
import javax.swing.AbstractAction;
+import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
public class RightClickMenu extends JPopupMenu {
+ final static String PROMPT_PAUSE = "Pause chunk saving";
+ final static String PROMPT_RESUME = "Resume chunk saving";
+
public RightClickMenu(CanvasHandler handler) {
+ JMenuItem togglePause = new JMenuItem(PROMPT_PAUSE);
+
+ togglePause.addActionListener(new AbstractAction("Pause chunk saving") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (WorldManager.isPaused()) {
+ System.out.println("Resuming");
+ WorldManager.resumeSaving();
+ togglePause.setText(PROMPT_PAUSE);
+ } else {
+ System.out.println("Pausing");
+ WorldManager.pauseSaving();
+ togglePause.setText(PROMPT_RESUME);
+ }
+ }
+ });
+ add(togglePause);
+
+ add(new JMenuItem(new AbstractAction("Delete all downloaded chunks") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ WorldManager.deleteAllExisting();
+ }
+ }));
+
+
+ add(new Separator());
+
add(new JMenuItem(new AbstractAction("Save overview to file") {
@Override
public void actionPerformed(ActionEvent e) {
@@ -30,9 +62,10 @@ public void actionPerformed(ActionEvent e) {
}
}));
- add(new JMenuItem(new AbstractAction("Exit") {
+ add(new JMenuItem(new AbstractAction("Save & Exit") {
@Override
public void actionPerformed(ActionEvent e) {
+ WorldManager.save();
System.exit(0);
}
}));
diff --git a/src/main/java/proxy/ProxyServer.java b/src/main/java/proxy/ProxyServer.java
index 4e89d114..e974e418 100644
--- a/src/main/java/proxy/ProxyServer.java
+++ b/src/main/java/proxy/ProxyServer.java
@@ -2,6 +2,10 @@
import game.Game;
import game.NetworkMode;
+import org.xbill.DNS.Lookup;
+import org.xbill.DNS.Record;
+import org.xbill.DNS.SRVRecord;
+import org.xbill.DNS.Type;
import packets.DataReader;
import java.io.InputStream;
@@ -48,6 +52,10 @@ public void runServer(DataReader onServerBoundPacket, DataReader onClientBoundPa
@Override
public void run() {
+ if (Game.isSrvLookupEnabled()) {
+ performSrvLookup();
+ }
+
String friendlyHost = host + (portRemote == DEFAULT_PORT ? "" : ":" + portRemote);
System.out.println("Starting proxy for " + friendlyHost + ". Make sure to connect to localhost:" + portLocal + " instead of the regular server address.");
@@ -75,8 +83,7 @@ public void run() {
// If the server cannot connect, close client connection
attempt(() -> server.set(new Socket(host, portRemote)), (ex) -> {
- System.out.println("Cannot connect to " + friendlyHost + ". The server may be down or on a different address.");
-
+ System.out.println("Cannot connect to " + friendlyHost + ". The server may be down or on a different address. (" + ex.getClass().getCanonicalName() + ")");
attempt(client.get()::close);
});
@@ -130,6 +137,26 @@ public void run() {
}
}
+ /**
+ * Checks for DNS service records of the form _minecraft._tcp.example.com. If they exist, we will replace the
+ * current host and port with the ones found there.
+ */
+ private void performSrvLookup() {
+ attempt(() -> {
+ Record[] records = new Lookup("_minecraft._tcp." + host, Type.SRV).run();
+
+ // no records were found
+ if (records == null || records.length == 0) {
+ return;
+ }
+
+ // if there's multiple records, we'll just take the first one
+ SRVRecord srvRecord = (SRVRecord) records[0];
+ portRemote = srvRecord.getPort();
+ host = srvRecord.getTarget().toString(true);
+ });
+ }
+
/**
* Simple method to make exception handling cleaner.
*/
diff --git a/src/main/resources/entities-1.13.2.json b/src/main/resources/entities-1.13.2.json
new file mode 100644
index 00000000..0ee09ec9
--- /dev/null
+++ b/src/main/resources/entities-1.13.2.json
@@ -0,0 +1,99 @@
+{
+ "entities": {
+ "0": "minecraft:area_effect_cloud",
+ "1": "minecraft:armor_stand",
+ "2": "minecraft:arrow",
+ "3": "minecraft:bat",
+ "4": "minecraft:blaze",
+ "5": "minecraft:boat",
+ "6": "minecraft:cave_spider",
+ "7": "minecraft:chicken",
+ "8": "minecraft:cod",
+ "9": "minecraft:cow",
+ "10": "minecraft:creeper",
+ "11": "minecraft:donkey",
+ "12": "minecraft:dolphin",
+ "13": "minecraft:dragon_fireball",
+ "14": "minecraft:drowned",
+ "15": "minecraft:elder_guardian",
+ "16": "minecraft:end_crystal",
+ "17": "minecraft:ender_dragon",
+ "18": "minecraft:enderman",
+ "19": "minecraft:endermite",
+ "20": "minecraft:evoker_fangs",
+ "21": "minecraft:evoker",
+ "22": "minecraft:experience_orb",
+ "23": "minecraft:eye_of_ender",
+ "24": "minecraft:falling_block",
+ "25": "minecraft:fireworks_rocket",
+ "26": "minecraft:ghast",
+ "27": "minecraft:giant",
+ "28": "minecraft:guardian",
+ "29": "minecraft:horse",
+ "30": "minecraft:husk",
+ "31": "minecraft:illusioner",
+ "32": "minecraft:item",
+ "33": "minecraft:item_frame",
+ "34": "minecraft:fireball",
+ "35": "minecraft:leash_knot",
+ "36": "minecraft:llama",
+ "37": "minecraft:llama_spit",
+ "38": "minecraft:magma_cube",
+ "39": "minecraft:minecart",
+ "40": "minecraft:chest_minecart",
+ "41": "minecraft:commandblock_minecart",
+ "42": "minecraft:furnace_minecart",
+ "43": "minecraft:hopper_minecart",
+ "44": "minecraft:spawner_minecart",
+ "45": "minecraft:tnt_minecart",
+ "46": "minecraft:mule",
+ "47": "minecraft:mooshroom",
+ "48": "minecraft:ocelot",
+ "49": "minecraft:painting",
+ "50": "minecraft:parrot",
+ "51": "minecraft:pig",
+ "52": "minecraft:pufferfish",
+ "53": "minecraft:zombie_pigman",
+ "54": "minecraft:polar_bear",
+ "55": "minecraft:tnt",
+ "56": "minecraft:rabbit",
+ "57": "minecraft:salmon",
+ "58": "minecraft:sheep",
+ "59": "minecraft:shulker",
+ "60": "minecraft:shulker_bullet",
+ "61": "minecraft:silverfish",
+ "62": "minecraft:skeleton",
+ "63": "minecraft:skeleton_horse",
+ "64": "minecraft:slime",
+ "65": "minecraft:small_fireball",
+ "66": "minecraft:snow_golem",
+ "67": "minecraft:snowball",
+ "68": "minecraft:spectral_arrow",
+ "69": "minecraft:spider",
+ "70": "minecraft:squid",
+ "71": "minecraft:stray",
+ "72": "minecraft:tropical_fish",
+ "73": "minecraft:turtle",
+ "74": "minecraft:egg",
+ "75": "minecraft:ender_pearl",
+ "76": "minecraft:experience_bottle",
+ "77": "minecraft:potion",
+ "78": "minecraft:vex",
+ "79": "minecraft:villager",
+ "80": "minecraft:iron_golem}",
+ "81": "minecraft:vindicator",
+ "82": "minecraft:witch",
+ "83": "minecraft:wither",
+ "84": "minecraft:wither_skeleton",
+ "85": "minecraft:wither_skull",
+ "86": "minecraft:wolf",
+ "87": "minecraft:zombie",
+ "88": "minecraft:zombie_horse",
+ "89": "minecraft:zombie_villager",
+ "90": "minecraft:phantom",
+ "91": "minecraft:lightning_bolt",
+ "92": "minecraft:player",
+ "93": "minecraft:fishing_bobber",
+ "94": "minecraft:trident"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/server.json b/src/main/resources/server.json
index 740fe014..7f5572fd 100644
--- a/src/main/resources/server.json
+++ b/src/main/resources/server.json
@@ -12,5 +12,6 @@
"1.15.2": "https://launcher.mojang.com/v1/objects/bb2b6b1aefcd70dfd1892149ac3a215f6c636b07/server.jar",
"1.16.0": "https://launcher.mojang.com/v1/objects/a0d03225615ba897619220e256a266cb33a44b6b/server.jar",
"1.16.1": "https://launcher.mojang.com/v1/objects/a412fd69db1f81db3f511c1463fd304675244077/server.jar",
- "1.16.2": "https://launcher.mojang.com/v1/objects/c5f6fb23c3876461d46ec380421e42b289789530/server.jar"
+ "1.16.2": "https://launcher.mojang.com/v1/objects/c5f6fb23c3876461d46ec380421e42b289789530/server.jar",
+ "1.16.3": "https://launcher.mojang.com/v1/objects/f02f4473dbf152c23d7d484952121db0b36698cb/server.jar"
}
\ No newline at end of file
diff --git a/src/main/resources/version.json b/src/main/resources/version.json
index 50a52451..385b0710 100644
--- a/src/main/resources/version.json
+++ b/src/main/resources/version.json
@@ -132,7 +132,7 @@
"dataVersion": 2578,
"clientbound": {
"0x20": "chunk_data",
- "0x1E": "chunk_unload",
+ "0x1C": "chunk_unload",
"0x34": "player_position_look",
"0x2B": "player_vehicle_move",
"0x23": "chunk_update_light",