Minor cleanup and javadocs

This commit is contained in:
Joshua Castle 2023-01-15 16:27:28 -08:00
parent bfdebadacc
commit cc2e409f05
No known key found for this signature in database
GPG key ID: F674F38216C35D5D
5 changed files with 206 additions and 14 deletions

View file

@ -41,7 +41,7 @@ public final class Constants {
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
public static final String GEYSER_NAMESPACE = "geyser:";
public static final String GEYSER_NAMESPACE = "geyser_custom";
static {
URI wsUri = null;

View file

@ -94,7 +94,7 @@ public class GeyserCustomBlockData implements CustomBlockData {
@Override
public @NonNull String identifier() {
return Constants.GEYSER_NAMESPACE + name;
return Constants.GEYSER_NAMESPACE + ":" + name;
}
@Override

View file

@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.base.CharMatcher;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
@ -178,6 +179,13 @@ public class MappingsReader_v1 extends MappingsReader {
return customItemData.build();
}
/**
* Read a block mapping entry from a JSON node and java identifier
* @param identifier The java identifier of the block
* @param node The {@link JsonNode} containing the block mapping entry
* @return The {@link CustomBlockMapping} record to be read by {@link org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator#registerCustomBedrockBlocks}
* @throws InvalidCustomMappingsFileException If the JSON node is invalid
*/
@Override
public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException {
if (node == null || !node.isObject()) {
@ -189,6 +197,7 @@ public class MappingsReader_v1 extends MappingsReader {
throw new InvalidCustomMappingsFileException("A block entry has no name");
}
// If this is true, we will only register the states the user has specified rather than all of the blocks possible states
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean();
JsonNode stateOverrides = node.get("state_overrides");
@ -199,6 +208,7 @@ public class MappingsReader_v1 extends MappingsReader {
List<String> stateKeys = new ArrayList<>();
// Add the block's identifier to the object so we can use the resulting string to search the block mappings
if (stateOverrides != null && stateOverrides.isObject()) {
Iterator<Map.Entry<String, JsonNode>> fields = stateOverrides.fields();
while (fields.hasNext()) {
@ -206,54 +216,92 @@ public class MappingsReader_v1 extends MappingsReader {
}
}
// Find all the default states for the block we wish to override
List<String> defaultStates = List.copyOf(BlockRegistries.JAVA_IDENTIFIERS.get().keySet())
.stream()
.filter(s -> s.startsWith(identifier + "["))
.collect(Collectors.toList());
// If no states were found, the block must only have one state, so we add its plain identifier
if (defaultStates.isEmpty()) defaultStates.add(identifier);
// Create the data for the overall block
CustomBlockDataBuilder customBlockDataBuilder = new CustomBlockDataBuilder();
customBlockDataBuilder.name(name)
// We pass in the first state and just use the hitbox from that as the default
// Each state will have its own so this is fine
.components(createCustomBlockComponents(node, defaultStates.get(0), name))
// We must create permutation for every state override
.permutations(createCustomBlockPermutations(stateOverrides, identifier, name))
.booleanProperty("geyser_custom:default");
// This property is use to display the default state when onlyOverrideStates is false
.booleanProperty(String.format("%s:default", Constants.GEYSER_NAMESPACE));
// We need to have three property type maps, one for each type of block property
// Each contains the properties this block has for that type and its possible values, except for boolean since the values must be true/false
// If we are only overriding states, we pass in only the state keys supplied in the mapping
// Otherwise, we pass in all possible states for the block
BlockPropertyTypeMaps blockPropertyTypeMaps = createBlockPropertyTypeMaps(onlyOverrideStates ? stateKeys : defaultStates);
blockPropertyTypeMaps.stringValuesMap().forEach((key, value) -> customBlockDataBuilder.stringProperty(key, new ArrayList<String>(value)));
blockPropertyTypeMaps.intValuesMap().forEach((key, value) -> customBlockDataBuilder.intProperty(key, new ArrayList<Integer>(value)));
blockPropertyTypeMaps.booleanValuesSet().forEach((value) -> customBlockDataBuilder.booleanProperty(value));
// Finally, build the custom block data
CustomBlockData customBlockData = customBlockDataBuilder.build();
Map<String, CustomBlockState> states = createCustomBlockStatesMap(identifier, stateKeys, defaultStates, onlyOverrideStates, customBlockData,
// Create a map of the custom block states for this block, which contains the full state identifier mapped to the custom block state data
Map<String, CustomBlockState> states = createCustomBlockStatesMap(stateKeys, defaultStates, onlyOverrideStates, customBlockData,
blockPropertyTypeMaps.stateKeyStrings(), blockPropertyTypeMaps.stateKeyInts(), blockPropertyTypeMaps.stateKeyBools());
// Create the custom block mapping record to be passed into the custom block registry populator
return new CustomBlockMapping(customBlockData, states, identifier, !onlyOverrideStates);
}
/**
* Creates a list of {@link CustomBlockPermutation} from the given mappings node containing permutations, java identifier, and custom block name
* @param node an {@link JsonNode} from the mappings file containing the permutations
* @param identifier the java identifier of the block
* @param name the name of the custom block
* @return the list of custom block permutations
*/
private List<CustomBlockPermutation> createCustomBlockPermutations(JsonNode node, String identifier, String name) {
List<CustomBlockPermutation> permutations = new ArrayList<>();
// Create a custom block permutation record for each permutation passed into the mappings for the given block
if (node != null && node.isObject()) {
node.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode value = entry.getValue();
if (value.isObject()) {
// Each permutation has its own components, which override the base components when explicitly set
// Based on the input states, we construct a molang query that will evaluate to true when the block properties corresponding to the state are active
permutations.add(new CustomBlockPermutation(createCustomBlockComponents(value, (identifier + key), name), createCustomBlockPropertyQuery(key)));
}
});
}
permutations.add(new CustomBlockPermutation(new GeyserCustomBlockComponents.CustomBlockComponentsBuilder().build(), "q.block_property('geyser_custom:default') == 1"));
// We also need to create a permutation for the default state of the block with no components
// Functionally, this means the default components will be used
permutations.add(new CustomBlockPermutation(new GeyserCustomBlockComponents.CustomBlockComponentsBuilder().build(), String.format("q.block_property('%s:default') == 1", Constants.GEYSER_NAMESPACE)));
return permutations;
}
private Map<String, CustomBlockState> createCustomBlockStatesMap(String identifier, List<String> stateKeys,List<String> defaultStates, boolean onlyOverrideStates, CustomBlockData customBlockData,
/**
* Create a map of java block state identifiers to {@link CustomBlockState} so that {@link #readBlockMappingEntry} can include it in the {@link CustomBlockMapping} record
* @param stateKeys the list of java block state identifiers explicitly passed in the mappings
* @param defaultStates the list of all possible java block state identifiers for the block
* @param onlyOverrideStates whether or not we are only overriding the states passed in the mappings
* @param customBlockData the {@link CustomBlockData} for the block
* @param stateKeyStrings the map of java block state identifiers to their string properties
* @param stateKeyInts the map of java block state identifiers to their int properties
* @param stateKeyBools the map of java block state identifiers to their boolean properties
* @return the custom block states maps
*/
private Map<String, CustomBlockState> createCustomBlockStatesMap(List<String> stateKeys,List<String> defaultStates, boolean onlyOverrideStates, CustomBlockData customBlockData,
Map<String, Map<String, String>> stateKeyStrings, Map<String, Map<String, Integer>> stateKeyInts, Map<String, Map<String, Boolean>> stateKeyBools) {
Map<String, CustomBlockState> states = new HashMap<>();
// If not only overriding specified states, we must include the default states in the custom block states map
if (!onlyOverrideStates) {
defaultStates.removeAll(stateKeys);
createCustomBlockStates(defaultStates, true, customBlockData, stateKeyStrings, stateKeyInts, stateKeyBools, states);
@ -263,13 +311,25 @@ public class MappingsReader_v1 extends MappingsReader {
return states;
}
/**
* Create the custom block states for the given state keys and append them to the passed states map
* @param stateKeys the list of java block state identifiers
* @param defaultState whether or not this is the default state
* @param customBlockData the {@link CustomBlockData} for the block
* @param stateKeyStrings the map of java block state identifiers to their string properties
* @param stateKeyInts the map of java block state identifiers to their int properties
* @param stateKeyBools the map of java block state identifiers to their boolean properties
* @param states the map of java block state identifiers to their {@link CustomBlockState} to append
*/
private void createCustomBlockStates(List<String> stateKeys, boolean defaultState, CustomBlockData customBlockData,
Map<String, Map<String, String>> stateKeyStrings, Map<String, Map<String, Integer>> stateKeyInts,
Map<String, Map<String, Boolean>> stateKeyBools, Map<String, CustomBlockState> states) {
stateKeys.forEach((key) -> {
CustomBlockState.Builder builder = customBlockData.blockStateBuilder();
builder.booleanProperty("geyser_custom:default", defaultState);
// We always include the default property, which is used to set the default state when onlyOverrideStates is false
builder.booleanProperty(String.format("%s:default", Constants.GEYSER_NAMESPACE), defaultState);
// The properties must be added to the builder seperately for each type
stateKeyStrings.getOrDefault(key, Collections.emptyMap()).forEach((property, stringValue) -> builder.stringProperty(property, stringValue));
stateKeyInts.getOrDefault(key, Collections.emptyMap()).forEach((property, intValue) -> builder.intProperty(property, intValue));
stateKeyBools.getOrDefault(key, Collections.emptyMap()).forEach((property, boolValue) -> builder.booleanProperty(property, boolValue));
@ -280,7 +340,15 @@ public class MappingsReader_v1 extends MappingsReader {
});
}
/**
* Creates a record of {@link BlockPropertyTypeMaps} for the given list of java block state identifiers that are being actively used by the custom block
* @param usedStateKeys the list of java block state identifiers that are being actively used by the custom block
* @return the {@link BlockPropertyTypeMaps} record
*/
private BlockPropertyTypeMaps createBlockPropertyTypeMaps(List<String> usedStateKeys) {
// Each of the three property type has two maps
// The first map is used to store the possible values for each property
// The second map is used to store the value for each property for each state
Map<String, LinkedHashSet<String>> stringValuesMap = new HashMap<>();
Map<String, Map<String, String>> stateKeyStrings = new HashMap<>();
@ -292,14 +360,19 @@ public class MappingsReader_v1 extends MappingsReader {
for (String state : usedStateKeys) {
// No bracket means that there is only one state, so the maps should be empty
if (!state.contains("[")) continue;
// Split the state string into an array containing each property=value pair
String[] pairs = splitStateString(state);
for (String pair : pairs) {
// Get the property and value individually
String[] parts = pair.split("=");
String property = parts[0];
String value = parts[1];
// Figure out what property type we are dealing with
if (value.equals("true") || value.equals("false")) {
booleanValuesSet.add(property);
Map<String, Boolean> propertyMap = stateKeyBools.getOrDefault(state, new HashMap<>());
@ -308,6 +381,7 @@ public class MappingsReader_v1 extends MappingsReader {
} else if (CharMatcher.inRange('0', '9').matchesAllOf(value)) {
int intValue = Integer.parseInt(value);
LinkedHashSet<Integer> values = intValuesMap.get(property);
// Initialize the property to values map if it doesn't exist
if (values == null) {
values = new LinkedHashSet<>();
intValuesMap.put(property, values);
@ -317,7 +391,9 @@ public class MappingsReader_v1 extends MappingsReader {
propertyMap.put(property, intValue);
stateKeyInts.put(state, propertyMap);
} else {
// If it's n not a boolean or int it must be a string
LinkedHashSet<String> values = stringValuesMap.get(property);
// Initialize the property to values map if it doesn't exist
if (values == null) {
values = new LinkedHashSet<>();
stringValuesMap.put(property, values);
@ -329,14 +405,22 @@ public class MappingsReader_v1 extends MappingsReader {
}
}
}
// We should now have all of the maps
return new BlockPropertyTypeMaps(stringValuesMap, stateKeyStrings, intValuesMap, stateKeyInts, booleanValuesSet, stateKeyBools);
}
/**
* Creates a {@link CustomBlockComponents} object for the passed permutation or base block node, java block state identifier, and custom block name
* @param node the permutation or base block {@link JsonNode}
* @param stateKey the java block state identifier
* @param name the custom block name
* @return the {@link CustomBlockComponents} object
*/
private CustomBlockComponents createCustomBlockComponents(JsonNode node, String stateKey, String name) {
// This is needed to find the correct selection box for the given block
int id = BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(stateKey, -1);
CustomBlockComponentsBuilder builder = new CustomBlockComponentsBuilder();
// .friction()
BoxComponent boxComponent = createBoxComponent(id);
builder.selectionBox(boxComponent).collisionBox(boxComponent);
@ -358,6 +442,10 @@ public class MappingsReader_v1 extends MappingsReader {
}
builder.displayName(displayName);
if (node.has("friction")) {
builder.friction(node.get("friction").floatValue());
}
if (node.has("light_emission")) {
builder.lightEmission(node.get("light_emission").asInt());
}
@ -411,6 +499,11 @@ public class MappingsReader_v1 extends MappingsReader {
}
}
// Tags can be applied so that blocks will match return true when queried for the tag
// Potentially useful for resource pack creators
// Ideally we could programatically extract the tags here https://wiki.bedrock.dev/blocks/block-tags.html
// This would let us automatically apply the correct valilla tags to blocks
// However, its worth noting that vanilla tools do not currently honor these tags anyways
if (node.has("tags")) {
JsonNode tags = node.get("tags");
if (tags.isArray()) {
@ -426,6 +519,11 @@ public class MappingsReader_v1 extends MappingsReader {
return components;
}
/**
* Creates the {@link BoxComponent} for the passed collision box index
* @param id the collision box index
* @return the {@link BoxComponent}
*/
private BoxComponent createBoxComponent(int id) {
BoundingBox boundingBox = BlockUtils.getCollision(id).getBoundingBoxes()[0];
@ -433,6 +531,11 @@ public class MappingsReader_v1 extends MappingsReader {
float offsetY = (float) boundingBox.getSizeY() * 8;
float offsetZ = (float) boundingBox.getSizeZ() * 8;
// Unfortunately we need to clamp the values here to a an effective size of one block
// This is quite a pain for anything like fences, as the player can just jump over them
// One possible solution would be to create invisible blocks that we use only for collision box
// These could be placed above the block when a custom block exceeds this limit
// I am hopeful this will be extended slightly since the geometry of blocks can be 1.875^3
float cornerX = clamp((float) boundingBox.getMiddleX() * 16 - 8 - offsetX, -8, 8);
float cornerY = clamp((float) boundingBox.getMiddleY() * 16 - offsetY, 0, 16);
float cornerZ = clamp((float) boundingBox.getMiddleZ() * 16 - 8 - offsetZ, -8, 8);
@ -446,7 +549,15 @@ public class MappingsReader_v1 extends MappingsReader {
return boxComponent;
}
/**
* Creates the {@link MaterialInstance} for the passed material instance node and custom block name
* The name is used as a fallback if no texture is provided by the node
* @param node the material instance node
* @param name the custom block name
* @return the {@link MaterialInstance}
*/
private MaterialInstance createMaterialInstanceComponent(JsonNode node, String name) {
// Set default values, and use what the user provides if they have provided something
String texture = name;
if (node.has("texture")) {
texture = node.get("texture").asText();
@ -470,9 +581,16 @@ public class MappingsReader_v1 extends MappingsReader {
return new MaterialInstance(texture, renderMethod, faceDimming, ambientOcclusion);
}
/**
* Creates the {@link PlacementFilter} for the passed conditions node
* @param node the conditions node
* @return the {@link PlacementFilter}
*/
private PlacementFilter createPlacementFilterComponent(JsonNode node) {
List<Conditions> conditions = new ArrayList<>();
// The structure of the placement filter component is the most complex of the current components
// Each condition effectively seperated into an two arrays: one of allowed faces, and one of blocks/block molang queries
node.forEach(condition -> {
Set<Face> faces = new HashSet<>();
if (condition.has("allowed_faces")) {
@ -507,7 +625,13 @@ public class MappingsReader_v1 extends MappingsReader {
return new PlacementFilter(conditions);
}
/**
* Creates a molang query that returns true when the given java state identifier is the active state
* @param state the java state identifier
* @return the molang query
*/
private String createCustomBlockPropertyQuery(String state) {
// This creates a molang query from the given input blockstate string
String[] conditions = splitStateString(state);
String[] queries = new String[conditions.length];
@ -527,10 +651,17 @@ public class MappingsReader_v1 extends MappingsReader {
String query = String.join(" && ", queries);
return String.format("q.block_property('geyser_custom:default') == 0 && %s", query);
// Appends the default property to ensure it can be disabled when a state without specific overrides is active
return String.format("q.block_property('%1$s:default') == 0 && %2$s", Constants.GEYSER_NAMESPACE, query);
}
/**
* Splits the given java state identifier into an array of property=value pairs
* @param state the java state identifier
* @return the array of property=value pairs
*/
private String[] splitStateString(String state) {
// Split the given state string into an array of property=value pairs
int openBracketIndex = state.indexOf("[");
int closeBracketIndex = state.indexOf("]");
@ -541,6 +672,13 @@ public class MappingsReader_v1 extends MappingsReader {
return pairs;
}
/**
* Clamps the given value between the given min and max
* @param value the value to clamp
* @param min the minimum value
* @param max the maximum value
* @return the clamped value
*/
private float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}

View file

@ -35,6 +35,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class CustomBlockRegistryPopulator {
/**
* Registers all custom blocks defined by extensions and user supplied mappings
*/
public static void registerCustomBedrockBlocks() {
if (!GeyserImpl.getInstance().getConfig().isAddCustomBlocks()) {
return;
@ -69,7 +72,6 @@ public class CustomBlockRegistryPopulator {
}
CustomBlockState oldBlockState = blockStateOverrides.put(id, customBlockState);
if (oldBlockState != null) {
// TODO should this be an error? Allow extensions to query block state overrides?
GeyserImpl.getInstance().getLogger().debug("Duplicate block state override for Java Identifier: " +
javaIdentifier + " Old override: " + oldBlockState.name() + " New override: " + customBlockState.name());
}
@ -101,15 +103,23 @@ public class CustomBlockRegistryPopulator {
});
BlockRegistries.CUSTOM_BLOCKS.set(customBlocks.toArray(new CustomBlockData[0]));
GeyserImpl.getInstance().getLogger().debug("Registered " + customBlocks.size() + " custom blocks.");
GeyserImpl.getInstance().getLogger().info("Registered " + customBlocks.size() + " custom blocks.");
BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.set(blockStateOverrides);
GeyserImpl.getInstance().getLogger().debug("Registered " + blockStateOverrides.size() + " custom block overrides.");
GeyserImpl.getInstance().getLogger().info("Registered " + blockStateOverrides.size() + " custom block overrides.");
BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.set(customBlockItemOverrides);
GeyserImpl.getInstance().getLogger().debug("Registered " + customBlockItemOverrides.size() + " custom block item overrides.");
GeyserImpl.getInstance().getLogger().info("Registered " + customBlockItemOverrides.size() + " custom block item overrides.");
}
/**
* Generates and appends all custom block states to the provided list of custom block states
* Appends the custom block states to the provided list of NBT maps
* @param customBlock the custom block data to generate states for
* @param blockStates the list of NBT maps to append the custom block states to
* @param customExtBlockStates the list of custom block states to append the custom block states to
* @param stateVersion the state version to use for the custom block states
*/
static void generateCustomBlockStates(CustomBlockData customBlock, List<NbtMap> blockStates, List<CustomBlockState> customExtBlockStates, int stateVersion) {
int totalPermutations = 1;
for (CustomBlockProperty<?> property : customBlock.properties().values()) {
@ -134,6 +144,12 @@ public class CustomBlockRegistryPopulator {
}
}
/**
* Generates and returns the block property data for the provided custom block
* @param customBlock the custom block to generate block property data for
* @param protocolVersion the protocol version to use for the block property data
* @return the block property data for the provided custom block
*/
@SuppressWarnings("unchecked")
static BlockPropertyData generateBlockPropertyData(CustomBlockData customBlock, int protocolVersion) {
List<NbtMap> permutations = new ArrayList<>();
@ -161,11 +177,15 @@ public class CustomBlockRegistryPopulator {
NbtMap propertyTag = NbtMap.builder()
.putCompound("components", CustomBlockRegistryPopulator.convertComponents(customBlock.components(), protocolVersion))
// this is required or the client will crash
// in the future, this can be used to replace items in the creative inventory
// this would require us to map https://wiki.bedrock.dev/documentation/creative-categories.html#for-blocks programatically
.putCompound("menu_category", NbtMap.builder()
.putString("category", "none")
.putString("group", "")
.putBoolean("is_hidden_in_commands", false)
.build())
// meaning of this version is unknown, but it's required for tags to work and should probably be checked periodically
.putInt("molangVersion", 1)
.putList("permutations", NbtType.COMPOUND, permutations)
.putList("properties", NbtType.COMPOUND, properties)
@ -173,6 +193,12 @@ public class CustomBlockRegistryPopulator {
return new BlockPropertyData(customBlock.identifier(), propertyTag);
}
/**
* Converts the provided custom block components to an {@link NbtMap} to be sent to the client in the StartGame packet
* @param components the custom block components to convert
* @param protocolVersion the protocol version to use for the conversion
* @return the NBT representation of the provided custom block components
*/
static NbtMap convertComponents(CustomBlockComponents components, int protocolVersion) {
if (components == null) {
return NbtMap.EMPTY;
@ -206,6 +232,9 @@ public class CustomBlockRegistryPopulator {
.build());
}
builder.putCompound("minecraft:material_instances", NbtMap.builder()
// we could read these, but there is no functional reason to use them at the moment
// they only allow you to make aliases for material instances
// but you could already just define the same instance twice if this was really needed
.putCompound("mappings", NbtMap.EMPTY)
.putCompound("materials", materialsBuilder.build())
.build());
@ -230,6 +259,9 @@ public class CustomBlockRegistryPopulator {
.putInt("value", components.lightEmission())
.build());
}
// This is supposed to be sent as "light_dampening" since "block_light_filter" is the old value
// However, it seems they forgot to actually update it on the network despite all the documentation changing
// So we'll send this for now
if (components.lightDampening() != null) {
builder.putCompound("minecraft:block_light_filter", NbtMap.builder()
.putByte("value", components.lightDampening().byteValue())
@ -245,6 +277,9 @@ public class CustomBlockRegistryPopulator {
if (components.unitCube()) {
builder.putCompound("minecraft:unit_cube", NbtMap.EMPTY);
}
// place_air is not an actual component
// We just apply a dummy event to prevent the client from trying to place a block
// This mitigates the issue with the client sometimes double placing blocks
if (components.placeAir()) {
builder.putCompound("minecraft:on_player_placing", NbtMap.builder()
.putString("triggerType", "geyser:place_event")
@ -256,6 +291,11 @@ public class CustomBlockRegistryPopulator {
return builder.build();
}
/**
* Converts the provided box component to an {@link NbtMap}
* @param boxComponent the box component to convert
* @return the NBT representation of the provided box component
*/
private static NbtMap convertBox(BoxComponent boxComponent) {
return NbtMap.builder()
.putBoolean("enabled", !boxComponent.isEmpty())
@ -264,20 +304,30 @@ public class CustomBlockRegistryPopulator {
.build();
}
/**
* Converts the provided placement filter to a list of {@link NbtMap}
* @param placementFilter the placement filter to convert
* @return the NBT representation of the provided placement filter
*/
private static List<NbtMap> convertPlacementFilter(PlacementFilter placementFilter) {
List<NbtMap> conditions = new ArrayList<>();
placementFilter.conditions().forEach((condition) -> {
NbtMapBuilder conditionBuilder = NbtMap.builder();
// allowed_faces on the network is represented by 6 bits for the 6 possible faces
// the enum has the proper values for that face only, so we just bitwise OR them together
byte allowedFaces = 0;
for (Face face : condition.allowedFaces()) { allowedFaces |= face.getValue(); }
conditionBuilder.putByte("allowed_faces", allowedFaces);
// block_filters is a list of either blocks or queries for block tags
// if these match the block the player is trying to place on, the placement is allowed by the client
List <NbtMap> blockFilters = new ArrayList<>();
condition.blockFilters().forEach((value, type) -> {
NbtMapBuilder blockFilterBuilder = NbtMap.builder();
switch (type) {
case BLOCK -> blockFilterBuilder.putString("name", value);
// meaning of this version is unknown, but it's required for tags to work and should probably be checked periodically
case TAG -> blockFilterBuilder.putString("tags", value).putInt("tags_version", 6);
}
blockFilters.add(blockFilterBuilder.build());

View file

@ -38,6 +38,7 @@ import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
@ -375,6 +376,9 @@ public class ItemRegistryPopulator {
// and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents
Integer lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId();
for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) {
// For now we opt to preserve the pre custom block phase and just use the vanilla blocks in the creative inventory
// In the future if we get the mappings for categories it we could put the custom blocks in the creative inventory
// This would likely also require changes to recipe handling
int bedrockBlockRuntimeId = blockMappings.getVanillaBedrockBlockId(i);
NbtMap blockTag = blockMappings.getBedrockBlockStates().get(bedrockBlockRuntimeId);
String bedrockName = blockTag.getString("name");
@ -504,7 +508,7 @@ public class ItemRegistryPopulator {
for (CustomItemData customItem : customItemsToLoad) {
int customProtocolId = nextFreeBedrockId++;
String customItemName = "geyser_custom:" + customItem.name();
String customItemName = Constants.GEYSER_NAMESPACE + ":" + customItem.name();
if (!registeredItemNames.add(customItemName)) {
if (firstMappingsPass) {
GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItem.name() + "' already exists and was registered again! Skipping...");