Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;

import java.util.UUID;

Expand All @@ -41,8 +40,8 @@ public class BasePiglinEntity extends MonsterEntity {

public BasePiglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
// Both TARGET_EID and BLOCK are needed for melee attack animation
dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getDefinition(1));
// Both TARGET_EID and VARIANT are needed for melee attack animation
dirtyMetadata.put(EntityDataTypes.VARIANT, 1);
setFlag(EntityFlag.SHAKING, isShaking());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void setHand(GeyserItemStack stack) {
boolean toCrossbow = stack != null && stack.is(Items.CROSSBOW);

if (toCrossbow ^ getMainHandItem().is(Items.CROSSBOW)) { // If switching to/from crossbow
dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getDefinition(toCrossbow ? 0 : 1));
dirtyMetadata.put(EntityDataTypes.VARIANT, toCrossbow ? 0 : 1);
dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
setFlag(EntityFlag.CHARGED, false);
setFlag(EntityFlag.USING_ITEM, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.geysermc.geyser.registry.populator.conversion.Conversion844_827;
import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
import org.geysermc.geyser.util.BlockHashUtils;
import org.geysermc.geyser.util.JsonUtils;

import java.io.DataInputStream;
Expand Down Expand Up @@ -137,30 +138,31 @@ private static void registerBedrockBlocks() {
//noinspection UnstableApiUsage
Interner<NbtMap> statesInterner = Interners.newStrongInterner();

for (ObjectIntPair<String> palette : blockMappers.keySet()) {
int protocolVersion = palette.valueInt();
List<NbtMap> vanillaBlockStates;
List<NbtMap> blockStates;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow(String.format("bedrock/block_palette.%s.nbt", palette.key()));
NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) {
NbtMap blockPalette = (NbtMap) nbtInputStream.readTag();
List<NbtMap> vanillaBlockStates;
List<NbtMap> blockStates;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("bedrock/block_palette.nbt");
NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) {
NbtMap blockPalette = (NbtMap) nbtInputStream.readTag();

vanillaBlockStates = new ArrayList<>(blockPalette.getList("blocks", NbtType.COMPOUND));
for (int i = 0; i < vanillaBlockStates.size(); i++) {
NbtMapBuilder builder = vanillaBlockStates.get(i).toBuilder();
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
builder.remove("network_id"); // Added in 1.19.80
builder.remove("block_id"); // Added in 1.20.60
//noinspection UnstableApiUsage
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
vanillaBlockStates.set(i, builder.build());
}

vanillaBlockStates = new ArrayList<>(blockPalette.getList("blocks", NbtType.COMPOUND));
for (int i = 0; i < vanillaBlockStates.size(); i++) {
NbtMapBuilder builder = vanillaBlockStates.get(i).toBuilder();
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
builder.remove("network_id"); // Added in 1.19.80
builder.remove("block_id"); // Added in 1.20.60
//noinspection UnstableApiUsage
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
vanillaBlockStates.set(i, builder.build());
}
blockStates = new ArrayList<>(vanillaBlockStates);
} catch (Exception e) {
throw new AssertionError("Unable to get blocks from runtime block states", e);
}

blockStates = new ArrayList<>(vanillaBlockStates);
} catch (Exception e) {
throw new AssertionError("Unable to get blocks from runtime block states", e);
}
for (ObjectIntPair<String> palette : blockMappers.keySet()) {
int protocolVersion = palette.valueInt();

List<BlockPropertyData> customBlockProperties = new ArrayList<>();
List<NbtMap> customBlockStates = new ArrayList<>();
Expand All @@ -179,17 +181,19 @@ private static void registerBedrockBlocks() {
blockStates.sort((a, b) -> Long.compareUnsigned(fnv164(a.getString("name")), fnv164(b.getString("name"))));
}

// New since 1.16.100 - find the block runtime ID by the order given to us in the block palette,
// as we no longer send a block palette
final Remapper stateMapper = blockMappers.get(palette);

Object2ObjectMap<NbtMap, GeyserBedrockBlock> blockStateOrderedMap = new Object2ObjectOpenHashMap<>(blockStates.size());
GeyserBedrockBlock[] bedrockRuntimeMap = new GeyserBedrockBlock[blockStates.size()];
for (int i = 0; i < blockStates.size(); i++) {
NbtMap tag = blockStates.get(i);
GeyserBedrockBlock block = new GeyserBedrockBlock(i, tag);
if (blockStateOrderedMap.put(tag, block) != null) {
Int2ObjectMap<GeyserBedrockBlock> bedrockHashedIdMap = new Int2ObjectOpenHashMap<>();
for (final NbtMap tag : blockStates) {
NbtMap remappedTag = stateMapper.remap(tag);

int hashedId = BlockHashUtils.toHash(remappedTag);
GeyserBedrockBlock block = new GeyserBedrockBlock(hashedId, remappedTag);
if (blockStateOrderedMap.put(tag, block) != null) { // Not a typo, use the latest block tag.
throw new AssertionError("Duplicate block states in Bedrock palette: " + tag);
}
bedrockRuntimeMap[i] = block;
bedrockHashedIdMap.put(hashedId, block);
}

Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions = Object2ObjectMaps.emptyMap();
Expand Down Expand Up @@ -229,8 +233,6 @@ private static void registerBedrockBlocks() {
BlockDefinition movingBlockDefinition = null;
Iterator<NbtMap> blocksIterator = BLOCKS_NBT.iterator();

Remapper stateMapper = blockMappers.get(palette);

GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE];
GeyserBedrockBlock[] javaToVanillaBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE];

Expand Down Expand Up @@ -261,8 +263,7 @@ private static void registerBedrockBlocks() {
BlockState blockState = javaBlockStates.get(javaRuntimeId);
String javaId = blockState.toString();

NbtMap originalBedrockTag = buildBedrockState(blockState, entry);
NbtMap bedrockTag = stateMapper.remap(originalBedrockTag);
NbtMap bedrockTag = buildBedrockState(blockState, entry);

GeyserBedrockBlock vanillaBedrockDefinition = blockStateOrderedMap.get(bedrockTag);

Expand All @@ -271,11 +272,7 @@ private static void registerBedrockBlocks() {
if (blockStateOverride == null) {
bedrockDefinition = vanillaBedrockDefinition;
if (bedrockDefinition == null) {
throw new RuntimeException("""
Unable to find %s Bedrock runtime ID for %s! Original block tag:
%s
Updated block tag:
%s""".formatted(javaId, palette.key(), originalBedrockTag, bedrockTag));
throw new RuntimeException("Unable to find %s Bedrock runtime ID for %s! Block tag: %s".formatted(javaId, palette.key(), bedrockTag));
}
} else {
bedrockDefinition = customBlockStateDefinitions.get(blockStateOverride);
Expand Down Expand Up @@ -330,7 +327,7 @@ private static void registerBedrockBlocks() {
// Get the tag needed for non-empty flower pots
if (javaPottable.contains(block)) {
// Specifically NOT putIfAbsent - mangrove propagule breaks otherwise
flowerPotBlocks.put(block, blockStates.get(bedrockDefinition.getRuntimeId()));
flowerPotBlocks.put(block, bedrockHashedIdMap.get(bedrockDefinition.getRuntimeId()).getState());
}

javaToVanillaBedrockBlocks[javaRuntimeId] = vanillaBedrockDefinition;
Expand Down Expand Up @@ -407,7 +404,7 @@ private static void registerBedrockBlocks() {
}
});

BlockRegistries.BLOCKS.register(palette.valueInt(), builder.bedrockRuntimeMap(bedrockRuntimeMap)
BlockRegistries.BLOCKS.register(palette.valueInt(), builder.bedrockHashedIdMap(bedrockHashedIdMap)
.javaToBedrockBlocks(javaToBedrockBlocks)
.javaToVanillaBedrockBlocks(javaToVanillaBedrockBlocks)
.javaToBedrockIdentifiers(javaToBedrockIdentifiers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public static void populate() {

BlockDefinition bedrockBlock = null;
Integer firstBlockRuntimeId = entry.getValue().getFirstBlockRuntimeId();
BlockDefinition customBlockItemOverride = null;
GeyserBedrockBlock customBlockItemOverride = null;
if (firstBlockRuntimeId != null) {
BlockDefinition blockOverride = bedrockBlockIdOverrides.get(bedrockIdentifier);

Expand Down Expand Up @@ -439,7 +439,7 @@ public static void populate() {
if (bedrockBlock == null) {
// We need to loop around again (we can't cache the block tags above) because Bedrock can include states that we don't have a pairing for
// in it's "preferred" block state - I.E. the first matching block state in the list
for (GeyserBedrockBlock block : blockMappings.getBedrockRuntimeMap()) {
for (GeyserBedrockBlock block : blockMappings.getBedrockHashedIdMap().values()) {
if (block == null) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class BlockMappings implements DefinitionRegistry<BlockDefinition> {
Int2ObjectMap<String> javaToBedrockIdentifiers;

Map<NbtMap, GeyserBedrockBlock> stateDefinitionMap;
GeyserBedrockBlock[] bedrockRuntimeMap;
Int2ObjectMap<GeyserBedrockBlock> bedrockHashedIdMap;
int[] remappedVanillaIds;

BlockDefinition commandBlock;
Expand Down Expand Up @@ -123,10 +123,7 @@ public BlockDefinition getStructureBlockFromMode(String mode) {

@Override
public @Nullable GeyserBedrockBlock getDefinition(int bedrockId) {
if (bedrockId < 0 || bedrockId >= this.bedrockRuntimeMap.length) {
return null;
}
return bedrockRuntimeMap[bedrockId];
return this.bedrockHashedIdMap.get(bedrockId);
}

public @Nullable GeyserBedrockBlock getDefinition(NbtMap tag) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,23 @@

package org.geysermc.geyser.registry.type;

import lombok.Getter;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;

public class GeyserBedrockBlock implements BlockDefinition {
private final int runtimeId;
private final int hashedNetworkId;
@Getter
private final NbtMap state;

public GeyserBedrockBlock(int runtimeId, NbtMap state) {
this.runtimeId = runtimeId;
public GeyserBedrockBlock(int hashedNetworkId, NbtMap state) {
this.hashedNetworkId = hashedNetworkId;
this.state = state;
}

@Override
public int getRuntimeId() {
return runtimeId;
}

public NbtMap getState() {
return state;
public int getRuntimeId() { // A little bit misleading name lol.
return this.hashedNetworkId;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1770,7 +1770,9 @@ private void startGame() {
startGamePacket.setCustomBiomeName("");
startGamePacket.setEducationProductionId("");
startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty());


startGamePacket.setBlockNetworkIdsHashed(true);

String serverName = geyser.config().gameplay().serverName();
startGamePacket.setLevelId(serverName);
startGamePacket.setLevelName(serverName);
Expand Down
72 changes: 72 additions & 0 deletions core/src/main/java/org/geysermc/geyser/util/BlockHashUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2025 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.geyser.util;

import org.cloudburstmc.nbt.NBTOutputStream;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.TreeMap;

// Taken from https://gist.github.com/Alemiz112/504d0f79feac7ef57eda174b668dd345
public class BlockHashUtils {
private static final int FNV1_32_INIT = 0x811c9dc5;
private static final int FNV1_PRIME_32 = 0x01000193;

public static int toHash(NbtMap block) {
if (block.getString("name").equals("minecraft:unknown")) {
return -2; // This is special case
}

NbtMap tag = NbtMap.builder()
.putString("name", block.getString("name"))
.putCompound("states", NbtMap.fromMap(
new TreeMap<>(block.getCompound("states"))))
.build();

byte[] bytes;
try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
NBTOutputStream outputStream = NbtUtils.createWriterLE(stream)) {
outputStream.writeTag(tag);
bytes = stream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}

return fnv1a_32(bytes);
}

private static int fnv1a_32(byte[] data) {
int hash = FNV1_32_INIT;
for (byte datum : data) {
hash ^= (datum & 0xff);
hash *= FNV1_PRIME_32;
}
return hash;
}
}
Binary file not shown.
Binary file not shown.