mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Minor cleanup and javadocs
This commit is contained in:
parent
bfdebadacc
commit
cc2e409f05
5 changed files with 206 additions and 14 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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...");
|
||||
|
|
Loading…
Reference in a new issue