diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b4f8e5da..d460ce6a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
- java-version: '17'
+ java-version: '21'
java-package: jdk+fx
- run: |
mvn package -DskipTests
@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
- java-version: '17'
+ java-version: '21'
java-package: jdk+fx
- run: |
mvn test
diff --git a/README.md b/README.md
index 94b2ffdc..16e2722e 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,8 @@ If you run into any problems, check the [FAQ](https://github.com/mircokroon/mine
### Requirements
-- Java 17 or higher
-- Minecraft version 1.12.2+ // 1.13.2+ // 1.14.1+ // 1.15.2+ // 1.16.2+ // 1.17+ // 1.18+ // 1.19.3+ // 1.20+
+- Java 21 or higher
+- Minecraft version 1.12.2+ // 1.13.2+ // 1.14.1+ // 1.15.2+ // 1.16.2+ // 1.17+ // 1.18+ // 1.19.3+ // 1.20+ // 1.21+
### Command-line
[Download](https://github.com/mircokroon/minecraft-world-downloader/releases/latest/download/world-downloader.jar) the cross-platform `world-downloader.jar` and run it using the command-line:
diff --git a/pom.xml b/pom.xml
index 7147d7ce..860dae32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,8 +9,8 @@
1.0.0.0
jar
- 17
- 17
+ 21
+ 21
UTF-8
@@ -43,7 +43,7 @@
org.apache.maven.plugins
maven-shade-plugin
- 3.3.0
+ 3.5.3
true
diff --git a/src/main/java/config/Version.java b/src/main/java/config/Version.java
index fcea1f89..17081c7d 100644
--- a/src/main/java/config/Version.java
+++ b/src/main/java/config/Version.java
@@ -13,7 +13,8 @@ public enum Version {
V1_19_3(761, 3218),
V1_20(763, 3463),
V1_20_2(764, 3578),
- V1_20_3(765, 3698),
+ V1_20_4(765, 3698),
+ V1_20_6(766, 3839),
ANY(0, 0);
public final int dataVersion;
diff --git a/src/main/java/game/data/LevelData.java b/src/main/java/game/data/LevelData.java
index 8d3c27f1..24480821 100644
--- a/src/main/java/game/data/LevelData.java
+++ b/src/main/java/game/data/LevelData.java
@@ -3,7 +3,6 @@
import static util.ExceptionHandling.attempt;
import config.Config;
-import config.Option;
import config.Version;
import game.data.coordinates.Coordinate3D;
import game.data.coordinates.CoordinateDouble3D;
@@ -224,7 +223,7 @@ private void disableWorldGeneration(CompoundTag data) {
new NamedTag("biome", new StringTag("minecraft:the_void"))
)));
- CompoundTag dimensions = new CompoundTag(worldManager.getDimensionCodec().getDimensions().stream().map(dimension -> {
+ CompoundTag dimensions = new CompoundTag(worldManager.getDimensionRegistry().getDimensions().stream().map(dimension -> {
CompoundTag dim = new CompoundTag();
dim.add("type", new StringTag(dimension.getType()));
dim.add("generator", generator);
diff --git a/src/main/java/game/data/WorldManager.java b/src/main/java/game/data/WorldManager.java
index 6fb0d9cf..da7e0ac4 100644
--- a/src/main/java/game/data/WorldManager.java
+++ b/src/main/java/game/data/WorldManager.java
@@ -2,6 +2,8 @@
import static util.ExceptionHandling.attempt;
+import game.data.chunk.version.Chunk_1_17;
+import game.data.dimension.DimensionType;
import gui.ChunkImageState;
import java.io.File;
import java.io.IOException;
@@ -41,7 +43,7 @@
import game.data.coordinates.CoordinateDim2D;
import game.data.coordinates.CoordinateDouble3D;
import game.data.dimension.Dimension;
-import game.data.dimension.DimensionCodec;
+import game.data.dimension.DimensionRegistry;
import game.data.entity.EntityNames;
import game.data.entity.EntityRegistry;
import game.data.maps.MapRegistry;
@@ -53,7 +55,6 @@
import packets.DataTypeProvider;
import packets.builder.PacketBuilder;
import proxy.PacketInjector;
-import se.llbit.nbt.CompoundTag;
import se.llbit.nbt.Tag;
import util.PathUtils;
@@ -81,7 +82,7 @@ public class WorldManager {
private ContainerManager containerManager;
private CommandBlockManager commandBlockManager;
private VillagerManager villagerManager;
- private DimensionCodec dimensionCodec;
+ private DimensionRegistry dimensionCodec;
private final RenderDistanceExtender renderDistanceExtender;
private BiConsumer playerPosListener;
@@ -89,6 +90,7 @@ public class WorldManager {
private boolean isBelowGround = false;
private double playerRotation = 0;
private Dimension dimension;
+ private DimensionType dimensionType;
private final EntityRegistry entityRegistry;
private final ChunkFactory chunkFactory;
@@ -160,6 +162,11 @@ public void setDimension(Dimension dimension) {
GuiManager.setDimension(this.dimension);
}
+ public void setDimensionType(DimensionType dimensionType) {
+ this.dimensionType = dimensionType;
+ Chunk_1_17.setWorldHeight(dimensionType.getDimensionMinHeight(), dimensionType.getDimensionMaxHeight());
+ }
+
private void saveAndUnloadChunks() {
if (saveService == null) {
return;
@@ -381,20 +388,23 @@ public void touchChunk(ChunkEntities c) {
regions.get(c.getLocation().chunkToDimRegion()).touch();
}
- public DimensionCodec getDimensionCodec() {
+ public DimensionRegistry getDimensionRegistry() {
+ if (dimensionCodec == null) {
+ this.dimensionCodec = DimensionRegistry.empty();
+ }
return dimensionCodec;
}
/**
- * Set the dimension codec, used to store information about the dimensions that this server supports.
+ * Set the dimension registry, used to store information about the dimensions that this server supports.
*/
- public void setDimensionCodec(DimensionCodec codec) {
- dimensionCodec = codec;
+ public void setDimensionRegistry(DimensionRegistry registry) {
+ dimensionCodec = registry;
// We can immediately try to write the dimension data to the proper directory.
try {
Path p = PathUtils.toPath(Config.getWorldOutputDir(), "datapacks", "downloaded", "data");
- if (codec.write(p)) {
+ if (registry.write(p)) {
// we need to copy that pack.mcmeta file from so that Minecraft will recognise the datapack
Path packMeta = PathUtils.toPath(p.getParent().toString(), "pack.mcmeta");
diff --git a/src/main/java/game/data/chunk/ChunkSection.java b/src/main/java/game/data/chunk/ChunkSection.java
index 4ddbb1de..882cfb83 100644
--- a/src/main/java/game/data/chunk/ChunkSection.java
+++ b/src/main/java/game/data/chunk/ChunkSection.java
@@ -1,6 +1,6 @@
package game.data.chunk;
-import game.data.chunk.palette.GlobalPalette;
+import game.data.chunk.palette.BlockRegistry;
import java.util.Arrays;
import org.apache.commons.lang3.mutable.MutableBoolean;
@@ -92,7 +92,7 @@ public byte getY() {
}
public int computeHeight(int x, int z, MutableBoolean foundAir) {
- GlobalPalette globalPalette = GlobalPaletteProvider.getGlobalPalette(getDataVersion());
+ BlockRegistry globalPalette = GlobalPaletteProvider.getGlobalPalette(getDataVersion());
for (int y = 15; y >= 0 ; y--) {
int blockStateId = getNumericBlockStateAt(x, y, z);
diff --git a/src/main/java/game/data/chunk/palette/GlobalPalette.java b/src/main/java/game/data/chunk/palette/BlockRegistry.java
similarity index 87%
rename from src/main/java/game/data/chunk/palette/GlobalPalette.java
rename to src/main/java/game/data/chunk/palette/BlockRegistry.java
index 2e9f5bfd..9bacc43b 100644
--- a/src/main/java/game/data/chunk/palette/GlobalPalette.java
+++ b/src/main/java/game/data/chunk/palette/BlockRegistry.java
@@ -1,162 +1,161 @@
-package game.data.chunk.palette;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonPrimitive;
-import org.apache.commons.lang3.NotImplementedException;
-import se.llbit.nbt.*;
-
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.*;
-
-/**
- * Holds a global palette as introduced in 1.13. These are read from a simple JSON file that is generated by the
- * Minecraft server.jar. More details are in the readme file in the resource folder.
- */
-public class GlobalPalette implements StateProvider {
- private static final HashMap EMPTY_MAP = new HashMap<>();
- private final Map states;
- private final Map nameStates;
- private String version;
-
- /**
- * Instantiate a global palette using the given Minecraft version.
- * @param version the Minecraft version (e.g. 1.12.2), NOT protocol version
- */
- public GlobalPalette(String version) {
- this(GlobalPalette.class.getClassLoader().getResourceAsStream("blocks-" + version + ".json"));
- }
-
- /**
- * Instantiate a global palette using the input stream (to a JSON file).
- */
- public GlobalPalette(InputStream input) {
- this.states = new HashMap<>();
- this.nameStates = new HashMap<>();
-
- // if the file doesn't exist, there is no palette for this version.
- if (input == null) { return; }
-
- JsonResult map = new Gson().fromJson(new InputStreamReader(input), JsonResult.class);
- map.forEach((name, type) -> type.states.forEach(state -> {
- if (state.properties == null) {
- state.properties = EMPTY_MAP;
- }
-
- CompoundTag properties = state.getProperties();
-
- addBlockState(new BlockState(name, state.id, properties));
- }));
- }
-
- public int getRequiredBits() {
- return (int) Math.ceil(Math.log(states.size()) / Math.log(2));
- }
-
- /**
- * Get a block state from a given index. Used to convert packet palettes to the global palette.
- */
- @Override
- public BlockState getState(int key) {
- return states.getOrDefault(key, null);
- }
-
- /**
- * Returns the first state in the palette, used to replace unknown states with air.
- */
- @Override
- public BlockState getDefaultState() {
- return states.values().iterator().next();
- }
-
- @Override
- public BlockState getState(SpecificTag nbt) {
- return nameStates.get(new BlockStateIdentifier(nbt));
- }
-
- @Override
- public int getStateId(SpecificTag nbt) {
- BlockState state = getState(nbt);
- if (state == null) {
- return 0;
- }
- return state.getNumericId();
- }
-
- public void addBlockState(BlockState state) {
- // skip existing states, these should be the same but might have different names
- if (states.containsKey(state.getNumericId())) {
- return;
- }
-
- states.put(state.getNumericId(), state);
- nameStates.put(new BlockStateIdentifier(state.getName(), state.getProperties()), state);
- }
-}
-
-class BlockStateIdentifier {
- String name;
- CompoundTag properties;
-
- public BlockStateIdentifier(SpecificTag t) {
- this(t.get("Name").stringValue(), t.get("Properties").asCompound());
- }
-
- public BlockStateIdentifier(String name, CompoundTag properties) {
- this.name = name;
- this.properties = properties;
-
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- BlockStateIdentifier that = (BlockStateIdentifier) o;
-
- if (!Objects.equals(name, that.name)) return false;
- return Objects.equals(properties, that.properties);
- }
-
- @Override
- public int hashCode() {
- int result = name != null ? name.hashCode() : 0;
- result = 31 * result + (properties != null ? properties.hashCode() : 0);
- return result;
- }
-}
-
-// we need a class to represent this type because of type erasure, otherwise Gson will get angry over casting.
-class JsonResult extends HashMap { }
-
-// additional classes for inside the JsonResult
-class JsonBlockType { ArrayList states; }
-class JsonBlockState {
- int id;
- HashMap properties;
-
- /**
- * Turns properties hashmap into a compoundtag, which lets us correctly handle the block states that depend on
- * properties.
- */
- CompoundTag getProperties() {
- CompoundTag res = new CompoundTag();
- for (Map.Entry entry : properties.entrySet()) {
- JsonPrimitive wrappedVal = entry.getValue();
- SpecificTag value;
-
- if (wrappedVal.isBoolean()) {
- value = new ByteTag(wrappedVal.getAsBoolean() ? 1 : 0);
- } else if (wrappedVal.isNumber()) {
- value = new IntTag(wrappedVal.getAsInt());
- } else {
- value = new StringTag(wrappedVal.getAsString());
- }
-
- res.add(entry.getKey(), value);
-
- }
- return res;
- }
-}
+package game.data.chunk.palette;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonPrimitive;
+import se.llbit.nbt.*;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.*;
+
+/**
+ * Holds a block registry as introduced in 1.13. These are read from a simple JSON file that is generated by the
+ * Minecraft server.jar. More details are in the readme file in the resource folder.
+ */
+public class BlockRegistry implements Registry {
+ private static final HashMap EMPTY_MAP = new HashMap<>();
+ private final Map states;
+ private final Map nameStates;
+ private String version;
+
+ /**
+ * Instantiate a block registry using the given Minecraft version.
+ * @param version the Minecraft version (e.g. 1.12.2), NOT protocol version
+ */
+ public BlockRegistry(String version) {
+ this(BlockRegistry.class.getClassLoader().getResourceAsStream("blocks-" + version + ".json"));
+ }
+
+ /**
+ * Instantiate a block registry using the input stream (to a JSON file).
+ */
+ public BlockRegistry(InputStream input) {
+ this.states = new HashMap<>();
+ this.nameStates = new HashMap<>();
+
+ // if the file doesn't exist, there is no palette for this version.
+ if (input == null) { return; }
+
+ JsonResult map = new Gson().fromJson(new InputStreamReader(input), JsonResult.class);
+ map.forEach((name, type) -> type.states.forEach(state -> {
+ if (state.properties == null) {
+ state.properties = EMPTY_MAP;
+ }
+
+ CompoundTag properties = state.getProperties();
+
+ addBlockState(new BlockState(name, state.id, properties));
+ }));
+ }
+
+ public int getRequiredBits() {
+ return (int) Math.ceil(Math.log(states.size()) / Math.log(2));
+ }
+
+ /**
+ * Get a block state from a given index. Used to convert packet palettes to the block registry.
+ */
+ @Override
+ public BlockState getState(int key) {
+ return states.getOrDefault(key, null);
+ }
+
+ /**
+ * Returns the first state in the palette, used to replace unknown states with air.
+ */
+ @Override
+ public BlockState getDefaultState() {
+ return states.values().iterator().next();
+ }
+
+ @Override
+ public BlockState getState(SpecificTag nbt) {
+ return nameStates.get(new BlockStateIdentifier(nbt));
+ }
+
+ @Override
+ public int getStateId(SpecificTag nbt) {
+ BlockState state = getState(nbt);
+ if (state == null) {
+ return 0;
+ }
+ return state.getNumericId();
+ }
+
+ public void addBlockState(BlockState state) {
+ // skip existing states, these should be the same but might have different names
+ if (states.containsKey(state.getNumericId())) {
+ return;
+ }
+
+ states.put(state.getNumericId(), state);
+ nameStates.put(new BlockStateIdentifier(state.getName(), state.getProperties()), state);
+ }
+}
+
+class BlockStateIdentifier {
+ String name;
+ CompoundTag properties;
+
+ public BlockStateIdentifier(SpecificTag t) {
+ this(t.get("Name").stringValue(), t.get("Properties").asCompound());
+ }
+
+ public BlockStateIdentifier(String name, CompoundTag properties) {
+ this.name = name;
+ this.properties = properties;
+
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ BlockStateIdentifier that = (BlockStateIdentifier) o;
+
+ if (!Objects.equals(name, that.name)) return false;
+ return Objects.equals(properties, that.properties);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (properties != null ? properties.hashCode() : 0);
+ return result;
+ }
+}
+
+// we need a class to represent this type because of type erasure, otherwise Gson will get angry over casting.
+class JsonResult extends HashMap { }
+
+// additional classes for inside the JsonResult
+class JsonBlockType { ArrayList states; }
+class JsonBlockState {
+ int id;
+ HashMap properties;
+
+ /**
+ * Turns properties hashmap into a compoundtag, which lets us correctly handle the block states that depend on
+ * properties.
+ */
+ CompoundTag getProperties() {
+ CompoundTag res = new CompoundTag();
+ for (Map.Entry entry : properties.entrySet()) {
+ JsonPrimitive wrappedVal = entry.getValue();
+ SpecificTag value;
+
+ if (wrappedVal.isBoolean()) {
+ value = new ByteTag(wrappedVal.getAsBoolean() ? 1 : 0);
+ } else if (wrappedVal.isNumber()) {
+ value = new IntTag(wrappedVal.getAsInt());
+ } else {
+ value = new StringTag(wrappedVal.getAsString());
+ }
+
+ res.add(entry.getKey(), value);
+
+ }
+ return res;
+ }
+}
diff --git a/src/main/java/game/data/chunk/palette/BlockState.java b/src/main/java/game/data/chunk/palette/BlockState.java
index 33001828..7d3b2ed0 100644
--- a/src/main/java/game/data/chunk/palette/BlockState.java
+++ b/src/main/java/game/data/chunk/palette/BlockState.java
@@ -11,7 +11,7 @@
import java.util.Map;
/**
- * A block state in the global palette (1.13+).
+ * A block state in the block registry (1.13+).
*/
public class BlockState implements State {
private static final Map transparency;
@@ -48,7 +48,7 @@ public String getProperty(String name) {
return properties.get(name).stringValue();
}
- CompoundTag getProperties() {
+ public CompoundTag getProperties() {
return properties;
}
@@ -118,7 +118,7 @@ public String toString() {
return "BlockState{" +
"name='" + name + '\'' +
", id=" + id +
-// ", properties=" + properties +
+ ", properties=" + properties.toString().replaceAll("[\\s\\n\\r\\t]", " ") +
'}';
}
}
diff --git a/src/main/java/game/data/chunk/palette/GlobalPaletteProvider.java b/src/main/java/game/data/chunk/palette/GlobalPaletteProvider.java
index 35c08c18..e043f02b 100644
--- a/src/main/java/game/data/chunk/palette/GlobalPaletteProvider.java
+++ b/src/main/java/game/data/chunk/palette/GlobalPaletteProvider.java
@@ -11,21 +11,21 @@
import se.llbit.nbt.CompoundTag;
/**
- * This class manages the global palettes. It can hold not only a palette for the current game version, but also for
+ * This class manages the block registries. It can hold not only a palette for the current game version, but also for
* different versions when these are needed to load previously saved chunks.
*/
public final class GlobalPaletteProvider {
private GlobalPaletteProvider() { }
- private static HashMap palettes = new HashMap<>();
+ private static HashMap palettes = new HashMap<>();
private static Queue uninitialised;
/**
- * Retrieves a global palette based on the data version number. If the palette is not already known, it will be
+ * Retrieves a block registry based on the data version number. If the palette is not already known, it will be
* created through requestPalette.
*/
- public static GlobalPalette getGlobalPalette(int dataVersion) {
- GlobalPalette palette = palettes.get(dataVersion);
+ public static BlockRegistry getGlobalPalette(int dataVersion) {
+ BlockRegistry palette = palettes.get(dataVersion);
if (palette == null) {
return requestPalette(dataVersion);
@@ -36,7 +36,7 @@ public static GlobalPalette getGlobalPalette(int dataVersion) {
/**
* If no data version is specified, the current game version is used instead.
*/
- public static GlobalPalette getGlobalPalette() {
+ public static BlockRegistry getGlobalPalette() {
return getGlobalPalette(Config.getDataVersion());
}
@@ -45,10 +45,10 @@ public static GlobalPalette getGlobalPalette() {
* version handler has this value for us. The registry loader will load it either from a previously generated
* report, or it will download the relevant Minecraft version and generate it.
*/
- private static GlobalPalette requestPalette(int dataVersion) {
+ private static BlockRegistry requestPalette(int dataVersion) {
Protocol version = ProtocolVersionHandler.getInstance().getProtocolByDataVersion(dataVersion);
try {
- GlobalPalette p = RegistryLoader.forVersion(version.getVersion()).generateGlobalPalette();
+ BlockRegistry p = RegistryLoader.forVersion(version.getVersion()).generateGlobalPalette();
palettes.put(dataVersion, p);
if (uninitialised != null) {
@@ -64,7 +64,7 @@ private static GlobalPalette requestPalette(int dataVersion) {
}
public static void registerBlock(String name, int id) {
- GlobalPalette palette = palettes.get(Config.getDataVersion());
+ BlockRegistry palette = palettes.get(Config.getDataVersion());
BlockState state = new BlockState(name, id, new CompoundTag());
diff --git a/src/main/java/game/data/chunk/palette/Palette.java b/src/main/java/game/data/chunk/palette/Palette.java
index a7654ebb..4ec9e54a 100644
--- a/src/main/java/game/data/chunk/palette/Palette.java
+++ b/src/main/java/game/data/chunk/palette/Palette.java
@@ -6,6 +6,7 @@
import java.util.List;
import game.data.chunk.ChunkSection;
+import java.util.stream.Stream;
import packets.DataTypeProvider;
import packets.builder.PacketBuilder;
import se.llbit.nbt.ListTag;
@@ -17,26 +18,29 @@
public class Palette {
private int bitsPerBlock;
private int[] palette;
- StateProvider stateProvider;
+ Registry registry;
PaletteType type = PaletteType.BLOCKS;
protected Palette() {
// palette needs initializing
this.palette = new int[1];
- this.stateProvider = GlobalPaletteProvider.getGlobalPalette();
+ this.registry = GlobalPaletteProvider.getGlobalPalette();
+ if (this.registry == null) {
+ System.out.println("No state provider available: GlobalPaletteProvider.getGlobalPalette()");
+ }
}
private Palette(int bitsPerBlock, int[] palette) {
this.bitsPerBlock = bitsPerBlock;
this.palette = palette;
- this.stateProvider = GlobalPaletteProvider.getGlobalPalette();
+ this.registry = GlobalPaletteProvider.getGlobalPalette();
synchronizeBitsPerBlock();
}
Palette(int[] arr) {
this.palette = arr;
this.bitsPerBlock = computeBitsPerBlock(Math.max(0, arr.length - 1));
- this.stateProvider = GlobalPaletteProvider.getGlobalPalette();
+ this.registry = GlobalPaletteProvider.getGlobalPalette();
}
public static Palette biomes(int dataVersion, ListTag palette) {
@@ -47,7 +51,7 @@ public static Palette biomes(int dataVersion, ListTag palette) {
}
public void biomePalette() {
- this.stateProvider = WorldManager.getInstance().getDimensionCodec().getBiomeProvider();
+ this.registry = WorldManager.getInstance().getDimensionRegistry().getBiomeRegistry();
this.type = PaletteType.BIOMES;
}
@@ -84,14 +88,14 @@ public Palette(int dataVersion, ListTag nbt, boolean isBiomePalette) {
if (isBiomePalette) {
this.biomePalette();
} else {
- this.stateProvider = GlobalPaletteProvider.getGlobalPalette(dataVersion);
+ this.registry = GlobalPaletteProvider.getGlobalPalette(dataVersion);
}
this.palette = new int[nbt.size()];
this.bitsPerBlock = computeBitsPerBlock(nbt.size() - 1);
for (int i = 0; i < nbt.size(); i++) {
- int bs = this.stateProvider.getStateId((SpecificTag) nbt.get(i));
+ int bs = this.registry.getStateId((SpecificTag) nbt.get(i));
this.palette[i] = bs;
}
@@ -165,18 +169,18 @@ public boolean isEmpty() {
}
/**
- * Create an NBT version of this palette using the global palette.
+ * Create an NBT version of this palette using the block registry.
*/
public List toNbt() {
List tags = new ArrayList<>();
- if (stateProvider == null) {
- throw new UnsupportedOperationException("Cannot create palette NBT without a global palette.");
+ if (registry == null) {
+ throw new UnsupportedOperationException("Cannot create palette NBT without a block registry.");
}
for (int i : palette) {
- State state = stateProvider.getState(i);
- if (state == null) { state = stateProvider.getDefaultState(); }
+ State state = registry.getState(i);
+ if (state == null) { state = registry.getDefaultState(); }
tags.add(state.toNbt());
@@ -256,5 +260,9 @@ private void resize(ChunkSection section, int oldBpp) {
public int size() {
return palette.length;
}
+
+ public Stream values() {
+ return Arrays.stream(palette).mapToObj(el -> registry.getState(el));
+ }
}
diff --git a/src/main/java/game/data/chunk/palette/StateProvider.java b/src/main/java/game/data/chunk/palette/Registry.java
similarity index 75%
rename from src/main/java/game/data/chunk/palette/StateProvider.java
rename to src/main/java/game/data/chunk/palette/Registry.java
index d96c4cd8..d4978ea0 100644
--- a/src/main/java/game/data/chunk/palette/StateProvider.java
+++ b/src/main/java/game/data/chunk/palette/Registry.java
@@ -1,17 +1,16 @@
-package game.data.chunk.palette;
-
-import se.llbit.nbt.CompoundTag;
-import se.llbit.nbt.SpecificTag;
-
-/**
- * Interface for data source of palettes.
- */
-public interface StateProvider {
- State getState(int i);
-
- State getState(SpecificTag nbt);
-
- int getStateId(SpecificTag nbt);
-
- State getDefaultState();
-}
+package game.data.chunk.palette;
+
+import se.llbit.nbt.SpecificTag;
+
+/**
+ * Interface for data source of palettes.
+ */
+public interface Registry {
+ State getState(int i);
+
+ State getState(SpecificTag nbt);
+
+ int getStateId(SpecificTag nbt);
+
+ State getDefaultState();
+}
diff --git a/src/main/java/game/data/chunk/palette/SingleValuePalette.java b/src/main/java/game/data/chunk/palette/SingleValuePalette.java
index 42660df9..10b38b4a 100644
--- a/src/main/java/game/data/chunk/palette/SingleValuePalette.java
+++ b/src/main/java/game/data/chunk/palette/SingleValuePalette.java
@@ -16,7 +16,7 @@ public SingleValuePalette(PaletteType type, SpecificTag state) {
if (type == PaletteType.BIOMES) {
this.biomePalette();
}
- this.val = this.stateProvider.getStateId(state);
+ this.val = this.registry.getStateId(state);
}
@Override
@@ -62,7 +62,7 @@ public int getBitsPerBlock() {
@Override
public List toNbt() {
- return List.of(stateProvider.getState(val).toNbt());
+ return List.of(registry.getState(val).toNbt());
}
public Palette asNormalPalette() {
diff --git a/src/main/java/game/data/chunk/version/Chunk_1_18.java b/src/main/java/game/data/chunk/version/Chunk_1_18.java
index fa55e2b8..04359af2 100644
--- a/src/main/java/game/data/chunk/version/Chunk_1_18.java
+++ b/src/main/java/game/data/chunk/version/Chunk_1_18.java
@@ -4,7 +4,7 @@
import game.data.chunk.BlockEntityRegistry;
import game.data.chunk.ChunkSection;
import game.data.chunk.palette.BlockState;
-import game.data.chunk.palette.GlobalPalette;
+import game.data.chunk.palette.BlockRegistry;
import game.data.chunk.palette.GlobalPaletteProvider;
import game.data.chunk.palette.Palette;
import game.data.chunk.palette.PaletteType;
@@ -135,7 +135,7 @@ public void readChunkColumn(DataTypeProvider dataProvider) {
protected void findBlockEntities(ChunkSection section, int sectionY) {
BlockEntityRegistry blockEntities = RegistryManager.getInstance().getBlockEntityRegistry();
- GlobalPalette globalPalette = GlobalPaletteProvider.getGlobalPalette(getDataVersion());
+ BlockRegistry globalPalette = GlobalPaletteProvider.getGlobalPalette(getDataVersion());
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
diff --git a/src/main/java/game/data/container/ItemRegistry.java b/src/main/java/game/data/container/ItemRegistry.java
index 0ff07ee6..2bb113bd 100644
--- a/src/main/java/game/data/container/ItemRegistry.java
+++ b/src/main/java/game/data/container/ItemRegistry.java
@@ -2,7 +2,7 @@
import com.google.gson.Gson;
-import game.data.chunk.palette.GlobalPalette;
+import game.data.chunk.palette.BlockRegistry;
import game.data.registries.RegistriesJson;
import java.io.FileInputStream;
@@ -16,7 +16,7 @@ public class ItemRegistry {
private Map nameToId;
public static ItemRegistry fromJson(String version) {
- InputStream x = GlobalPalette.class.getClassLoader().getResourceAsStream("items-" + version + ".json");
+ InputStream x = BlockRegistry.class.getClassLoader().getResourceAsStream("items-" + version + ".json");
if (x == null) {
return null;
}
diff --git a/src/main/java/game/data/dimension/Biome.java b/src/main/java/game/data/dimension/Biome.java
index cd7d7eb3..f7e4301c 100644
--- a/src/main/java/game/data/dimension/Biome.java
+++ b/src/main/java/game/data/dimension/Biome.java
@@ -50,6 +50,17 @@ public void write(Path fromPath) throws IOException {
Files.createDirectories(p.getParent());
Files.write(p, Collections.singleton(properties.json()));
}
+
+ @Override
+ public String toString() {
+ return "Biome{" +
+ "namespace='" + namespace + '\'' +
+ ", path='" + path + '\'' +
+ ", name='" + name + '\'' +
+ ", id=" + id +
+ ", properties=" + properties +
+ '}';
+ }
}
/**
@@ -82,6 +93,10 @@ class BiomeProperties {
BiomeProperties(CompoundTag tag) {
properties = new HashMap<>(defaultProperties);
+ if (tag == null) {
+ return;
+ }
+
for (NamedTag el : tag) {
properties.put(el.name, el.tag);
}
@@ -91,6 +106,6 @@ class BiomeProperties {
* Datapack stores this as a JSON file.
*/
public String json() {
- return DimensionCodec.GSON.toJson(this.properties);
+ return DimensionRegistry.GSON.toJson(this.properties);
}
}
diff --git a/src/main/java/game/data/dimension/BiomeProvider.java b/src/main/java/game/data/dimension/BiomeRegistry.java
similarity index 70%
rename from src/main/java/game/data/dimension/BiomeProvider.java
rename to src/main/java/game/data/dimension/BiomeRegistry.java
index 758dd0f1..d798e348 100644
--- a/src/main/java/game/data/dimension/BiomeProvider.java
+++ b/src/main/java/game/data/dimension/BiomeRegistry.java
@@ -1,25 +1,31 @@
package game.data.dimension;
import game.data.chunk.palette.State;
-import game.data.chunk.palette.StateProvider;
+import game.data.chunk.palette.Registry;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import se.llbit.nbt.SpecificTag;
import se.llbit.nbt.StringTag;
-public class BiomeProvider implements StateProvider {
+public class BiomeRegistry implements Registry {
private final Map biomesFromId;
private final Map biomeIdsFromIdentifier;
- public BiomeProvider(Map biomes) {
- biomesFromId = new HashMap<>();
- biomeIdsFromIdentifier = new HashMap<>();
+ public BiomeRegistry(Map biomes) {
+ this();
- biomes.forEach((name, biome) -> {
- biomesFromId.put(biome.id, new BiomeIdentifier(name));
- biomeIdsFromIdentifier.put(new BiomeIdentifier(name), biome.id);
- });
+ biomes.forEach(this::addBiome);
+ }
+
+ public void addBiome(String name, Biome biome) {
+ biomesFromId.put(biome.id, new BiomeIdentifier(name));
+ biomeIdsFromIdentifier.put(new BiomeIdentifier(name), biome.id);
+ }
+
+ public BiomeRegistry() {
+ this.biomesFromId = new HashMap<>();
+ this.biomeIdsFromIdentifier = new HashMap<>();
}
@Override
@@ -41,6 +47,11 @@ public State getState(SpecificTag nbt) {
public State getDefaultState() {
return biomesFromId.get(0);
}
+
+ @Override
+ public String toString() {
+ return "BiomeRegistry{" + biomesFromId + '}';
+ }
}
class BiomeIdentifier implements State {
diff --git a/src/main/java/game/data/dimension/Dimension.java b/src/main/java/game/data/dimension/Dimension.java
index d2266697..af3701ac 100644
--- a/src/main/java/game/data/dimension/Dimension.java
+++ b/src/main/java/game/data/dimension/Dimension.java
@@ -66,10 +66,10 @@ public static Dimension fromString(String readString) {
case "minecraft:the_nether": return NETHER;
case "minecraft:overworld": return OVERWORLD;
default: {
- DimensionCodec codec = WorldManager.getInstance().getDimensionCodec();
- if (codec == null) { return OVERWORLD; }
+ DimensionRegistry registry = WorldManager.getInstance().getDimensionRegistry();
+ if (registry == null) { return OVERWORLD; }
- Dimension dim = codec.getDimension(readString);
+ Dimension dim = registry.getDimension(readString);
if (dim == null) { return OVERWORLD; }
return dim;
@@ -127,7 +127,7 @@ public void registerType(SpecificTag dimensionNbt) {
}
int hash = dimensionNbt.hashCode();
- DimensionType type = WorldManager.getInstance().getDimensionCodec().getDimensionType(hash);
+ DimensionType type = WorldManager.getInstance().getDimensionRegistry().getDimensionType(hash);
if (type != null) {
this.type = type.getName();
@@ -168,8 +168,7 @@ public String getName() {
* implementation).
*/
public void setType(String dimensionType) {
- DimensionType type =
- WorldManager.getInstance().getDimensionCodec().getDimensionType(dimensionType);
+ DimensionType type = WorldManager.getInstance().getDimensionRegistry().getDimensionType(dimensionType);
if (type == null) {
return;
diff --git a/src/main/java/game/data/dimension/DimensionCodec.java b/src/main/java/game/data/dimension/DimensionRegistry.java
similarity index 63%
rename from src/main/java/game/data/dimension/DimensionCodec.java
rename to src/main/java/game/data/dimension/DimensionRegistry.java
index 5c7ea402..ac9a2c8e 100644
--- a/src/main/java/game/data/dimension/DimensionCodec.java
+++ b/src/main/java/game/data/dimension/DimensionRegistry.java
@@ -1,176 +1,218 @@
-package game.data.dimension;
-
-import com.google.gson.*;
-import game.data.chunk.palette.StateProvider;
-import se.llbit.nbt.*;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Storage for custom dimensions, dimension types and biomes. Default biomes/dimensions are also sent by the server but
- * not stored.
- */
-public class DimensionCodec {
- public static final Gson GSON;
-
- static {
- /*
- * To convert the properties to JSON, we need to register adapters so that GSON knows how to turn our NBT object
- * into desirable JSON.
- */
- GsonBuilder g = new GsonBuilder();
- g.registerTypeAdapter(StringTag.class, (JsonSerializer) (str, x, y) -> new JsonPrimitive(str.value));
- g.registerTypeAdapter(DoubleTag.class, (JsonSerializer) (num, x, y) -> new JsonPrimitive(num.value));
- g.registerTypeAdapter(FloatTag.class, (JsonSerializer) (num, x, y) -> new JsonPrimitive(num.value));
- g.registerTypeAdapter(IntTag.class, (JsonSerializer) (num, x, y) -> new JsonPrimitive(num.value));
- g.registerTypeAdapter(NamedTag.class, (JsonSerializer) (tag, x, ctx) -> ctx.serialize(tag.tag));
- g.registerTypeAdapter(ByteTag.class, (JsonSerializer) (tag, x, ctx) -> ctx.serialize(tag.value == 1));
- g.registerTypeAdapter(CompoundTag.class, (JsonSerializer) (list, x, ctx) -> {
- JsonObject obj = new JsonObject();
-
- for (NamedTag t : list) {
- obj.add(t.name, ctx.serialize(t));
- }
-
- return obj;
- });
- g.registerTypeAdapter(ListTag.class, (JsonSerializer) (list, x, ctx) -> {
- JsonArray arr = new JsonArray();
-
- for (SpecificTag t : list) {
- arr.add(ctx.serialize(t));
- }
-
- return arr;
- });
- GSON = g.create();
- }
-
-
- private BiomeProvider biomeProvider;
-
- private final Map dimensions;
- private final Map dimensionTypesByHash;
- private final Map dimensionTypesByName;
- private final Map biomes;
-
- private DimensionCodec() {
- this.dimensions = new HashMap<>();
- this.dimensionTypesByHash = new HashMap<>();
- this.dimensionTypesByName = new HashMap<>();
- this.biomes = new HashMap<>();
- }
-
- public static DimensionCodec fromNbt(SpecificTag tag) {
- DimensionCodec codec = new DimensionCodec();
-
- codec.readDimensionTypes(tag.get("minecraft:dimension_type").asCompound().get("value").asList());
- codec.readBiomes(tag.get("minecraft:worldgen/biome").asCompound().get("value").asList());
-
- return codec;
- }
-
- public DimensionCodec setDimensionNames(String[] dimensionNames) {
- this.readDimensions(dimensionNames);
- return this;
- }
-
- public Collection getDimensions() {
- return dimensions.values();
- }
-
- private void readDimensions(String[] dimensionNames) {
- for (String dimensionName : dimensionNames) {
- String[] parts = dimensionName.split(":");
- String namespace = parts[0];
- String name = parts[1];
-
- this.dimensions.put(dimensionName, new Dimension(namespace, name));
- }
- }
-
- /**
- * Dimension types. These are not very useful currently as the server does not actually let the client know which
- * dimensions have which types.
- */
- private void readDimensionTypes(ListTag dimensionList) {
- for (SpecificTag dim : dimensionList) {
- CompoundTag d = dim.asCompound();
-
- String identifier = ((StringTag) d.get("name")).value;
- String[] parts = identifier.split(":");
- String namespace = parts[0];
- String name = parts[1];
-
- DimensionType type = new DimensionType(namespace, name, d);
- this.dimensionTypesByHash.put(type.getSignature(), type);
- this.dimensionTypesByName.put(type.getName(), type);
- }
- }
-
- /**
- * Biome registry.
- */
- private void readBiomes(ListTag biomeList) {
- for (SpecificTag biome : biomeList) {
- CompoundTag b = biome.asCompound();
-
- String identifier = ((StringTag) b.get("name")).value;
- int id = ((IntTag) b.get("id")).value;
- String[] parts = identifier.split(":");
- String namespace = parts[0];
- String name = parts[1];
-
- this.biomes.put(identifier, new Biome(namespace, name, id, b.get("element").asCompound()));
- }
- this.biomeProvider = new BiomeProvider(this.biomes);
- }
-
- public Dimension getDimension(String name) {
- return dimensions.get(name);
- }
-
- /**
- * Get a dimension by it's signature. The signature is just the hash of the properties.
- */
- public DimensionType getDimensionType(int signature) {
- return dimensionTypesByHash.get(signature);
- }
-
- public DimensionType getDimensionType(String name) {
- return dimensionTypesByName.get(name);
- }
-
- /**
- * Write all the custom dimension data, if there is any.
- */
- public boolean write(Path destination) throws IOException {
- if (biomes.isEmpty() && dimensionTypesByHash.isEmpty()) {
- // nothing to write
- return false;
- }
-
- for (Dimension d : dimensions.values()) {
- d.write(destination);
- }
-
- for (DimensionType d : dimensionTypesByHash.values()) {
- d.write(destination);
- }
-
- for (Biome b : biomes.values()) {
- b.write(destination);
- }
-
-
- return true;
- }
-
- public StateProvider getBiomeProvider() {
- return biomeProvider;
- }
-}
+package game.data.dimension;
+
+import com.google.gson.*;
+import config.Config;
+import game.data.chunk.palette.Registry;
+import java.util.List;
+import packets.DataTypeProvider;
+import se.llbit.nbt.*;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Storage for custom dimensions, dimension types and biomes. Default biomes/dimensions are also sent by the server but
+ * not stored.
+ */
+public class DimensionRegistry {
+ public static final Gson GSON;
+
+ static {
+ /*
+ * To convert the properties to JSON, we need to register adapters so that GSON knows how to turn our NBT object
+ * into desirable JSON.
+ */
+ GsonBuilder g = new GsonBuilder();
+ g.registerTypeAdapter(StringTag.class, (JsonSerializer) (str, x, y) -> new JsonPrimitive(str.value));
+ g.registerTypeAdapter(DoubleTag.class, (JsonSerializer) (num, x, y) -> new JsonPrimitive(num.value));
+ g.registerTypeAdapter(FloatTag.class, (JsonSerializer) (num, x, y) -> new JsonPrimitive(num.value));
+ g.registerTypeAdapter(IntTag.class, (JsonSerializer) (num, x, y) -> new JsonPrimitive(num.value));
+ g.registerTypeAdapter(NamedTag.class, (JsonSerializer) (tag, x, ctx) -> ctx.serialize(tag.tag));
+ g.registerTypeAdapter(ByteTag.class, (JsonSerializer) (tag, x, ctx) -> ctx.serialize(tag.value == 1));
+ g.registerTypeAdapter(CompoundTag.class, (JsonSerializer) (list, x, ctx) -> {
+ JsonObject obj = new JsonObject();
+
+ for (NamedTag t : list) {
+ obj.add(t.name, ctx.serialize(t));
+ }
+
+ return obj;
+ });
+ g.registerTypeAdapter(ListTag.class, (JsonSerializer) (list, x, ctx) -> {
+ JsonArray arr = new JsonArray();
+
+ for (SpecificTag t : list) {
+ arr.add(ctx.serialize(t));
+ }
+
+ return arr;
+ });
+ GSON = g.create();
+ }
+
+
+ private BiomeRegistry biomeRegistry;
+
+ private final Map dimensions;
+ private final Map dimensionTypesByID;
+ private final Map dimensionTypesByName;
+ private final Map biomes;
+
+ private DimensionRegistry() {
+ this.dimensions = new HashMap<>();
+ this.dimensionTypesByName = new HashMap<>();
+ this.dimensionTypesByID = new HashMap<>();
+ this.biomes = new HashMap<>();
+
+ this.dimensions.put("minecraft:overworld", Dimension.OVERWORLD);
+ this.dimensions.put("minecraft:the_nether", Dimension.NETHER);
+ this.dimensions.put("minecraft:the_end", Dimension.END);
+ }
+
+ public static DimensionRegistry empty() {
+ return new DimensionRegistry();
+ }
+
+ public static DimensionRegistry fromNbt(SpecificTag tag) {
+ DimensionRegistry codec = new DimensionRegistry();
+
+ codec.readDimensionTypes(tag.get("minecraft:dimension_type").asCompound().get("value").asList());
+ codec.readBiomes(tag.get("minecraft:worldgen/biome").asCompound().get("value").asList());
+
+ return codec;
+ }
+
+ public void setDimensionNames(String[] dimensionNames) {
+ this.readDimensions(dimensionNames);
+ }
+
+ public Collection getDimensions() {
+ return dimensions.values();
+ }
+
+ private void readDimensions(String[] dimensionNames) {
+ for (String dimensionName : dimensionNames) {
+ String[] parts = dimensionName.split(":");
+ String namespace = parts[0];
+ String name = parts[1];
+
+ this.dimensions.put(dimensionName, new Dimension(namespace, name));
+ }
+ }
+
+ /**
+ * Dimension types. These are not very useful currently as the server does not actually let the client know which
+ * dimensions have which types.
+ */
+ private void readDimensionTypes(ListTag dimensionList) {
+ for (SpecificTag dim : dimensionList) {
+ CompoundTag d = dim.asCompound();
+
+ String identifier = ((StringTag) d.get("name")).value;
+ String[] parts = identifier.split(":");
+ String namespace = parts[0];
+ String name = parts[1];
+
+ DimensionType type = new DimensionType(namespace, name, d);
+ this.dimensionTypesByID.put(type.getSignature(), type);
+ this.dimensionTypesByName.put(type.getName(), type);
+ }
+ }
+
+ /**
+ * Biome registry.
+ */
+ private void readBiomes(ListTag biomeList) {
+ for (SpecificTag biome : biomeList) {
+ CompoundTag b = biome.asCompound();
+
+ String identifier = ((StringTag) b.get("name")).value;
+ int id = ((IntTag) b.get("id")).value;
+ String[] parts = identifier.split(":");
+ String namespace = parts[0];
+ String name = parts[1];
+
+ this.biomes.put(identifier, new Biome(namespace, name, id, b.get("element").asCompound()));
+ }
+ this.biomeRegistry = new BiomeRegistry(this.biomes);
+ }
+
+ public Dimension getDimension(String name) {
+ return dimensions.get(name);
+ }
+
+ /**
+ * Get a dimension by it's ID. Pre-1.19 the ID is the hash of the properties.
+ */
+ public DimensionType getDimensionType(int id) {
+ return dimensionTypesByID.get(id);
+ }
+
+ public DimensionType getDimensionType(String id) {
+ return dimensionTypesByName.get(id);
+ }
+
+ /**
+ * Write all the custom dimension data, if there is any.
+ */
+ public boolean write(Path destination) throws IOException {
+ if (biomes.isEmpty() && dimensionTypesByID.isEmpty()) {
+ // nothing to write
+ return false;
+ }
+
+ for (Dimension d : dimensions.values()) {
+ d.write(destination);
+ }
+
+ for (DimensionType d : dimensionTypesByID.values()) {
+ d.write(destination);
+ }
+
+ for (Biome b : biomes.values()) {
+ b.write(destination);
+ }
+
+
+ return true;
+ }
+
+ public Registry getBiomeRegistry() {
+ return biomeRegistry;
+ }
+
+ public void loadBiomes(DataTypeProvider.Registry registry) {
+ this.biomeRegistry = new BiomeRegistry();
+
+ List entries = registry.entries();
+ for (int id = 0; id < entries.size(); id++) {
+ var biome = entries.get(id);
+
+ String[] parts = biome.name().split(":");
+ String namespace = parts[0];
+ String name = parts[1];
+
+ var res = new Biome(namespace, name, id, biome.nbt().map(Tag::asCompound).orElse(null));
+ this.biomes.put(biome.name(), res);
+ this.biomeRegistry.addBiome(biome.name(), res);
+ }
+ }
+
+ public void loadDimensions(DataTypeProvider.Registry registry) {
+ List entries = registry.entries();
+ for (int id = 0; id < entries.size(); id++) {
+ var dim = entries.get(id);
+
+ String[] parts = dim.name().split(":");
+ String namespace = parts[0];
+ String name = parts[1];
+
+ DimensionType type = new DimensionType(namespace, name, id, dim.nbt().map(Tag::asCompound).orElse(null));
+ this.dimensionTypesByID.put(id, type);
+ this.dimensionTypesByName.put(type.getName(), type);
+ }
+ }
+}
diff --git a/src/main/java/game/data/dimension/DimensionType.java b/src/main/java/game/data/dimension/DimensionType.java
index 2c3bc456..5332f300 100644
--- a/src/main/java/game/data/dimension/DimensionType.java
+++ b/src/main/java/game/data/dimension/DimensionType.java
@@ -15,6 +15,18 @@
* lava flows.
*/
public class DimensionType {
+ private static final CompoundTag PROPERTIES_OVERWORLD_DEFAULT;
+ private static final CompoundTag PROPERTIES_DEFAULT;
+
+ static {
+ PROPERTIES_OVERWORLD_DEFAULT = new CompoundTag();
+ PROPERTIES_OVERWORLD_DEFAULT.add("min_y", new IntTag(-64));
+ PROPERTIES_OVERWORLD_DEFAULT.add("height", new IntTag(384));
+
+ PROPERTIES_DEFAULT = new CompoundTag();
+ PROPERTIES_DEFAULT.add("min_y", new IntTag(0));
+ PROPERTIES_DEFAULT.add("height", new IntTag(256));
+ }
private final String namespace;
private final String name;
@@ -22,12 +34,60 @@ public class DimensionType {
private final int signature;
private CompoundTag properties;
+ // default values
+ private int dimensionMinHeight = 0;
+ private int dimensionMaxHeight = 256;
+
+ // after 1.20.6
+ DimensionType(String namespace, String name, int id, CompoundTag properties) {
+ this.properties = properties;
+ this.index = id;
+ this.namespace = namespace;
+ this.name = name;
+ this.signature = this.properties == null ? 0 : this.properties.hashCode();
+
+ if (properties == null) {
+ setPropertiesFromName();
+ }
+
+ parseHeights();
+ }
+
+ // before 1.20.6
DimensionType(String namespace, String name, CompoundTag tag) {
this.properties = (CompoundTag) tag.get("element");
this.index = ((IntTag) tag.get("id")).value;
this.namespace = namespace;
this.name = name;
this.signature = this.properties.hashCode();
+
+ parseHeights();
+ }
+
+ private void setPropertiesFromName() {
+ if (!this.namespace.equals("minecraft")) { return; }
+
+ if (this.name.equals("overworld")) {
+ this.properties = PROPERTIES_OVERWORLD_DEFAULT;
+ } else {
+ this.properties = PROPERTIES_DEFAULT;
+ }
+ }
+
+ private void parseHeights() {
+ if (this.properties == null) {
+ return;
+ }
+
+ var minTag = this.properties.get("min_y");
+ var heightTag = this.properties.get("height");
+
+ if (minTag.isError() || heightTag.isError()) {
+ return;
+ }
+
+ this.dimensionMinHeight = minTag.intValue();
+ this.dimensionMaxHeight = heightTag.intValue();
}
public CompoundTag getProperties() {
@@ -50,7 +110,7 @@ public void write(Path prefix) throws IOException {
Path destination = PathUtils.toPath(prefix.toString(), namespace, "dimension_type", name + ".json");
Files.createDirectories(destination.getParent());
- Files.write(destination, Collections.singleton(DimensionCodec.GSON.toJson(properties)));
+ Files.write(destination, Collections.singleton(DimensionRegistry.GSON.toJson(properties)));
}
/**
@@ -59,4 +119,12 @@ public void write(Path prefix) throws IOException {
public String getName() {
return namespace + ":" + name;
}
+
+ public int getDimensionMinHeight() {
+ return dimensionMinHeight;
+ }
+
+ public int getDimensionMaxHeight() {
+ return dimensionMaxHeight;
+ }
}
diff --git a/src/main/java/game/data/entity/EntityNames.java b/src/main/java/game/data/entity/EntityNames.java
index 4c2ab6f7..6834b933 100644
--- a/src/main/java/game/data/entity/EntityNames.java
+++ b/src/main/java/game/data/entity/EntityNames.java
@@ -49,7 +49,7 @@ public static EntityNames fromRegistry(InputStream input) {
}
/**
- * Get a block state from a given index. Used to convert packet palettes to the global palette.
+ * Get a block state from a given index. Used to convert packet palettes to the block registry.
*/
public String getName(int key) {
return entities.getOrDefault(key, null);
diff --git a/src/main/java/game/data/registries/RegistryLoader.java b/src/main/java/game/data/registries/RegistryLoader.java
index 8309d6ef..29b4db1f 100644
--- a/src/main/java/game/data/registries/RegistryLoader.java
+++ b/src/main/java/game/data/registries/RegistryLoader.java
@@ -10,6 +10,7 @@
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -19,7 +20,7 @@
import config.Config;
import game.UnsupportedMinecraftVersionException;
import game.data.chunk.BlockEntityRegistry;
-import game.data.chunk.palette.GlobalPalette;
+import game.data.chunk.palette.BlockRegistry;
import game.data.container.ItemRegistry;
import game.data.container.MenuRegistry;
import game.data.entity.EntityNames;
@@ -130,13 +131,14 @@ private void generateReports() throws IOException, InterruptedException {
System.out.println("Starting output of Minecraft server.jar:");
ProcessBuilder pb;
+ String javaRuntime = Paths.get(System.getProperty("java.home"), "bin", "java").toString();
if (Config.versionReporter().isAtLeast(Version.V1_18)) {
pb = new ProcessBuilder(
- "java", "-DbundlerMainClass=net.minecraft.data.Main", "-jar", "server.jar", "--reports"
+ javaRuntime, "-DbundlerMainClass=net.minecraft.data.Main", "-jar", "server.jar", "--reports"
);
} else {
pb = new ProcessBuilder(
- "java", "-cp", "server.jar", "net.minecraft.data.Main", "--reports"
+ javaRuntime, "-cp", "server.jar", "net.minecraft.data.Main", "--reports"
);
}
@@ -200,11 +202,11 @@ public EntityNames generateEntityNames() throws IOException {
}
}
- public GlobalPalette generateGlobalPalette() throws IOException {
+ public BlockRegistry generateGlobalPalette() throws IOException {
if (versionSupportsBlockGenerator()) {
- return new GlobalPalette(new FileInputStream(blocksPath.toFile()));
+ return new BlockRegistry(new FileInputStream(blocksPath.toFile()));
} else {
- return new GlobalPalette("1.12.2");
+ return new BlockRegistry("1.12.2");
}
}
diff --git a/src/main/java/game/data/registries/modded/ForgeRegistryHandler.java b/src/main/java/game/data/registries/modded/ForgeRegistryHandler.java
index 5a9ad6cd..ba6ffaee 100644
--- a/src/main/java/game/data/registries/modded/ForgeRegistryHandler.java
+++ b/src/main/java/game/data/registries/modded/ForgeRegistryHandler.java
@@ -146,11 +146,11 @@ public Tag toNbt() {
}
/**
- * Add blocks to the global palette
+ * Add blocks to the block registry
*/
public void registerBlocks() {
// since blockstates in 1.12.2 have half a byte of data at the end, we need to shift the
- // blockstates we register to the global palette for the minimap to remain correct(ish)
+ // blockstates we register to the block registry for the minimap to remain correct(ish)
ids.forEach(pair -> GlobalPaletteProvider.registerBlock(pair.getKey(), pair.getValue() << 4));
}
}
diff --git a/src/main/java/game/protocol/ConfigurationProtocol.java b/src/main/java/game/protocol/ConfigurationProtocol.java
index 2bbfa1dd..e09529c8 100644
--- a/src/main/java/game/protocol/ConfigurationProtocol.java
+++ b/src/main/java/game/protocol/ConfigurationProtocol.java
@@ -14,9 +14,11 @@ public ConfigurationProtocol() {
clientBound = new HashMap<>();
serverBound = new HashMap<>();
- if (Config.versionReporter().isAtLeast(Version.V1_20_2)) {
+ if (Config.versionReporter().isAtLeast(Version.V1_20_6)) {
+ serverBound.put(0x03, "FinishConfiguration");
+ clientBound.put(0x07, "RegistryData");
+ } else if (Config.versionReporter().isAtLeast(Version.V1_20_2)) {
serverBound.put(0x02, "FinishConfiguration");
-
clientBound.put(0x05, "RegistryData");
} else {
serverBound.put(0x03, "FinishConfiguration");
diff --git a/src/main/java/game/protocol/LoginProtocol.java b/src/main/java/game/protocol/LoginProtocol.java
index ccf0e144..1117457c 100644
--- a/src/main/java/game/protocol/LoginProtocol.java
+++ b/src/main/java/game/protocol/LoginProtocol.java
@@ -11,14 +11,15 @@ public LoginProtocol() {
clientBound = new HashMap<>();
serverBound = new HashMap<>();
- clientBound.put(0x00, "disconnect");
- clientBound.put(0x01, "encryption_request");
- clientBound.put(0x02, "login_success");
- clientBound.put(0x03, "set_compression");
+ clientBound.put(0x00, "LoginDisconnect");
+ clientBound.put(0x01, "Hello");
+ clientBound.put(0x02, "GameProfile");
+ clientBound.put(0x03, "LoginCompression");
- serverBound.put(0x00, "login_start");
- serverBound.put(0x01, "encryption_response");
- serverBound.put(0x02, "login_plugin_response");
+ serverBound.put(0x00, "Hello");
+ serverBound.put(0x01, "Key");
+ serverBound.put(0x02, "CustomQueryAnswer");
+ serverBound.put(0x03, "LoginAcknowledged");
}
@Override
diff --git a/src/main/java/gui/AuthTabController.java b/src/main/java/gui/AuthTabController.java
index 1d9acd46..e550ebbd 100644
--- a/src/main/java/gui/AuthTabController.java
+++ b/src/main/java/gui/AuthTabController.java
@@ -89,7 +89,7 @@ private void clearAuthenticationStatus() {
}
public void checkButtonPressed(ActionEvent actionEvent) {
- AuthDetailsManager.validateAuthStatus(this::setStatusText, this::setStatusError);
+ AuthDetailsManager.validateAuthStatus(username -> setStatusText("Valid session found! \n\nUsername: " + username), this::setStatusError);
clearAuthenticationStatus();
}
diff --git a/src/main/java/gui/GuiMap.java b/src/main/java/gui/GuiMap.java
index 9777ee3d..2ad646c9 100644
--- a/src/main/java/gui/GuiMap.java
+++ b/src/main/java/gui/GuiMap.java
@@ -358,6 +358,10 @@ private void drawEntities() {
* If the name of the player is not known it's first requested from the Mojang API.
*/
private void drawOtherPlayer(GraphicsContext graphics, PlayerEntity player) {
+ if (player.getPosition() == null) {
+ return;
+ }
+
double playerX = ((player.getPosition().getX() - bounds.getMinX()) / blocksPerPixel);
double playerZ = ((player.getPosition().getZ() - bounds.getMinZ()) / blocksPerPixel);
if (mouseOver && isNear(playerX, playerZ)) {
diff --git a/src/main/java/gui/GuiSettings.java b/src/main/java/gui/GuiSettings.java
index fc93c341..75d3b14c 100644
--- a/src/main/java/gui/GuiSettings.java
+++ b/src/main/java/gui/GuiSettings.java
@@ -1,6 +1,7 @@
package gui;
+import static util.ExceptionHandling.attempt;
import static util.ExceptionHandling.attemptQuiet;
import config.Config;
@@ -14,6 +15,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -31,6 +33,14 @@
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.paint.Paint;
+import org.apache.commons.lang3.SystemUtils;
+import proxy.auth.AuthDetailsManager;
+import proxy.auth.AuthenticationMethod;
+import proxy.auth.MicrosoftAuthHandler;
+import proxy.auth.MicrosoftAuthServer;
import util.PathUtils;
public class GuiSettings {
@@ -54,7 +64,7 @@ public class GuiSettings {
public Slider extendedDistance;
public IntField extendedDistanceText;
public Hyperlink openWorldDir;
- public Hyperlink verifyAuthLink;
+ public Hyperlink projectSupportLink;
public CheckBox renderOtherPlayers;
public CheckBox enableInfoMessages;
public Tab generalTab;
@@ -64,6 +74,12 @@ public class GuiSettings {
public AuthTabController authController;
public CheckBox enableDrawExtendedChunks;
public CheckBox enableCaveRenderMode;
+ public Label authResult;
+
+
+
+
+ private MicrosoftAuthServer authServer;
Config config;
private boolean portInUse;
@@ -142,13 +158,14 @@ void initialize() {
}
}));
- verifyAuthLink.setOnAction(e -> tabPane.getSelectionModel().select(authTab));
+ projectSupportLink.setOnAction(actionEvent -> GuiManager.openWebLink("https://github.com/sponsors/mircokroon?frequency=one-time&amount=5"));
handleDataValidation();
handleErrorTab();
handleResizing();
resetHeight();
+ validateAuth();
}
private void resetHeight() {
@@ -331,4 +348,39 @@ public void setSelectedIp(String address) {
tabPane.getSelectionModel().selectFirst();
this.saveButton.requestFocus();
}
+
+ public void msAuthPressed(ActionEvent actionEvent) {
+ if (!SystemUtils.IS_OS_WINDOWS && !SystemUtils.IS_OS_MAC) {
+ tabPane.getSelectionModel().select(authTab);
+ return;
+ }
+ Config.setAuthMethod(AuthenticationMethod.MICROSOFT);
+ Consumer onStart = shortUrl -> GuiManager.openWebLink(shortUrl);
+
+
+ // server already running
+ if (authServer != null) {
+ onStart.accept(authServer.getShortUrl());
+ return;
+ }
+
+ attempt(() -> {
+ authServer = new MicrosoftAuthServer(onStart, (authCode, usedPort) -> Platform.runLater(() -> {
+ Config.setMicrosoftAuth(MicrosoftAuthHandler.fromCode(authCode, usedPort));
+ authServer = null;
+
+ validateAuth();
+ }));
+ });
+ }
+
+ private void validateAuth() {
+ AuthDetailsManager.validateAuthStatus(username -> {
+ authResult.setText("Username: " + username);
+ authResult.getStyleClass().remove("label-err");
+ }, str -> {
+ authResult.setText("Not logged in");
+ authResult.getStyleClass().add("label-err");
+ });
+ }
}
diff --git a/src/main/java/packets/DataTypeProvider.java b/src/main/java/packets/DataTypeProvider.java
index 2a5cd7d0..210333c6 100644
--- a/src/main/java/packets/DataTypeProvider.java
+++ b/src/main/java/packets/DataTypeProvider.java
@@ -7,9 +7,12 @@
import game.data.container.Slot;
import game.data.container.Slot_1_12;
import game.data.coordinates.CoordinateDouble3D;
+import java.util.Optional;
+import java.util.function.Supplier;
import packets.version.DataTypeProvider_1_13;
import packets.version.DataTypeProvider_1_14;
import packets.version.DataTypeProvider_1_20_2;
+import packets.version.DataTypeProvider_1_20_6;
import se.llbit.nbt.NamedTag;
import se.llbit.nbt.SpecificTag;
@@ -21,6 +24,7 @@
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
+import se.llbit.nbt.Tag;
/**
* Class to provide an interface between the raw byte data and the various data types. Most methods are
@@ -31,6 +35,21 @@ public class DataTypeProvider {
private byte[] finalFullPacket;
private int pos;
+ public byte[] debug__getFullArray() {
+ return finalFullPacket;
+ }
+ public String debug__readableString() {
+ char[] out = new char[finalFullPacket.length];
+ for (int i = 0; i < finalFullPacket.length; i++) {
+ byte b = finalFullPacket[i];
+ if (b >= 32) {
+ out[i] = (char) b;
+ } else {
+ out[i] = '_';
+ }
+ }
+ return new String(out);
+ }
public DataTypeProvider(byte[] finalFullPacket) {
this.finalFullPacket = finalFullPacket;
this.pos = 0;
@@ -38,6 +57,7 @@ public DataTypeProvider(byte[] finalFullPacket) {
public static DataTypeProvider ofPacket(byte[] finalFullPacket) {
return Config.versionReporter().select(DataTypeProvider.class,
+ Option.of(Version.V1_20_6, () -> new DataTypeProvider_1_20_6(finalFullPacket)),
Option.of(Version.V1_20_2, () -> new DataTypeProvider_1_20_2(finalFullPacket)),
Option.of(Version.V1_14, () -> new DataTypeProvider_1_14(finalFullPacket)),
Option.of(Version.V1_13, () -> new DataTypeProvider_1_13(finalFullPacket)),
@@ -295,6 +315,30 @@ public DataTypeProvider copy() {
return new DataTypeProvider(Arrays.copyOf(this.finalFullPacket, this.finalFullPacket.length));
}
+ public record Registry(String name, List entries) {}
+ public record RegistryEntry(String name, Optional nbt) {}
+ public Registry readRegistry() {
+ String name = readString();
+ int numEntries = readVarInt();
+
+ List entries = new ArrayList<>(numEntries);
+ for (int i = 0; i < numEntries; i++) {
+ String identifier = readString();
+
+ Optional b = readOptional(this::readNbtTag);
+ entries.add(new RegistryEntry(identifier, b));
+ }
+
+ return new Registry(name, entries);
+ }
+
+ public Optional readOptional(Supplier provider) {
+ if (readBoolean()) {
+ return Optional.of(provider.get());
+ }
+ return Optional.empty();
+ }
+
public int remaining() {
return this.finalFullPacket.length - pos;
}
diff --git a/src/main/java/packets/builder/Chat.java b/src/main/java/packets/builder/Chat.java
index 7baa77e2..4ee932f9 100644
--- a/src/main/java/packets/builder/Chat.java
+++ b/src/main/java/packets/builder/Chat.java
@@ -4,6 +4,8 @@
import java.util.ArrayList;
import java.util.List;
+import se.llbit.nbt.SpecificTag;
+import se.llbit.nbt.StringTag;
/**
* Chat object, can be sent to the client to display messages.
@@ -34,4 +36,8 @@ public void addChild(Chat c) {
public String toJson() {
return new Gson().toJson(this);
}
+
+ public SpecificTag toNbt() {
+ return new StringTag(text);
+ }
}
diff --git a/src/main/java/packets/builder/MessageTarget.java b/src/main/java/packets/builder/MessageTarget.java
index e0f4c930..32cfd62f 100644
--- a/src/main/java/packets/builder/MessageTarget.java
+++ b/src/main/java/packets/builder/MessageTarget.java
@@ -9,12 +9,11 @@ public enum MessageTarget {
GAMEINFO;
byte getIdentifier() {
- switch (this) {
- case CHAT: return 0;
- case SYSTEM: return 1;
- case GAMEINFO: return 2;
- }
- return 0;
+ return switch (this) {
+ case CHAT -> 0;
+ case SYSTEM -> 1;
+ case GAMEINFO -> 2;
+ };
}
}
diff --git a/src/main/java/packets/builder/PacketBuilder.java b/src/main/java/packets/builder/PacketBuilder.java
index c7ba2c83..4fdf602d 100644
--- a/src/main/java/packets/builder/PacketBuilder.java
+++ b/src/main/java/packets/builder/PacketBuilder.java
@@ -1,10 +1,16 @@
package packets.builder;
import config.Config;
+import config.Option;
import config.Version;
import game.protocol.Protocol;
+import java.util.Arrays;
import packets.DataTypeProvider;
import packets.UUID;
+import packets.handler.ClientBoundConfigurationPacketHandler;
+import packets.handler.PacketHandler;
+import packets.handler.version.ClientBoundConfigurationPacketHandler_1_20_2;
+import packets.handler.version.ClientBoundConfigurationPacketHandler_1_20_6;
import packets.lib.ByteQueue;
import proxy.CompressionManager;
import se.llbit.nbt.NamedTag;
@@ -48,16 +54,30 @@ public void copy(DataTypeProvider provider, NetworkType... types) {
public static PacketBuilder constructClientMessage(String message, MessageTarget target) {
return constructClientMessage(new Chat(message), target);
}
+
/**
* Construct a message packet for the client.
*/
public static PacketBuilder constructClientMessage(Chat message, MessageTarget target) {
Protocol protocol = Config.versionReporter().getProtocol();
- String packetName = Config.versionReporter().isAtLeast(Version.V1_19) ? "SystemChat" : "Chat";
+ String packetName = Config.versionReporter().select(String.class,
+ Option.of(Version.V1_20_6, () -> "SetActionBarText"),
+ Option.of(Version.V1_19, () -> "SystemChat"),
+ Option.of(Version.ANY, () -> "Chat")
+ );
+
PacketBuilder builder = new PacketBuilder(protocol.clientBound(packetName));
- builder.writeString(message.toJson());
- builder.writeByte(target.getIdentifier());
+ if (Config.versionReporter().isAtLeast(Version.V1_20_6)) {
+ builder.writeNbtDirect(message.toNbt());
+ } else {
+ builder.writeString(message.toJson());
+ }
+
+ // 1.20.6 has separate packet for action bar text, so dont need to give identifier anymore
+ if (!Config.versionReporter().isAtLeast(Version.V1_20_6)) {
+ builder.writeByte(target.getIdentifier());
+ }
// uuid is only included from 1.16, from 1.19 player chat is a different packet
if (Config.versionReporter().isAtLeast(Version.V1_16) && !Config.versionReporter().isAtLeast(Version.V1_19)) {
@@ -199,7 +219,6 @@ public void writeBoolean(boolean val) {
/**
* Writes an NBT tag. We need to wrap this in a NamedTag, as the named tag is not written itself.
- * TODO: update on 1.20.2
*/
public void writeNbt(SpecificTag nbt) {
try {
@@ -214,6 +233,21 @@ public void write(int b) {
}
}
+ // write NBT without wrapping it in a NamedTag
+ public void writeNbtDirect(SpecificTag nbt) {
+ try {
+ writeByte((byte) nbt.tagType());
+ nbt.write(new DataOutputStream(new OutputStream() {
+ @Override
+ public void write(int b) {
+ bytes.insert((byte) b);
+ }
+ }));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
public void writeByte(byte b) {
bytes.insert(b);
diff --git a/src/main/java/packets/handler/ClientBoundConfigurationPacketHandler.java b/src/main/java/packets/handler/ClientBoundConfigurationPacketHandler.java
index 2166ae5c..01a5d7f1 100644
--- a/src/main/java/packets/handler/ClientBoundConfigurationPacketHandler.java
+++ b/src/main/java/packets/handler/ClientBoundConfigurationPacketHandler.java
@@ -4,6 +4,7 @@
import config.Option;
import config.Version;
import packets.handler.version.ClientBoundConfigurationPacketHandler_1_20_2;
+import packets.handler.version.ClientBoundConfigurationPacketHandler_1_20_6;
import proxy.ConnectionManager;
import java.util.HashMap;
@@ -18,6 +19,7 @@ public ClientBoundConfigurationPacketHandler(ConnectionManager connectionManager
public static PacketHandler of(ConnectionManager connectionManager) {
return Config.versionReporter().select(PacketHandler.class,
+ Option.of(Version.V1_20_6, () -> new ClientBoundConfigurationPacketHandler_1_20_6(connectionManager)),
Option.of(Version.V1_20_2, () -> new ClientBoundConfigurationPacketHandler_1_20_2(connectionManager)),
Option.of(Version.ANY, () -> new ClientBoundConfigurationPacketHandler(connectionManager))
);
diff --git a/src/main/java/packets/handler/ClientBoundGamePacketHandler.java b/src/main/java/packets/handler/ClientBoundGamePacketHandler.java
index 3474e6ae..4b2448ce 100644
--- a/src/main/java/packets/handler/ClientBoundGamePacketHandler.java
+++ b/src/main/java/packets/handler/ClientBoundGamePacketHandler.java
@@ -160,7 +160,7 @@ public ClientBoundGamePacketHandler(ConnectionManager connectionManager) {
return true;
});
- operations.put("TradeList", provider -> {
+ operations.put("MerchantOffers", provider -> {
worldManager.getEntityRegistry().addVillagerTrades(provider);
return true;
});
@@ -179,6 +179,7 @@ public ClientBoundGamePacketHandler(ConnectionManager connectionManager) {
public static PacketHandler of(ConnectionManager connectionManager) {
return Config.versionReporter().select(PacketHandler.class,
+ Option.of(Version.V1_20_6, () -> new ClientBoundGamePacketHandler_1_20_6(connectionManager)),
Option.of(Version.V1_20_2, () -> new ClientBoundGamePacketHandler_1_20_2(connectionManager)),
Option.of(Version.V1_19, () -> new ClientBoundGamePacketHandler_1_19(connectionManager)),
Option.of(Version.V1_18, () -> new ClientBoundGamePacketHandler_1_18(connectionManager)),
diff --git a/src/main/java/packets/handler/ClientBoundLoginPacketHandler.java b/src/main/java/packets/handler/ClientBoundLoginPacketHandler.java
index 6251c643..e91eb170 100644
--- a/src/main/java/packets/handler/ClientBoundLoginPacketHandler.java
+++ b/src/main/java/packets/handler/ClientBoundLoginPacketHandler.java
@@ -14,20 +14,27 @@ public class ClientBoundLoginPacketHandler extends PacketHandler {
public ClientBoundLoginPacketHandler(ConnectionManager connectionManager) {
super(connectionManager);
- operations.put("disconnect", provider -> {
+ operations.put("LoginDisconnect", provider -> {
String reason = provider.readString();
System.out.println("Disconnect: " + reason);
return true;
});
- operations.put("encryption_request", provider -> {
+
+ operations.put("Hello", provider -> {
String serverId = provider.readString();
byte[] pubKey = provider.readByteArray(provider.readVarInt());
byte[] nonce = provider.readByteArray(provider.readVarInt());
- getConnectionManager().getEncryptionManager().setServerEncryptionRequest(pubKey, nonce, serverId);
+ if (Config.versionReporter().isAtLeast(Version.V1_20_6)) {
+ boolean shouldAuthenticate = provider.readBoolean();
+ getConnectionManager().getEncryptionManager().setServerEncryptionRequest(pubKey, nonce, serverId, shouldAuthenticate);
+ } else {
+ getConnectionManager().getEncryptionManager().setServerEncryptionRequest(pubKey, nonce, serverId);
+ }
+
return false;
});
- operations.put("login_success", provider -> {
+ operations.put("GameProfile", provider -> {
String uuid = Config.versionReporter().select(String.class,
Option.of(Version.V1_16, () -> provider.readUUID().toString()),
Option.of(Version.ANY, provider::readString)
@@ -36,14 +43,12 @@ public ClientBoundLoginPacketHandler(ConnectionManager connectionManager) {
String username = provider.readString();
System.out.println("Login success: " + username + " logged in with uuid " + uuid);
- if (Config.versionReporter().isAtLeast(Version.V1_20_2)) {
- getConnectionManager().setMode(NetworkMode.CONFIGURATION);
- } else {
+ if (!Config.versionReporter().isAtLeast(Version.V1_20_2)) {
getConnectionManager().setMode(NetworkMode.GAME);
}
return true;
});
- operations.put("set_compression", provider -> {
+ operations.put("LoginCompression", provider -> {
int limit = provider.readVarInt();
getConnectionManager().getCompressionManager().enableCompression(limit);
return true;
diff --git a/src/main/java/packets/handler/PacketHandler.java b/src/main/java/packets/handler/PacketHandler.java
index faf1b645..44f5ee15 100644
--- a/src/main/java/packets/handler/PacketHandler.java
+++ b/src/main/java/packets/handler/PacketHandler.java
@@ -58,6 +58,20 @@ public final boolean handle(int size) {
return operator.apply(typeProvider);
}
+ public int indexOf(byte[] outerArray, byte[] smallerArray) {
+ for(int i = 0; i < outerArray.length - smallerArray.length+1; ++i) {
+ boolean found = true;
+ for(int j = 0; j < smallerArray.length; ++j) {
+ if (outerArray[i+j] != smallerArray[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) return i;
+ }
+ return -1;
+ }
+
public abstract Map getOperators();
public abstract boolean isClientBound();
diff --git a/src/main/java/packets/handler/ServerBoundLoginPacketHandler.java b/src/main/java/packets/handler/ServerBoundLoginPacketHandler.java
index dc922536..941d4cab 100644
--- a/src/main/java/packets/handler/ServerBoundLoginPacketHandler.java
+++ b/src/main/java/packets/handler/ServerBoundLoginPacketHandler.java
@@ -2,6 +2,9 @@
import static util.PrintUtils.devPrint;
+import config.Config;
+import config.Version;
+import game.NetworkMode;
import java.util.HashMap;
import java.util.Map;
import proxy.ConnectionManager;
@@ -11,7 +14,7 @@ public class ServerBoundLoginPacketHandler extends PacketHandler {
public ServerBoundLoginPacketHandler(ConnectionManager connectionManager) {
super(connectionManager);
- operations.put("login_start", provider -> {
+ operations.put("Hello", provider -> {
String username = provider.readString();
devPrint("Login by: " + username);
@@ -20,7 +23,7 @@ public ServerBoundLoginPacketHandler(ConnectionManager connectionManager) {
return true;
});
- operations.put("encryption_response", provider -> {
+ operations.put("Key", provider -> {
int sharedSecretLength = provider.readVarInt();
byte[] sharedSecret = provider.readByteArray(sharedSecretLength);
byte[] nonce = provider.readByteArray(provider.readVarInt());
@@ -28,6 +31,15 @@ public ServerBoundLoginPacketHandler(ConnectionManager connectionManager) {
return false;
});
+
+ operations.put("LoginAcknowledged", provider -> {
+ if (Config.versionReporter().isAtLeast(Version.V1_20_2)) {
+ getConnectionManager().setMode(NetworkMode.CONFIGURATION);
+ } else {
+ getConnectionManager().setMode(NetworkMode.GAME);
+ }
+ return true;
+ });
}
@Override
diff --git a/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_2.java b/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_2.java
index 9bb313bc..afaa2f98 100644
--- a/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_2.java
+++ b/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_2.java
@@ -1,7 +1,7 @@
package packets.handler.version;
import game.data.WorldManager;
-import game.data.dimension.DimensionCodec;
+import game.data.dimension.DimensionRegistry;
import packets.handler.ClientBoundConfigurationPacketHandler;
import packets.handler.PacketOperator;
import proxy.ConnectionManager;
@@ -18,7 +18,7 @@ public ClientBoundConfigurationPacketHandler_1_20_2(ConnectionManager connection
operators.put("RegistryData", provider -> {
SpecificTag registryData = provider.readNbtTag();
- worldManager.setDimensionCodec(DimensionCodec.fromNbt(registryData));
+ worldManager.setDimensionRegistry(DimensionRegistry.fromNbt(registryData));
return true;
});
diff --git a/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_6.java b/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_6.java
new file mode 100644
index 00000000..cb46d93b
--- /dev/null
+++ b/src/main/java/packets/handler/version/ClientBoundConfigurationPacketHandler_1_20_6.java
@@ -0,0 +1,23 @@
+package packets.handler.version;
+
+import game.data.WorldManager;
+import java.util.Map;
+import packets.handler.ClientBoundConfigurationPacketHandler;
+import packets.handler.PacketOperator;
+import proxy.ConnectionManager;
+
+public class ClientBoundConfigurationPacketHandler_1_20_6 extends ClientBoundConfigurationPacketHandler {
+ public ClientBoundConfigurationPacketHandler_1_20_6(ConnectionManager connectionManager) {
+ super(connectionManager);
+
+ Map operators = getOperators();
+ operators.put("RegistryData", provider -> {
+ var registry = provider.readRegistry();
+ switch (registry.name()) {
+ case "minecraft:worldgen/biome" -> WorldManager.getInstance().getDimensionRegistry().loadBiomes(registry);
+ case "minecraft:dimension_type" -> WorldManager.getInstance().getDimensionRegistry().loadDimensions(registry);
+ }
+ return true;
+ });
+ }
+}
diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java
index a5426dd3..de7aa53b 100644
--- a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java
+++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_16.java
@@ -5,9 +5,8 @@
import game.data.WorldManager;
import game.data.coordinates.Coordinate3D;
import game.data.dimension.Dimension;
-import game.data.dimension.DimensionCodec;
+import game.data.dimension.DimensionRegistry;
import game.protocol.Protocol;
-import game.protocol.ProtocolVersionHandler;
import packets.builder.PacketBuilder;
import packets.handler.PacketOperator;
import proxy.ConnectionManager;
@@ -34,7 +33,10 @@ public ClientBoundGamePacketHandler_1_16(ConnectionManager connectionManager) {
String[] dimensionNames = provider.readStringArray(numDimensions);
SpecificTag dimensionCodec = provider.readNbtTag();
- WorldManager.getInstance().setDimensionCodec(DimensionCodec.fromNbt(dimensionCodec).setDimensionNames(dimensionNames));
+
+ DimensionRegistry registry = DimensionRegistry.fromNbt(dimensionCodec);
+ registry.setDimensionNames(dimensionNames);
+ WorldManager.getInstance().setDimensionRegistry(registry);
SpecificTag dimensionNbt = provider.readNbtTag();
diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java
index f264cfae..d358f895 100644
--- a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java
+++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_19.java
@@ -10,13 +10,14 @@
import config.Config;
import game.data.WorldManager;
import game.data.dimension.Dimension;
-import game.data.dimension.DimensionCodec;
+import game.data.dimension.DimensionRegistry;
import game.protocol.Protocol;
import java.util.Map;
import packets.builder.PacketBuilder;
import packets.handler.PacketOperator;
import proxy.ConnectionManager;
import se.llbit.nbt.SpecificTag;
+import se.llbit.nbt.Tag;
public class ClientBoundGamePacketHandler_1_19 extends ClientBoundGamePacketHandler_1_18 {
public ClientBoundGamePacketHandler_1_19(ConnectionManager connectionManager) {
@@ -34,8 +35,11 @@ public ClientBoundGamePacketHandler_1_19(ConnectionManager connectionManager) {
int numDimensions = provider.readVarInt();
String[] dimensionNames = provider.readStringArray(numDimensions);
- SpecificTag dimensionCodec = provider.readNbtTag();
- WorldManager.getInstance().setDimensionCodec(DimensionCodec.fromNbt(dimensionCodec).setDimensionNames(dimensionNames));
+ SpecificTag nbt = provider.readNbtTag();
+ DimensionRegistry registry = DimensionRegistry.fromNbt(nbt);
+ registry.setDimensionNames(dimensionNames);
+
+ WorldManager.getInstance().setDimensionRegistry(registry);
String dimensionType = provider.readString();
// current active dimension
@@ -46,7 +50,7 @@ public ClientBoundGamePacketHandler_1_19(ConnectionManager connectionManager) {
replacement.writeVarInt(numDimensions);
replacement.writeStringArray(dimensionNames);
- replacement.writeNbt(dimensionCodec);
+ replacement.writeNbt(nbt);
replacement.writeString(dimensionType);
replacement.writeString(worldName);
diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java
index 4599deb3..cb3fbbe2 100644
--- a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java
+++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_2.java
@@ -3,14 +3,11 @@
import config.Config;
import game.data.WorldManager;
import game.data.dimension.Dimension;
-import game.data.dimension.DimensionCodec;
import game.data.entity.EntityRegistry;
import game.protocol.Protocol;
import packets.builder.PacketBuilder;
import packets.handler.PacketOperator;
import proxy.ConnectionManager;
-import se.llbit.nbt.CompoundTag;
-import se.llbit.nbt.SpecificTag;
import java.util.Map;
@@ -32,7 +29,7 @@ public ClientBoundGamePacketHandler_1_20_2(ConnectionManager connectionManager)
// handle dimension codec
int numDimensions = provider.readVarInt();
String[] dimensionNames = provider.readStringArray(numDimensions);
- WorldManager.getInstance().getDimensionCodec().setDimensionNames(dimensionNames);
+ WorldManager.getInstance().getDimensionRegistry().setDimensionNames(dimensionNames);
replacement.writeVarInt(numDimensions);
replacement.writeStringArray(dimensionNames);
@@ -51,6 +48,7 @@ public ClientBoundGamePacketHandler_1_20_2(ConnectionManager connectionManager)
Dimension dimension = Dimension.fromString(dimensionName);
dimension.setType(dimensionType);
WorldManager.getInstance().setDimension(dimension);
+ WorldManager.getInstance().setDimensionType(WorldManager.getInstance().getDimensionRegistry().getDimensionType(dimensionType));
replacement.writeString(dimensionType);
replacement.writeString(dimensionName);
@@ -63,7 +61,7 @@ public ClientBoundGamePacketHandler_1_20_2(ConnectionManager connectionManager)
return false;
});
- operators.put("UpdatePlayerInfo", provider -> {
+ operators.put("PlayerInfoUpdate", provider -> {
entityRegistry.updatePlayerAction(provider);
return true;
});
diff --git a/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_6.java b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_6.java
new file mode 100644
index 00000000..08492032
--- /dev/null
+++ b/src/main/java/packets/handler/version/ClientBoundGamePacketHandler_1_20_6.java
@@ -0,0 +1,91 @@
+package packets.handler.version;
+
+import static packets.builder.NetworkType.BOOL;
+import static packets.builder.NetworkType.INT;
+import static packets.builder.NetworkType.VARINT;
+
+import config.Config;
+import game.NetworkMode;
+import game.data.WorldManager;
+import game.protocol.Protocol;
+import java.util.Arrays;
+import java.util.Map;
+import packets.DataTypeProvider;
+import packets.builder.PacketBuilder;
+import packets.handler.PacketOperator;
+import proxy.ConnectionManager;
+
+public class ClientBoundGamePacketHandler_1_20_6 extends ClientBoundGamePacketHandler_1_20_2 {
+
+ private WorldManager world;
+
+ public ClientBoundGamePacketHandler_1_20_6(ConnectionManager connectionManager) {
+ super(connectionManager);
+
+ Protocol protocol = Config.versionReporter().getProtocol();
+
+ Map operators = getOperators();
+
+ operators.put("StartConfiguration", dataTypeProvider -> {
+ getConnectionManager().setMode(NetworkMode.CONFIGURATION);
+ return true;
+ });
+
+ operators.put("ContainerSetContent", provider -> {
+ int windowId = provider.readNext();
+
+ int stateId = provider.readVarInt();
+ int count = provider.readVarInt();
+ WorldManager.getInstance().getContainerManager().items(windowId, count, provider);
+
+ return true;
+ });
+
+ operators.put("Login", provider -> {
+ PacketBuilder replacement = new PacketBuilder(protocol.clientBound("Login"));
+
+ replacement.copy(provider, INT, BOOL); /* playerId, hardcore */
+
+ int numLevels = provider.readVarInt();
+ String[] levels = provider.readStringArray(numLevels);
+
+ replacement.writeVarInt(numLevels);
+ replacement.writeStringArray(levels);
+
+ replacement.copy(provider, VARINT); /* maxPlayers */
+
+ // extend view distance communicated to the client to the given value
+ int viewDist = provider.readVarInt();
+ replacement.writeVarInt(Math.max(viewDist, Config.getExtendedRenderDistance()));
+
+ replacement.copy(provider, VARINT, BOOL, BOOL, BOOL); /* simulationDist, reducedDebug, showDeathScreen, limitedCrafting */
+
+ commonInfo(provider, replacement);
+
+ getConnectionManager().getEncryptionManager().sendImmediately(replacement);
+ return false;
+ });
+
+ operators.put("Respawn", provider -> {
+ commonInfo(provider, null);
+
+ return true;
+ });
+ }
+
+ private void commonInfo(DataTypeProvider provider, PacketBuilder replacement) {
+ // handle dimension codec
+ int dimensionType = provider.readVarInt();
+ String dimensionName = provider.readString();
+
+ world = WorldManager.getInstance();
+ world.setDimension(world.getDimensionRegistry().getDimension(dimensionName));
+ world.setDimensionType(world.getDimensionRegistry().getDimensionType(dimensionType));
+
+ if (replacement != null) {
+ replacement.writeVarInt(dimensionType);
+ replacement.writeString(dimensionName);
+ replacement.copyRemainder(provider);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/packets/version/DataTypeProvider_1_20_6.java b/src/main/java/packets/version/DataTypeProvider_1_20_6.java
new file mode 100644
index 00000000..2674446f
--- /dev/null
+++ b/src/main/java/packets/version/DataTypeProvider_1_20_6.java
@@ -0,0 +1,22 @@
+package packets.version;
+
+import game.data.container.Slot;
+
+public class DataTypeProvider_1_20_6 extends DataTypeProvider_1_20_2 {
+ public DataTypeProvider_1_20_6(byte[] finalFullPacket) {
+ super(finalFullPacket);
+ }
+
+ @Override
+ public Slot readSlot() {
+ int count = readVarInt();
+
+ // TODO: handle 1.20.6+ item components
+ if (count > 0) {
+ return new Slot(readVarInt(), (byte) count, null);
+ }
+ return null;
+ }
+
+
+}
diff --git a/src/main/java/proxy/EncryptionManager.java b/src/main/java/proxy/EncryptionManager.java
index de5b32a6..9c46d5d8 100644
--- a/src/main/java/proxy/EncryptionManager.java
+++ b/src/main/java/proxy/EncryptionManager.java
@@ -3,6 +3,7 @@
import static util.PrintUtils.devPrintFormat;
import config.Config;
+import config.Version;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
@@ -54,6 +55,7 @@ public class EncryptionManager {
private final ClientAuthenticator clientAuthenticator;
private RSAPublicKey clientProfilePublicKey;
+ private boolean shouldAuthenticate = true; /* >= 1.20.6 */
{
// generate the keypair for the local server
@@ -74,6 +76,10 @@ public boolean isEncryptionEnabled() {
return encryptionEnabled;
}
+ public void setServerEncryptionRequest(byte[] encoded, byte[] nonce, String serverId, boolean shouldAuthenticate) {
+ this.shouldAuthenticate = shouldAuthenticate;
+ setServerEncryptionRequest(encoded, nonce, serverId);
+ }
/**
* When the server sends the client an encryption request, this method will be called to get the server's given
* public key and call the replacement request sender.
@@ -136,6 +142,10 @@ private void sendReplacementEncryptionRequest() {
builder.writeVarInt(nonce.length); // verify token len
builder.writeByteArray(nonce); // verify token
+ if (Config.versionReporter().isAtLeast(Version.V1_20_6)) {
+ builder.writeBoolean(shouldAuthenticate);
+ }
+
attempt(() -> streamToClient(builder.build()));
}
@@ -295,13 +305,15 @@ public static byte[] generateSalt() {
* future traffic.
*/
private void sendReplacementEncryptionConfirmation() {
- // authenticate the client so that the remote server will accept us
- boolean client = disconnectOnError(() -> clientAuthenticator.makeRequest(generateServerHash()));
- if (!client) { return; }
-
- // verify the connecting client connection is who they claim to be
- boolean server = disconnectOnError(() -> new ServerAuthenticator(username).makeRequest(generateServerHash()));
- if (!server) { return; }
+ if (shouldAuthenticate) {
+ // authenticate the client so that the remote server will accept us
+ boolean client = disconnectOnError(() -> clientAuthenticator.makeRequest(generateServerHash()));
+ if (!client) { return; }
+
+ // verify the connecting client connection is who they claim to be
+ boolean server = disconnectOnError(() -> new ServerAuthenticator(username).makeRequest(generateServerHash()));
+ if (!server) { return; }
+ }
// encryption confirmation
attempt(() -> {
diff --git a/src/main/java/proxy/auth/AuthDetailsManager.java b/src/main/java/proxy/auth/AuthDetailsManager.java
index e9c3d9ac..480bdea5 100644
--- a/src/main/java/proxy/auth/AuthDetailsManager.java
+++ b/src/main/java/proxy/auth/AuthDetailsManager.java
@@ -51,7 +51,7 @@ public static void validateAuthStatus(Consumer onSuccess, Consumer
-
-
+
+
@@ -20,14 +20,17 @@
-
+
-
+
+
+
+
diff --git a/src/main/resources/ui/dark.css b/src/main/resources/ui/dark.css
index fb9d46bf..428321c7 100644
--- a/src/main/resources/ui/dark.css
+++ b/src/main/resources/ui/dark.css
@@ -138,3 +138,7 @@
-fx-font-size: 1.0em;
}
+.link-subtle {
+ -fx-text-fill: #bbb;
+ -fx-underline: true;
+}
\ No newline at end of file
diff --git a/src/test/java/game/data/chunk/ChunkTest.java b/src/test/java/game/data/chunk/ChunkTest.java
index eef6d5c0..89bf5ca7 100644
--- a/src/test/java/game/data/chunk/ChunkTest.java
+++ b/src/test/java/game/data/chunk/ChunkTest.java
@@ -7,9 +7,9 @@
import game.data.WorldManager;
import game.data.chunk.palette.BlockColors;
import game.data.dimension.Biome;
-import game.data.dimension.BiomeProvider;
+import game.data.dimension.BiomeRegistry;
import game.data.dimension.Dimension;
-import game.data.dimension.DimensionCodec;
+import game.data.dimension.DimensionRegistry;
import game.data.registries.RegistryManager;
import java.util.HashMap;
import java.util.Map;
@@ -43,13 +43,13 @@ private void testFor(int protocolVersion, String dataFile) throws IOException, C
when(mock.getChunkFactory()).thenReturn(new ChunkFactory());
Chunk_1_17.setWorldHeight(-63, 384);
- DimensionCodec codecMock = mock(DimensionCodec.class);
+ DimensionRegistry codecMock = mock(DimensionRegistry.class);
Map biomeMap = new HashMap<>();
biomeMap.put("minecraft:badlands", new Biome(0));
biomeMap.put("minecraft:forest", new Biome(1));
biomeMap.put("minecraft:river", new Biome(2));
- when(codecMock.getBiomeProvider()).thenReturn(new BiomeProvider(biomeMap));
- when(mock.getDimensionCodec()).thenReturn(codecMock);
+ when(codecMock.getBiomeRegistry()).thenReturn(new BiomeRegistry(biomeMap));
+ when(mock.getDimensionRegistry()).thenReturn(codecMock);
RegistryManager registryManager = mock(RegistryManager.class);
when(registryManager.getBlockEntityRegistry()).thenReturn(new BlockEntityRegistry());