3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-07-11 07:48:05 +02:00

Biome reworkings

- Introduce biome mappings for having a constant reference between Java biome identifier and their Bedrock equivalents
- Don't assume biome IDs and instead listen to the server for biome IDs
- Ensure that only valid Bedrock biomes are sent. With the caves and cliffs experimental toggle, Bedrock will crash if an invalid biome ID is sent its way.
Dieser Commit ist enthalten in:
Camotoy 2021-07-27 20:29:27 -04:00
Ursprung e0e6605fb6
Commit 8c96c3b11d
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 7EEFB66FE798081F
10 geänderte Dateien mit 210 neuen und 96 gelöschten Zeilen

Datei anzeigen

@ -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<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
private final PlayerInventory playerInventory;
@ -188,6 +187,13 @@ public class GeyserSession implements CommandSender {
private final Map<Vector3i, SkullPlayerEntity> skullCache = new ConcurrentHashMap<>();
private final Long2ObjectMap<ClientboundMapItemDataPacket> 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();

Datei anzeigen

@ -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<ServerJoinGamePacke
}
session.setWorldName(packet.getWorldName());
BiomeTranslator.loadServerBiomes(session, packet.getDimensionCodec());
session.getTagCache().clear();
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();

Datei anzeigen

@ -40,7 +40,7 @@ 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.chunk.ChunkSection;
import org.geysermc.connector.utils.BiomeUtils;
import org.geysermc.connector.network.translators.world.BiomeTranslator;
import org.geysermc.connector.utils.ChunkUtils;
@Translator(packet = ServerChunkDataPacket.class)
@ -101,7 +101,7 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
if (NEW_BIOME_WRITE) {
for (int i = 0; i < sectionCount; i++) {
BiomeUtils.toNewBedrockBiome(column.getBiomeData(), i).writeToNetwork(byteBuf);
BiomeTranslator.toNewBedrockBiome(session, column.getBiomeData(), i).writeToNetwork(byteBuf);
}
// As of 1.17.10, Bedrock hardcodes to always read 32 biome sections
@ -110,7 +110,7 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
}
} else {
byteBuf.writeBytes(BiomeUtils.toBedrockBiome(column.getBiomeData())); // Biomes - 256 bytes
byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(session, column.getBiomeData())); // Biomes - 256 bytes
}
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now

Datei anzeigen

@ -0,0 +1,112 @@
/*
* 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.network.translators.world;
import com.github.steveice10.opennbt.tag.builtin.*;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.chunk.BlockStorage;
import org.geysermc.connector.network.translators.world.chunk.ChunkSection;
import org.geysermc.connector.registry.Registries;
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 BiomeTranslator {
public static void loadServerBiomes(GeyserSession session, CompoundTag codec) {
Int2IntMap biomeTranslations = session.getBiomeTranslations();
biomeTranslations.clear();
CompoundTag worldGen = codec.get("minecraft:worldgen/biome");
ListTag serverBiomes = worldGen.get("value");
for (Tag tag : serverBiomes) {
CompoundTag biomeTag = (CompoundTag) tag;
String javaIdentifier = ((StringTag) biomeTag.get("name")).getValue();
int bedrockId = Registries.BIOME_IDENTIFIERS.get().getOrDefault(javaIdentifier, 0);
int javaId = ((IntTag) biomeTag.get("id")).getValue();
if (javaId != bedrockId) {
// When we see the Java ID, we should instead apply the Bedrock ID
biomeTranslations.put(javaId, bedrockId);
}
}
}
public static byte[] toBedrockBiome(GeyserSession session, int[] biomeData) {
byte[] bedrockData = new byte[256];
if (biomeData == null) {
return bedrockData;
}
Int2IntMap biomeTranslations = session.getBiomeTranslations();
for (int y = 0; y < 16; y += 4) {
for (int z = 0; z < 16; z += 4) {
for (int x = 0; x < 16; x += 4) {
int javaId = biomeData[((y >> 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;
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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<NbtMap> BIOMES = SimpleRegistry.create("bedrock/biome_definitions.dat", RegistryLoaders.NBT);
public static final SimpleRegistry<NbtMap> 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<Object2IntMap<String>> BIOME_IDENTIFIERS = SimpleRegistry.create("mappings/biomes.json", BiomeIdentifierRegistryLoader::new);
/**
* A mapped registry which stores a block entity identifier to its {@link BlockEntityTranslator}.

Datei anzeigen

@ -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 <M> the value being held by the registry

Datei anzeigen

@ -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<String, Object2IntMap<String>> {
@Override
public Object2IntMap<String> 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<Map<String, BiomeEntry>> biomeEntriesType = new TypeReference<Map<String, BiomeEntry>>() { };
Map<String, BiomeEntry> 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<String> biomes = new Object2IntOpenHashMap<>();
for (Map.Entry<String, BiomeEntry> 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;
}
}

Datei anzeigen

@ -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;
}
}

@ -1 +1 @@
Subproject commit 8351b0f5bb6e9a1d614f84e18c91e82288c34bf6
Subproject commit f109d34a343da0ade6132661839b893859680d91