From cc2e409f05bc14c6d6c1b0978595e4180764dece Mon Sep 17 00:00:00 2001 From: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 15 Jan 2023 16:27:28 -0800 Subject: [PATCH] Minor cleanup and javadocs --- .../java/org/geysermc/geyser/Constants.java | 2 +- .../level/block/GeyserCustomBlockData.java | 2 +- .../mappings/versions/MappingsReader_v1.java | 152 +++++++++++++++++- .../CustomBlockRegistryPopulator.java | 58 ++++++- .../populator/ItemRegistryPopulator.java | 6 +- 5 files changed, 206 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 787f9c222..589bb66a7 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -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; diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java index a454687f3..de60c16fa 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java @@ -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 diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index 4e1cc23b6..5e06a7f3f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -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 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> 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 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(value))); blockPropertyTypeMaps.intValuesMap().forEach((key, value) -> customBlockDataBuilder.intProperty(key, new ArrayList(value))); blockPropertyTypeMaps.booleanValuesSet().forEach((value) -> customBlockDataBuilder.booleanProperty(value)); + // Finally, build the custom block data CustomBlockData customBlockData = customBlockDataBuilder.build(); - Map 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 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 createCustomBlockPermutations(JsonNode node, String identifier, String name) { List 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 createCustomBlockStatesMap(String identifier, List stateKeys,List 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 createCustomBlockStatesMap(List stateKeys,List defaultStates, boolean onlyOverrideStates, CustomBlockData customBlockData, Map> stateKeyStrings, Map> stateKeyInts, Map> stateKeyBools) { Map 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 stateKeys, boolean defaultState, CustomBlockData customBlockData, Map> stateKeyStrings, Map> stateKeyInts, Map> stateKeyBools, Map 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 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> stringValuesMap = new HashMap<>(); Map> 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 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 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 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 = 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 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)); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java index a59af09d8..f3bf5f052 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java @@ -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 blockStates, List 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 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 convertPlacementFilter(PlacementFilter placementFilter) { List 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 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()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 87d487ea6..b307e9a04 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -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...");