diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 8f71e6fa8..355dcedcb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -58,9 +58,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -143,6 +141,7 @@ public class GeyserSession implements CommandSender { private final PreferencesCache preferencesCache; private final TagCache tagCache; private WorldCache worldCache; + private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>(); private final PlayerInventory playerInventory; @@ -188,6 +187,13 @@ public class GeyserSession implements CommandSender { private final Map skullCache = new ConcurrentHashMap<>(); private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + /** + * Stores the differences between Java and Bedrock biome network IDs. + * If Java's ocean biome is 0, and Bedrock's is 0, it will not be in the list. + * If Java's bamboo biome is 42, and Bedrock's is 48, it will be in this list. + */ + private final Int2IntMap biomeTranslations = new Int2IntOpenHashMap(); + /** * A map of Vector3i positions to Java entities. * Used for translating Bedrock block actions to Java entity actions. @@ -503,7 +509,7 @@ public class GeyserSession implements CommandSender { ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); - biomeDefinitionListPacket.setDefinitions(Registries.BIOMES.get()); + biomeDefinitionListPacket.setDefinitions(Registries.BIOMES_NBT.get()); upstream.sendPacket(biomeDefinitionListPacket); AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 6b5f63438..e46a1a89c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -39,6 +39,7 @@ import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.world.BiomeTranslator; import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.PluginMessageUtils; @@ -66,6 +67,7 @@ public class JavaJoinGameTranslator extends PacketTranslator> 2) & 63) << 4 | ((z >> 2) & 3) << 2 | ((x >> 2) & 3)]; + byte biomeId = (byte) biomeTranslations.getOrDefault(javaId, javaId); + int offset = ((z + (y / 4)) << 4) | x; + Arrays.fill(bedrockData, offset, offset + 4, biomeId); + } + } + } + return bedrockData; + } + + public static BlockStorage toNewBedrockBiome(GeyserSession session, int[] biomeData, int ySection) { + Int2IntMap biomeTranslations = session.getBiomeTranslations(); + // As of 1.17.10: the client expects the same format as a chunk but filled with biomes + BlockStorage storage = new BlockStorage(0); + + int biomeY = ySection << 2; + int javaOffsetY = biomeY << 4; + // Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries + for (int i = 0; i < 64; i++) { + int javaId = biomeData[javaOffsetY | i]; + int x = i & 3; + int y = (i >> 4) & 3; + int z = (i >> 2) & 3; + // Get the Bedrock biome ID override, or this ID if it's the same + int biomeId = biomeTranslations.getOrDefault(javaId, javaId); + int idx = storage.idFor(biomeId); + // Convert biome coordinates into block coordinates + // Bedrock expects a full 4096 blocks + for (int blockX = x << 2; blockX < (x << 2) + 4; blockX++) { + for (int blockZ = z << 2; blockZ < (z << 2) + 4; blockZ++) { + for (int blockY = y << 2; blockY < (y << 2) + 4; blockY++) { + storage.getBitArray().set(ChunkSection.blockPosition(blockX, blockY, blockZ), idx); + } + } + } + } + + return storage; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index faf8d6dc8..2d027faba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -101,7 +101,7 @@ public class BlockStorage { this.bitArray = newBitArray; } - private int idFor(int runtimeId) { + public int idFor(int runtimeId) { // Set to public so we can reuse the palette ID for biomes int index = this.palette.indexOf(runtimeId); if (index != -1) { return index; diff --git a/connector/src/main/java/org/geysermc/connector/registry/Registries.java b/connector/src/main/java/org/geysermc/connector/registry/Registries.java index 5cd2e4806..3447bdfc8 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/Registries.java +++ b/connector/src/main/java/org/geysermc/connector/registry/Registries.java @@ -36,6 +36,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import org.geysermc.connector.network.translators.collision.translators.BlockCollision; import org.geysermc.connector.network.translators.effect.Effect; import org.geysermc.connector.network.translators.sound.SoundHandler; @@ -59,7 +60,12 @@ public class Registries { /** * A registry holding a CompoundTag of all the known biomes. */ - public static final SimpleRegistry BIOMES = SimpleRegistry.create("bedrock/biome_definitions.dat", RegistryLoaders.NBT); + public static final SimpleRegistry BIOMES_NBT = SimpleRegistry.create("bedrock/biome_definitions.dat", RegistryLoaders.NBT); + + /** + * A mapped registry which stores Java biome identifiers and their Bedrock biome identifier. + */ + public static final SimpleRegistry> BIOME_IDENTIFIERS = SimpleRegistry.create("mappings/biomes.json", BiomeIdentifierRegistryLoader::new); /** * A mapped registry which stores a block entity identifier to its {@link BlockEntityTranslator}. diff --git a/connector/src/main/java/org/geysermc/connector/registry/Registry.java b/connector/src/main/java/org/geysermc/connector/registry/Registry.java index 0e999442f..135e94342 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/Registry.java +++ b/connector/src/main/java/org/geysermc/connector/registry/Registry.java @@ -58,7 +58,7 @@ import java.util.function.Consumer; * however it demonstrates a fairly basic use case of how this system works. Typically * though, the first parameter would be a location of some sort, such as a file path * where the loader will load the mappings from. The NBT registry is a good reference - * point for something both simple and practical. See {@link Registries#BIOMES} and + * point for something both simple and practical. See {@link Registries#BIOMES_NBT} and * {@link org.geysermc.connector.registry.loader.NbtRegistryLoader}. * * @param the value being held by the registry diff --git a/connector/src/main/java/org/geysermc/connector/registry/loader/BiomeIdentifierRegistryLoader.java b/connector/src/main/java/org/geysermc/connector/registry/loader/BiomeIdentifierRegistryLoader.java new file mode 100644 index 000000000..c38e3efa9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/registry/loader/BiomeIdentifierRegistryLoader.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.registry.loader; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +public class BiomeIdentifierRegistryLoader implements RegistryLoader> { + + @Override + public Object2IntMap load(String input) { + // As of Bedrock Edition 1.17.10 with the experimental toggle, any unmapped biome identifier sent to the client + // crashes the client. Therefore, we need to have a list of all valid Bedrock biome IDs with which we can use from. + // The server sends the corresponding Java network IDs, so we don't need to worry about that now. + + // Reference variable for Jackson to read off of + TypeReference> biomeEntriesType = new TypeReference>() { }; + Map biomeEntries; + + try (InputStream stream = FileUtils.getResource("mappings/biomes.json")) { + biomeEntries = GeyserConnector.JSON_MAPPER.readValue(stream, biomeEntriesType); + } catch (IOException e) { + throw new AssertionError("Unable to load Bedrock runtime biomes", e); + } + + Object2IntMap biomes = new Object2IntOpenHashMap<>(); + for (Map.Entry biome : biomeEntries.entrySet()) { + // Java Edition identifier -> Bedrock integer ID + biomes.put(biome.getKey(), biome.getValue().bedrockId); + } + + return biomes; + } + + private static class BiomeEntry { + /** + * The Bedrock network ID for this biome. + */ + @JsonProperty("bedrock_id") + private int bedrockId; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BiomeUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BiomeUtils.java deleted file mode 100644 index da557ea57..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/BiomeUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import org.geysermc.connector.network.translators.world.chunk.BlockStorage; - -import java.util.Arrays; - -// Based off of ProtocolSupport's LegacyBiomeData.java: -// https://github.com/ProtocolSupport/ProtocolSupport/blob/b2cad35977f3fcb65bee57b9e14fc9c975f71d32/src/protocolsupport/protocol/typeremapper/legacy/LegacyBiomeData.java -// Array index formula by https://wiki.vg/Chunk_Format -public class BiomeUtils { - public static byte[] toBedrockBiome(int[] biomeData) { - byte[] bedrockData = new byte[256]; - if (biomeData == null) { - return bedrockData; - } - - for (int y = 0; y < 16; y += 4) { - for (int z = 0; z < 16; z += 4) { - for (int x = 0; x < 16; x += 4) { - byte biomeId = (byte) biomeID(biomeData, x, y, z); - int offset = ((z + (y / 4)) << 4) | x; - Arrays.fill(bedrockData, offset, offset + 4, biomeId); - } - } - } - return bedrockData; - } - - public static BlockStorage toNewBedrockBiome(int[] biomeData, int ySection) { - BlockStorage storage = new BlockStorage(0); - int blockY = ySection << 4; - int i = 0; - // Iterate over biomes like a chunk, grab the biome from Java, and add it to Bedrock's biome palette - // Might be able to be optimized by iterating over Java's biome data section?? Unsure. - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - for (int y = blockY; y < (blockY + 16); y++) { - int biomeId = biomeID(biomeData, x, y, z); - storage.setFullBlock(i, biomeId); - i++; - } - } - } - return storage; - } - - private static int biomeID(int[] biomeData, int x, int y, int z) { - int biomeId = biomeData[((y >> 2) & 63) << 4 | ((z >> 2) & 3) << 2 | ((x >> 2) & 3)]; - if (biomeId >= 40 && biomeId <= 43) { // Java has multiple End dimensions that Bedrock doesn't recognize - biomeId = 9; - } else if (biomeId >= 170 && biomeId <= 173) { // 1.16 nether biomes. Dunno why it's like this :microjang: - biomeId += 8; - } else if (biomeId == 168) { // Bamboo jungle - biomeId = 48; - } else if (biomeId == 169) { // Bamboo jungle hills - biomeId = 49; - } - return biomeId; - } -} diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 8351b0f5b..f109d34a3 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 8351b0f5bb6e9a1d614f84e18c91e82288c34bf6 +Subproject commit f109d34a343da0ade6132661839b893859680d91