2021-07-13 01:19:40 +00:00
|
|
|
/*
|
2022-01-01 19:03:05 +00:00
|
|
|
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
2021-07-13 01:19:40 +00:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2021-11-20 21:34:30 +00:00
|
|
|
package org.geysermc.geyser.registry.populator;
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
2022-06-24 20:48:28 +00:00
|
|
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
2021-07-13 01:19:40 +00:00
|
|
|
import com.google.common.collect.ImmutableMap;
|
2023-04-07 18:08:22 +00:00
|
|
|
import com.google.common.collect.Interner;
|
|
|
|
import com.google.common.collect.Interners;
|
2023-08-21 23:04:08 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
2022-12-23 21:18:48 +00:00
|
|
|
import it.unimi.dsi.fastutil.objects.*;
|
|
|
|
import org.cloudburstmc.nbt.*;
|
2023-08-21 23:04:08 +00:00
|
|
|
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
|
2023-06-07 14:44:18 +00:00
|
|
|
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
2023-07-11 23:17:01 +00:00
|
|
|
import org.cloudburstmc.protocol.bedrock.codec.v594.Bedrock_v594;
|
2023-05-23 20:34:50 +00:00
|
|
|
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
2021-11-20 21:34:30 +00:00
|
|
|
import org.geysermc.geyser.GeyserImpl;
|
2023-08-21 23:04:08 +00:00
|
|
|
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
|
|
|
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
|
|
|
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
2021-11-20 23:29:46 +00:00
|
|
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
2021-11-28 16:03:45 +00:00
|
|
|
import org.geysermc.geyser.level.physics.PistonBehavior;
|
2021-11-20 21:34:30 +00:00
|
|
|
import org.geysermc.geyser.registry.BlockRegistries;
|
|
|
|
import org.geysermc.geyser.registry.type.BlockMapping;
|
|
|
|
import org.geysermc.geyser.registry.type.BlockMappings;
|
2022-12-23 21:18:48 +00:00
|
|
|
import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
|
2021-11-20 23:29:46 +00:00
|
|
|
import org.geysermc.geyser.util.BlockUtils;
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
import java.io.DataInputStream;
|
|
|
|
import java.io.InputStream;
|
2023-08-21 23:04:08 +00:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2022-12-21 00:47:45 +00:00
|
|
|
import java.util.*;
|
2021-07-13 01:19:40 +00:00
|
|
|
import java.util.function.BiFunction;
|
|
|
|
import java.util.zip.GZIPInputStream;
|
|
|
|
|
2021-07-22 02:14:00 +00:00
|
|
|
/**
|
|
|
|
* Populates the block registries.
|
|
|
|
*/
|
2022-08-07 16:09:54 +00:00
|
|
|
public final class BlockRegistryPopulator {
|
2023-08-21 23:04:08 +00:00
|
|
|
/**
|
|
|
|
* The stage of population
|
|
|
|
*/
|
|
|
|
public enum Stage {
|
|
|
|
PRE_INIT,
|
|
|
|
INIT_JAVA,
|
|
|
|
INIT_BEDROCK,
|
|
|
|
POST_INIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void populate(Stage stage) {
|
|
|
|
switch (stage) {
|
2023-09-09 17:23:19 +00:00
|
|
|
case PRE_INIT, POST_INIT -> { nullifyBlocksNode(); }
|
2023-08-21 23:04:08 +00:00
|
|
|
case INIT_JAVA -> { registerJavaBlocks(); }
|
|
|
|
case INIT_BEDROCK -> { registerBedrockBlocks(); }
|
|
|
|
default -> { throw new IllegalArgumentException("Unknown stage: " + stage); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
/**
|
|
|
|
* Stores the raw blocks JSON until it is no longer needed.
|
|
|
|
*/
|
|
|
|
private static JsonNode BLOCKS_JSON;
|
2023-09-09 17:23:19 +00:00
|
|
|
private static int MIN_CUSTOM_RUNTIME_ID = -1;
|
|
|
|
private static int JAVA_BLOCKS_SIZE = -1;
|
2021-07-13 01:19:40 +00:00
|
|
|
|
2023-08-21 23:04:08 +00:00
|
|
|
private static void nullifyBlocksNode() {
|
2021-07-13 01:19:40 +00:00
|
|
|
BLOCKS_JSON = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void registerBedrockBlocks() {
|
2022-08-07 16:09:54 +00:00
|
|
|
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
|
2023-05-24 06:33:43 +00:00
|
|
|
|
2023-07-11 23:17:01 +00:00
|
|
|
// adapt 1.20 mappings to 1.20.10+
|
|
|
|
BiFunction<String, NbtMapBuilder, String> concreteAndShulkerBoxMapper = (bedrockIdentifier, statesBuilder) -> {
|
|
|
|
if (bedrockIdentifier.equals("minecraft:concrete")) {
|
|
|
|
String color = (String) statesBuilder.remove("color");
|
|
|
|
if (color.equals("silver")) {
|
|
|
|
color = "light_gray";
|
|
|
|
}
|
|
|
|
return "minecraft:" + color + "_concrete";
|
|
|
|
}
|
|
|
|
if (bedrockIdentifier.equals("minecraft:shulker_box")) {
|
|
|
|
String color = (String) statesBuilder.remove("color");
|
|
|
|
if (color.equals("silver")) {
|
|
|
|
color = "light_gray";
|
|
|
|
}
|
|
|
|
return "minecraft:" + color + "_shulker_box";
|
|
|
|
}
|
|
|
|
if (bedrockIdentifier.equals("minecraft:observer")) {
|
|
|
|
int direction = (int) statesBuilder.remove("facing_direction");
|
|
|
|
statesBuilder.putString("minecraft:facing_direction", switch (direction) {
|
2023-07-12 17:21:29 +00:00
|
|
|
case 0 -> "down";
|
|
|
|
case 1 -> "up";
|
2023-07-11 23:17:01 +00:00
|
|
|
case 2 -> "north";
|
2023-07-12 17:21:29 +00:00
|
|
|
case 3 -> "south";
|
|
|
|
case 4 -> "west";
|
|
|
|
default -> "east";
|
2023-07-11 23:17:01 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2022-08-07 16:09:54 +00:00
|
|
|
ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> blockMappers = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
|
2023-06-07 14:44:18 +00:00
|
|
|
.put(ObjectIntPair.of("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()), emptyMapper)
|
2023-07-11 23:17:01 +00:00
|
|
|
.put(ObjectIntPair.of("1_20_10", Bedrock_v594.CODEC.getProtocolVersion()), concreteAndShulkerBoxMapper)
|
2022-11-29 02:53:17 +00:00
|
|
|
.build();
|
2022-08-07 16:09:54 +00:00
|
|
|
|
2023-04-07 18:08:22 +00:00
|
|
|
// We can keep this strong as nothing should be garbage collected
|
|
|
|
// Safe to intern since Cloudburst NBT is immutable
|
|
|
|
Interner<NbtMap> statesInterner = Interners.newStrongInterner();
|
|
|
|
|
2022-08-07 16:09:54 +00:00
|
|
|
for (Map.Entry<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> palette : blockMappers.entrySet()) {
|
2023-08-21 23:04:08 +00:00
|
|
|
int protocolVersion = palette.getKey().valueInt();
|
|
|
|
List<NbtMap> vanillaBlockStates;
|
|
|
|
List<NbtMap> blockStates;
|
2021-12-03 16:01:06 +00:00
|
|
|
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key()));
|
2023-08-21 23:04:08 +00:00
|
|
|
NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) {
|
2021-07-13 01:19:40 +00:00
|
|
|
NbtMap blockPalette = (NbtMap) nbtInputStream.readTag();
|
2023-08-21 23:04:08 +00:00
|
|
|
|
|
|
|
vanillaBlockStates = new ArrayList<>(blockPalette.getList("blocks", NbtType.COMPOUND));
|
|
|
|
for (int i = 0; i < vanillaBlockStates.size(); i++) {
|
|
|
|
NbtMapBuilder builder = vanillaBlockStates.get(i).toBuilder();
|
2023-09-06 18:19:51 +00:00
|
|
|
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
|
2023-08-21 23:04:08 +00:00
|
|
|
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
|
|
|
|
builder.remove("network_id"); // Added in 1.19.80 - ????
|
|
|
|
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
|
|
|
|
vanillaBlockStates.set(i, builder.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
blockStates = new ArrayList<>(vanillaBlockStates);
|
2021-07-13 01:19:40 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
throw new AssertionError("Unable to get blocks from runtime block states", e);
|
|
|
|
}
|
2023-08-21 23:04:08 +00:00
|
|
|
|
|
|
|
List<BlockPropertyData> customBlockProperties = new ArrayList<>();
|
|
|
|
List<NbtMap> customBlockStates = new ArrayList<>();
|
|
|
|
List<CustomBlockState> customExtBlockStates = new ArrayList<>();
|
|
|
|
int[] remappedVanillaIds = new int[0];
|
|
|
|
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
|
|
|
|
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
|
|
|
|
customBlockProperties.add(CustomBlockRegistryPopulator.generateBlockPropertyData(customBlock, protocolVersion));
|
2023-09-06 18:19:51 +00:00
|
|
|
CustomBlockRegistryPopulator.generateCustomBlockStates(customBlock, customBlockStates, customExtBlockStates);
|
2023-08-21 23:04:08 +00:00
|
|
|
}
|
|
|
|
blockStates.addAll(customBlockStates);
|
|
|
|
GeyserImpl.getInstance().getLogger().debug("Added " + customBlockStates.size() + " custom block states to v" + protocolVersion + " palette.");
|
|
|
|
|
|
|
|
// The palette is sorted by the FNV1 64-bit hash of the name
|
|
|
|
blockStates.sort((a, b) -> Long.compareUnsigned(fnv164(a.getString("name")), fnv164(b.getString("name"))));
|
|
|
|
}
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
// 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
|
2023-08-21 23:04:08 +00:00
|
|
|
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);
|
2021-07-13 01:19:40 +00:00
|
|
|
if (blockStateOrderedMap.containsKey(tag)) {
|
|
|
|
throw new AssertionError("Duplicate block states in Bedrock palette: " + tag);
|
|
|
|
}
|
2022-12-23 21:18:48 +00:00
|
|
|
GeyserBedrockBlock block = new GeyserBedrockBlock(i, tag);
|
|
|
|
blockStateOrderedMap.put(tag, block);
|
2023-04-25 17:29:05 +00:00
|
|
|
bedrockRuntimeMap[i] = block;
|
2023-08-21 23:04:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions = Object2ObjectMaps.emptyMap();
|
|
|
|
Int2ObjectMap<GeyserBedrockBlock> extendedCollisionBoxes = new Int2ObjectOpenHashMap<>();
|
|
|
|
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
|
|
|
|
customBlockStateDefinitions = new Object2ObjectOpenHashMap<>(customExtBlockStates.size());
|
|
|
|
for (int i = 0; i < customExtBlockStates.size(); i++) {
|
|
|
|
NbtMap tag = customBlockStates.get(i);
|
|
|
|
CustomBlockState blockState = customExtBlockStates.get(i);
|
|
|
|
GeyserBedrockBlock bedrockBlock = blockStateOrderedMap.get(tag);
|
|
|
|
customBlockStateDefinitions.put(blockState, bedrockBlock);
|
|
|
|
|
|
|
|
Set<Integer> extendedCollisionjavaIds = BlockRegistries.EXTENDED_COLLISION_BOXES.getOrDefault(blockState.block(), null);
|
|
|
|
if (extendedCollisionjavaIds != null) {
|
|
|
|
for (int javaId : extendedCollisionjavaIds) {
|
|
|
|
extendedCollisionBoxes.put(javaId, bedrockBlock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
remappedVanillaIds = new int[vanillaBlockStates.size()];
|
|
|
|
for (int i = 0; i < vanillaBlockStates.size(); i++) {
|
|
|
|
GeyserBedrockBlock bedrockBlock = blockStateOrderedMap.get(vanillaBlockStates.get(i));
|
|
|
|
remappedVanillaIds[i] = bedrockBlock != null ? bedrockBlock.getRuntimeId() : -1;
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-21 23:04:08 +00:00
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
int javaRuntimeId = -1;
|
2022-10-30 00:23:21 +00:00
|
|
|
|
2022-12-23 21:18:48 +00:00
|
|
|
GeyserBedrockBlock airDefinition = null;
|
2022-10-30 00:23:21 +00:00
|
|
|
BlockDefinition commandBlockDefinition = null;
|
|
|
|
BlockDefinition waterDefinition = null;
|
|
|
|
BlockDefinition movingBlockDefinition = null;
|
2021-07-13 01:19:40 +00:00
|
|
|
Iterator<Map.Entry<String, JsonNode>> blocksIterator = BLOCKS_JSON.fields();
|
|
|
|
|
2022-08-07 16:09:54 +00:00
|
|
|
BiFunction<String, NbtMapBuilder, String> stateMapper = blockMappers.getOrDefault(palette.getKey(), emptyMapper);
|
2021-07-13 01:19:40 +00:00
|
|
|
|
2023-09-09 17:23:19 +00:00
|
|
|
GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE];
|
|
|
|
GeyserBedrockBlock[] javaToVanillaBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE];
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
Map<String, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
|
2022-10-30 00:23:21 +00:00
|
|
|
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
|
2021-07-13 01:19:40 +00:00
|
|
|
|
2022-10-30 00:23:21 +00:00
|
|
|
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
|
2021-09-04 17:36:09 +00:00
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder();
|
|
|
|
while (blocksIterator.hasNext()) {
|
|
|
|
javaRuntimeId++;
|
|
|
|
Map.Entry<String, JsonNode> entry = blocksIterator.next();
|
|
|
|
String javaId = entry.getKey();
|
2023-09-06 18:19:51 +00:00
|
|
|
GeyserBedrockBlock vanillaBedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateMapper));
|
2023-08-21 23:04:08 +00:00
|
|
|
|
|
|
|
GeyserBedrockBlock bedrockDefinition;
|
|
|
|
CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(javaRuntimeId);
|
|
|
|
if (blockStateOverride == null) {
|
|
|
|
bedrockDefinition = vanillaBedrockDefinition;
|
|
|
|
if (bedrockDefinition == null) {
|
|
|
|
throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built NBT tag: \n" +
|
2023-09-06 18:19:51 +00:00
|
|
|
palette.getKey().key() + buildBedrockState(entry.getValue(), stateMapper));
|
2023-08-21 23:04:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bedrockDefinition = customBlockStateDefinitions.get(blockStateOverride);
|
|
|
|
if (bedrockDefinition == null) {
|
|
|
|
throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Custom block override: \n" +
|
|
|
|
blockStateOverride);
|
|
|
|
}
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (javaId) {
|
2022-10-30 00:23:21 +00:00
|
|
|
case "minecraft:air" -> airDefinition = bedrockDefinition;
|
|
|
|
case "minecraft:water[level=0]" -> waterDefinition = bedrockDefinition;
|
|
|
|
case "minecraft:command_block[conditional=false,facing=north]" -> commandBlockDefinition = bedrockDefinition;
|
|
|
|
case "minecraft:moving_piston[facing=north,type=normal]" -> movingBlockDefinition = bedrockDefinition;
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 17:36:09 +00:00
|
|
|
if (javaId.contains("jigsaw")) {
|
2022-10-30 00:23:21 +00:00
|
|
|
jigsawDefinitions.add(bedrockDefinition);
|
2021-09-04 17:36:09 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
boolean waterlogged = entry.getKey().contains("waterlogged=true")
|
|
|
|
|| javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
|
|
|
|
|
|
|
|
if (waterlogged) {
|
|
|
|
int finalJavaRuntimeId = javaRuntimeId;
|
2023-04-07 01:47:37 +00:00
|
|
|
BlockRegistries.WATERLOGGED.register(set -> set.set(finalJavaRuntimeId));
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:03:41 +00:00
|
|
|
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey());
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
// Get the tag needed for non-empty flower pots
|
|
|
|
if (entry.getValue().get("pottable") != null) {
|
2023-08-21 23:04:08 +00:00
|
|
|
flowerPotBlocks.put(cleanJavaIdentifier.intern(), blockStates.get(bedrockDefinition.getRuntimeId()));
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2023-08-21 23:04:08 +00:00
|
|
|
javaToVanillaBedrockBlocks[javaRuntimeId] = vanillaBedrockDefinition;
|
2022-10-30 00:23:21 +00:00
|
|
|
javaToBedrockBlocks[javaRuntimeId] = bedrockDefinition;
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2022-10-30 00:23:21 +00:00
|
|
|
if (commandBlockDefinition == null) {
|
2021-07-13 01:19:40 +00:00
|
|
|
throw new AssertionError("Unable to find command block in palette");
|
|
|
|
}
|
|
|
|
|
2022-10-30 00:23:21 +00:00
|
|
|
builder.commandBlock(commandBlockDefinition);
|
|
|
|
|
|
|
|
if (waterDefinition == null) {
|
2021-07-13 01:19:40 +00:00
|
|
|
throw new AssertionError("Unable to find water in palette");
|
|
|
|
}
|
2022-10-30 00:23:21 +00:00
|
|
|
builder.bedrockWater(waterDefinition);
|
2021-07-13 01:19:40 +00:00
|
|
|
|
2022-10-30 00:23:21 +00:00
|
|
|
if (airDefinition == null) {
|
2021-07-13 01:19:40 +00:00
|
|
|
throw new AssertionError("Unable to find air in palette");
|
|
|
|
}
|
2022-10-30 00:23:21 +00:00
|
|
|
builder.bedrockAir(airDefinition);
|
2021-07-13 01:19:40 +00:00
|
|
|
|
2022-10-30 00:23:21 +00:00
|
|
|
if (movingBlockDefinition == null) {
|
2021-09-10 01:20:25 +00:00
|
|
|
throw new AssertionError("Unable to find moving block in palette");
|
|
|
|
}
|
2022-10-30 00:23:21 +00:00
|
|
|
builder.bedrockMovingBlock(movingBlockDefinition);
|
2021-09-10 01:20:25 +00:00
|
|
|
|
2023-08-21 23:04:08 +00:00
|
|
|
Map<JavaBlockState, CustomBlockState> nonVanillaStateOverrides = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get();
|
|
|
|
if (nonVanillaStateOverrides.size() > 0) {
|
|
|
|
// First ensure all non vanilla runtime IDs at minimum are air in case they aren't consecutive
|
2023-09-09 17:23:19 +00:00
|
|
|
Arrays.fill(javaToVanillaBedrockBlocks, MIN_CUSTOM_RUNTIME_ID, javaToVanillaBedrockBlocks.length, airDefinition);
|
|
|
|
Arrays.fill(javaToBedrockBlocks, MIN_CUSTOM_RUNTIME_ID, javaToBedrockBlocks.length, airDefinition);
|
2023-08-21 23:04:08 +00:00
|
|
|
|
|
|
|
for (Map.Entry<JavaBlockState, CustomBlockState> entry : nonVanillaStateOverrides.entrySet()) {
|
|
|
|
GeyserBedrockBlock bedrockDefinition = customBlockStateDefinitions.get(entry.getValue());
|
|
|
|
if (bedrockDefinition == null) {
|
|
|
|
GeyserImpl.getInstance().getLogger().warning("Unable to find custom block for " + entry.getValue());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
JavaBlockState javaState = entry.getKey();
|
|
|
|
int stateRuntimeId = javaState.javaId();
|
|
|
|
|
|
|
|
boolean waterlogged = javaState.waterlogged();
|
|
|
|
|
|
|
|
if (waterlogged) {
|
|
|
|
BlockRegistries.WATERLOGGED.register(set -> set.set(stateRuntimeId));
|
|
|
|
}
|
|
|
|
|
|
|
|
javaToVanillaBedrockBlocks[stateRuntimeId] = bedrockDefinition; // TODO: Check this?
|
|
|
|
javaToBedrockBlocks[stateRuntimeId] = bedrockDefinition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
// Loop around again to find all item frame runtime IDs
|
2022-10-30 00:23:21 +00:00
|
|
|
Object2ObjectMaps.fastForEach(blockStateOrderedMap, entry -> {
|
2021-07-13 01:19:40 +00:00
|
|
|
String name = entry.getKey().getString("name");
|
|
|
|
if (name.equals("minecraft:frame") || name.equals("minecraft:glow_frame")) {
|
2022-10-30 00:23:21 +00:00
|
|
|
itemFrames.put(entry.getKey(), entry.getValue());
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
2022-10-30 00:23:21 +00:00
|
|
|
});
|
|
|
|
|
2023-09-06 18:19:51 +00:00
|
|
|
BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.bedrockRuntimeMap(bedrockRuntimeMap)
|
2021-09-04 17:45:32 +00:00
|
|
|
.javaToBedrockBlocks(javaToBedrockBlocks)
|
2023-08-21 23:04:08 +00:00
|
|
|
.javaToVanillaBedrockBlocks(javaToVanillaBedrockBlocks)
|
2023-07-11 23:17:01 +00:00
|
|
|
.stateDefinitionMap(blockStateOrderedMap)
|
2021-07-13 01:19:40 +00:00
|
|
|
.itemFrames(itemFrames)
|
|
|
|
.flowerPotBlocks(flowerPotBlocks)
|
2022-10-30 00:23:21 +00:00
|
|
|
.jigsawStates(jigsawDefinitions)
|
2023-08-21 23:04:08 +00:00
|
|
|
.remappedVanillaIds(remappedVanillaIds)
|
|
|
|
.blockProperties(customBlockProperties)
|
|
|
|
.customBlockStateDefinitions(customBlockStateDefinitions)
|
|
|
|
.extendedCollisionBoxes(extendedCollisionBoxes)
|
2021-07-13 01:19:40 +00:00
|
|
|
.build());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void registerJavaBlocks() {
|
|
|
|
JsonNode blocksJson;
|
2021-12-03 16:01:06 +00:00
|
|
|
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/blocks.json")) {
|
2021-11-20 21:34:30 +00:00
|
|
|
blocksJson = GeyserImpl.JSON_MAPPER.readTree(stream);
|
2021-07-13 01:19:40 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
throw new AssertionError("Unable to load Java block mappings", e);
|
|
|
|
}
|
|
|
|
|
2023-09-09 17:23:19 +00:00
|
|
|
JAVA_BLOCKS_SIZE = blocksJson.size();
|
2023-08-21 23:04:08 +00:00
|
|
|
|
|
|
|
if (BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().size() > 0) {
|
2023-09-09 17:23:19 +00:00
|
|
|
MIN_CUSTOM_RUNTIME_ID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).get().javaId();
|
|
|
|
int maxCustomRuntimeID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().max(Comparator.comparing(JavaBlockState::javaId)).get().javaId();
|
2023-08-21 23:04:08 +00:00
|
|
|
|
2023-09-09 17:23:19 +00:00
|
|
|
if (MIN_CUSTOM_RUNTIME_ID < blocksJson.size()) {
|
|
|
|
throw new RuntimeException("Non vanilla custom block state overrides runtime ID must start after the last vanilla block state (" + JAVA_BLOCKS_SIZE + ")");
|
2023-08-21 23:04:08 +00:00
|
|
|
}
|
|
|
|
|
2023-09-09 17:23:19 +00:00
|
|
|
JAVA_BLOCKS_SIZE = maxCustomRuntimeID + 1; // Runtime ids start at 0, so we need to add 1
|
2023-08-21 23:04:08 +00:00
|
|
|
}
|
|
|
|
|
2023-09-09 17:23:19 +00:00
|
|
|
BlockRegistries.JAVA_BLOCKS.set(new BlockMapping[JAVA_BLOCKS_SIZE]); // Set array size to number of blockstates
|
2021-09-04 17:36:09 +00:00
|
|
|
|
2021-12-08 01:05:44 +00:00
|
|
|
Deque<String> cleanIdentifiers = new ArrayDeque<>();
|
2021-07-22 03:18:31 +00:00
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
int javaRuntimeId = -1;
|
|
|
|
int cobwebBlockId = -1;
|
|
|
|
int furnaceRuntimeId = -1;
|
|
|
|
int furnaceLitRuntimeId = -1;
|
2021-09-10 01:20:25 +00:00
|
|
|
int honeyBlockRuntimeId = -1;
|
|
|
|
int slimeBlockRuntimeId = -1;
|
2021-07-13 01:19:40 +00:00
|
|
|
int spawnerRuntimeId = -1;
|
|
|
|
int uniqueJavaId = -1;
|
|
|
|
int waterRuntimeId = -1;
|
|
|
|
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocksJson.fields();
|
|
|
|
while (blocksIterator.hasNext()) {
|
|
|
|
javaRuntimeId++;
|
|
|
|
Map.Entry<String, JsonNode> entry = blocksIterator.next();
|
|
|
|
String javaId = entry.getKey();
|
|
|
|
|
|
|
|
// TODO fix this, (no block should have a null hardness)
|
|
|
|
BlockMapping.BlockMappingBuilder builder = BlockMapping.builder();
|
|
|
|
JsonNode hardnessNode = entry.getValue().get("block_hardness");
|
|
|
|
if (hardnessNode != null) {
|
2022-09-27 23:24:50 +00:00
|
|
|
builder.hardness(hardnessNode.floatValue());
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JsonNode canBreakWithHandNode = entry.getValue().get("can_break_with_hand");
|
|
|
|
if (canBreakWithHandNode != null) {
|
|
|
|
builder.canBreakWithHand(canBreakWithHandNode.booleanValue());
|
|
|
|
} else {
|
|
|
|
builder.canBreakWithHand(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
JsonNode collisionIndexNode = entry.getValue().get("collision_index");
|
|
|
|
if (hardnessNode != null) {
|
|
|
|
builder.collisionIndex(collisionIndexNode.intValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
JsonNode pickItemNode = entry.getValue().get("pick_item");
|
|
|
|
if (pickItemNode != null) {
|
2021-08-12 00:16:10 +00:00
|
|
|
builder.pickItem(pickItemNode.textValue().intern());
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2022-05-26 22:09:27 +00:00
|
|
|
if (javaId.equals("minecraft:obsidian") || javaId.equals("minecraft:crying_obsidian") || javaId.startsWith("minecraft:respawn_anchor") || javaId.startsWith("minecraft:reinforced_deepslate")) {
|
2021-09-10 01:20:25 +00:00
|
|
|
builder.pistonBehavior(PistonBehavior.BLOCK);
|
|
|
|
} else {
|
|
|
|
JsonNode pistonBehaviorNode = entry.getValue().get("piston_behavior");
|
|
|
|
if (pistonBehaviorNode != null) {
|
|
|
|
builder.pistonBehavior(PistonBehavior.getByName(pistonBehaviorNode.textValue()));
|
|
|
|
} else {
|
|
|
|
builder.pistonBehavior(PistonBehavior.NORMAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JsonNode hasBlockEntityNode = entry.getValue().get("has_block_entity");
|
|
|
|
if (hasBlockEntityNode != null) {
|
|
|
|
builder.isBlockEntity(hasBlockEntityNode.booleanValue());
|
|
|
|
} else {
|
|
|
|
builder.isBlockEntity(false);
|
|
|
|
}
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
|
|
|
|
|
2021-07-17 13:43:25 +00:00
|
|
|
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey());
|
2021-07-13 01:19:40 +00:00
|
|
|
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
|
|
|
|
|
2021-12-08 01:05:44 +00:00
|
|
|
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
|
2021-07-13 01:19:40 +00:00
|
|
|
uniqueJavaId++;
|
2021-08-12 00:16:10 +00:00
|
|
|
cleanIdentifiers.add(cleanJavaIdentifier.intern());
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2021-07-17 13:33:38 +00:00
|
|
|
builder.javaIdentifier(javaId);
|
|
|
|
builder.javaBlockId(uniqueJavaId);
|
|
|
|
|
2023-04-07 01:47:37 +00:00
|
|
|
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, javaRuntimeId);
|
2021-07-17 13:33:38 +00:00
|
|
|
BlockRegistries.JAVA_BLOCKS.register(javaRuntimeId, builder.build());
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
// Keeping this here since this is currently unchanged between versions
|
2021-10-12 01:14:06 +00:00
|
|
|
// It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions
|
|
|
|
BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern());
|
2021-07-13 01:19:40 +00:00
|
|
|
|
2023-03-19 15:30:56 +00:00
|
|
|
if (javaId.contains("cobweb")) {
|
2021-07-13 01:19:40 +00:00
|
|
|
cobwebBlockId = uniqueJavaId;
|
|
|
|
|
|
|
|
} else if (javaId.startsWith("minecraft:furnace[facing=north")) {
|
|
|
|
if (javaId.contains("lit=true")) {
|
|
|
|
furnaceLitRuntimeId = javaRuntimeId;
|
|
|
|
} else {
|
|
|
|
furnaceRuntimeId = javaRuntimeId;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (javaId.startsWith("minecraft:spawner")) {
|
|
|
|
spawnerRuntimeId = javaRuntimeId;
|
|
|
|
|
|
|
|
} else if ("minecraft:water[level=0]".equals(javaId)) {
|
|
|
|
waterRuntimeId = javaRuntimeId;
|
2021-09-10 01:20:25 +00:00
|
|
|
} else if (javaId.equals("minecraft:honey_block")) {
|
|
|
|
honeyBlockRuntimeId = javaRuntimeId;
|
|
|
|
} else if (javaId.equals("minecraft:slime_block")) {
|
|
|
|
slimeBlockRuntimeId = javaRuntimeId;
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cobwebBlockId == -1) {
|
|
|
|
throw new AssertionError("Unable to find cobwebs in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_COBWEB_ID = cobwebBlockId;
|
|
|
|
|
|
|
|
if (furnaceRuntimeId == -1) {
|
|
|
|
throw new AssertionError("Unable to find furnace in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_FURNACE_ID = furnaceRuntimeId;
|
|
|
|
|
|
|
|
if (furnaceLitRuntimeId == -1) {
|
|
|
|
throw new AssertionError("Unable to find lit furnace in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_FURNACE_LIT_ID = furnaceLitRuntimeId;
|
2021-09-10 01:20:25 +00:00
|
|
|
|
|
|
|
if (honeyBlockRuntimeId == -1) {
|
|
|
|
throw new AssertionError("Unable to find honey block in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_HONEY_BLOCK_ID = honeyBlockRuntimeId;
|
|
|
|
|
|
|
|
if (slimeBlockRuntimeId == -1) {
|
|
|
|
throw new AssertionError("Unable to find slime block in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_SLIME_BLOCK_ID = slimeBlockRuntimeId;
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
if (spawnerRuntimeId == -1) {
|
|
|
|
throw new AssertionError("Unable to find spawner in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_SPAWNER_ID = spawnerRuntimeId;
|
|
|
|
|
|
|
|
if (waterRuntimeId == -1) {
|
|
|
|
throw new AssertionError("Unable to find Java water in palette");
|
|
|
|
}
|
|
|
|
BlockStateValues.JAVA_WATER_ID = waterRuntimeId;
|
|
|
|
|
2023-08-21 23:04:08 +00:00
|
|
|
if (BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().size() > 0) {
|
|
|
|
Set<Integer> usedNonVanillaRuntimeIDs = new HashSet<>();
|
|
|
|
|
|
|
|
for (JavaBlockState javaBlockState : BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet()) {
|
|
|
|
if (!usedNonVanillaRuntimeIDs.add(javaBlockState.javaId())) {
|
|
|
|
throw new RuntimeException("Duplicate runtime ID " + javaBlockState.javaId() + " for non vanilla Java block state " + javaBlockState.identifier());
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomBlockState customBlockState = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().get(javaBlockState);
|
|
|
|
|
|
|
|
String javaId = javaBlockState.identifier();
|
|
|
|
int stateRuntimeId = javaBlockState.javaId();
|
|
|
|
BlockMapping blockMapping = BlockMapping.builder()
|
|
|
|
.canBreakWithHand(javaBlockState.canBreakWithHand())
|
|
|
|
.pickItem(javaBlockState.pickItem())
|
|
|
|
.isNonVanilla(true)
|
|
|
|
.javaIdentifier(javaId)
|
|
|
|
.javaBlockId(javaBlockState.stateGroupId())
|
|
|
|
.hardness(javaBlockState.blockHardness())
|
|
|
|
.pistonBehavior(javaBlockState.pistonBehavior() == null ? PistonBehavior.NORMAL : PistonBehavior.getByName(javaBlockState.pistonBehavior()))
|
|
|
|
.isBlockEntity(javaBlockState.hasBlockEntity())
|
|
|
|
.build();
|
|
|
|
|
|
|
|
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier());
|
|
|
|
String bedrockIdentifier = customBlockState.block().identifier();
|
|
|
|
|
|
|
|
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
|
|
|
|
uniqueJavaId++;
|
|
|
|
cleanIdentifiers.add(cleanJavaIdentifier.intern());
|
|
|
|
}
|
|
|
|
|
|
|
|
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId);
|
|
|
|
BlockRegistries.JAVA_BLOCKS.register(stateRuntimeId, blockMapping);
|
|
|
|
|
|
|
|
// Keeping this here since this is currently unchanged between versions
|
|
|
|
// It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions
|
|
|
|
BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-08 01:05:44 +00:00
|
|
|
BlockRegistries.CLEAN_JAVA_IDENTIFIERS.set(cleanIdentifiers.toArray(new String[0]));
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
BLOCKS_JSON = blocksJson;
|
2022-06-24 20:48:28 +00:00
|
|
|
|
|
|
|
JsonNode blockInteractionsJson;
|
|
|
|
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/interactions.json")) {
|
|
|
|
blockInteractionsJson = GeyserImpl.JSON_MAPPER.readTree(stream);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new AssertionError("Unable to load Java block interaction mappings", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
BlockRegistries.INTERACTIVE.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("always_consumes")));
|
|
|
|
BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("requires_may_build")));
|
|
|
|
}
|
|
|
|
|
2023-04-07 01:47:37 +00:00
|
|
|
private static BitSet toBlockStateSet(ArrayNode node) {
|
|
|
|
BitSet blockStateSet = new BitSet(node.size());
|
2022-06-24 20:48:28 +00:00
|
|
|
for (JsonNode javaIdentifier : node) {
|
2023-04-07 01:47:37 +00:00
|
|
|
blockStateSet.set(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getInt(javaIdentifier.textValue()));
|
2022-06-24 20:48:28 +00:00
|
|
|
}
|
|
|
|
return blockStateSet;
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
2023-09-06 18:19:51 +00:00
|
|
|
private static NbtMap buildBedrockState(JsonNode node, BiFunction<String, NbtMapBuilder, String> statesMapper) {
|
2021-07-13 01:19:40 +00:00
|
|
|
NbtMapBuilder tagBuilder = NbtMap.builder();
|
|
|
|
String bedrockIdentifier = node.get("bedrock_identifier").textValue();
|
2023-09-06 18:19:51 +00:00
|
|
|
tagBuilder.putString("name", bedrockIdentifier);
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
NbtMapBuilder statesBuilder = NbtMap.builder();
|
|
|
|
|
|
|
|
// check for states
|
2023-04-07 01:47:37 +00:00
|
|
|
JsonNode states = node.get("bedrock_states");
|
|
|
|
if (states != null) {
|
|
|
|
Iterator<Map.Entry<String, JsonNode>> statesIterator = states.fields();
|
2021-07-13 01:19:40 +00:00
|
|
|
|
|
|
|
while (statesIterator.hasNext()) {
|
|
|
|
Map.Entry<String, JsonNode> stateEntry = statesIterator.next();
|
|
|
|
JsonNode stateValue = stateEntry.getValue();
|
|
|
|
switch (stateValue.getNodeType()) {
|
2021-09-10 18:10:56 +00:00
|
|
|
case BOOLEAN -> statesBuilder.putBoolean(stateEntry.getKey(), stateValue.booleanValue());
|
|
|
|
case STRING -> statesBuilder.putString(stateEntry.getKey(), stateValue.textValue());
|
|
|
|
case NUMBER -> statesBuilder.putInt(stateEntry.getKey(), stateValue.intValue());
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
String newIdentifier = statesMapper.apply(bedrockIdentifier, statesBuilder);
|
|
|
|
if (newIdentifier != null) {
|
|
|
|
tagBuilder.putString("name", newIdentifier);
|
|
|
|
}
|
|
|
|
tagBuilder.put("states", statesBuilder.build());
|
|
|
|
return tagBuilder.build();
|
|
|
|
}
|
2023-08-21 23:04:08 +00:00
|
|
|
|
|
|
|
private static final long FNV1_64_OFFSET_BASIS = 0xcbf29ce484222325L;
|
|
|
|
private static final long FNV1_64_PRIME = 1099511628211L;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hashes a string using the FNV-1a 64-bit algorithm.
|
|
|
|
*
|
|
|
|
* @param str The string to hash
|
|
|
|
* @return The hashed string
|
|
|
|
*/
|
|
|
|
private static long fnv164(String str) {
|
|
|
|
long hash = FNV1_64_OFFSET_BASIS;
|
|
|
|
for (byte b : str.getBytes(StandardCharsets.UTF_8)) {
|
|
|
|
hash *= FNV1_64_PRIME;
|
|
|
|
hash ^= b;
|
|
|
|
}
|
|
|
|
return hash;
|
|
|
|
}
|
2021-07-13 01:19:40 +00:00
|
|
|
}
|