mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge pull request #2 from davchoo/feature/blocky
Simplify creating CustomBlockMappings
This commit is contained in:
commit
d1790cc09d
3 changed files with 133 additions and 326 deletions
|
@ -1,13 +0,0 @@
|
|||
package org.geysermc.geyser.registry.mappings.util;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public record BlockPropertyTypeMaps(
|
||||
@NonNull Map<String, LinkedHashSet<String>> stringValuesMap, @NonNull Map<String, Map<String, String>> stateKeyStrings,
|
||||
@NonNull Map<String, LinkedHashSet<Integer>> intValuesMap, @NonNull Map<String, Map<String, Integer>> stateKeyInts,
|
||||
@NonNull Set<String> booleanValuesSet, @NonNull Map<String, Map<String, Boolean>> stateKeyBools) {
|
||||
}
|
|
@ -36,5 +36,5 @@ import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
|||
* data required to register a custom block that overrides a group of java block
|
||||
* states.
|
||||
*/
|
||||
public record CustomBlockMapping(@NonNull CustomBlockData data, @NonNull Map<String, CustomBlockState> states, @NonNull String javaIdentifier, @NonNull boolean overrideItem) {
|
||||
public record CustomBlockMapping(@NonNull CustomBlockData data, @NonNull Map<String, CustomBlockState> states, @NonNull String javaIdentifier, boolean overrideItem) {
|
||||
}
|
||||
|
|
|
@ -27,29 +27,23 @@ package org.geysermc.geyser.registry.mappings.versions;
|
|||
|
||||
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 com.github.steveice10.mc.protocol.data.game.Identifier;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
|
||||
import org.geysermc.geyser.api.block.custom.component.RotationComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.*;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.mappings.util.BlockPropertyTypeMaps;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping;
|
||||
import org.geysermc.geyser.registry.type.BlockMapping;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
|
@ -57,17 +51,10 @@ import org.geysermc.geyser.util.BlockUtils;
|
|||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MappingsReader_v1 extends MappingsReader {
|
||||
|
@ -107,7 +94,7 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
blocksNode.fields().forEachRemaining(entry -> {
|
||||
if (entry.getValue().isObject()) {
|
||||
try {
|
||||
String identifier = entry.getKey();
|
||||
String identifier = Identifier.formalize(entry.getKey());
|
||||
CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue());
|
||||
consumer.accept(identifier, customBlockMapping);
|
||||
} catch (InvalidCustomMappingsFileException e) {
|
||||
|
@ -181,8 +168,8 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Read a block mapping entry from a JSON node and java identifier
|
||||
* @param identifier The java identifier of the block
|
||||
* 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
|
||||
|
@ -198,272 +185,126 @@ 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
|
||||
// If this is true, we will only register the states the user has specified rather than all the possible block states
|
||||
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean();
|
||||
JsonNode stateOverrides = node.get("state_overrides");
|
||||
|
||||
|
||||
if (onlyOverrideStates && (stateOverrides == null || !stateOverrides.isObject())) {
|
||||
throw new InvalidCustomMappingsFileException("A block entry has only_override_states set to true but no state_overrides");
|
||||
throw new InvalidCustomMappingsFileException("Block entry for " + identifier + " has only_override_states set to true, but has no state_overrides.");
|
||||
}
|
||||
|
||||
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
|
||||
Map<String, CustomBlockComponents> componentsMap = new LinkedHashMap<>();
|
||||
if (stateOverrides != null && stateOverrides.isObject()) {
|
||||
// Load components for specific Java block states
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = stateOverrides.fields();
|
||||
while (fields.hasNext()) {
|
||||
stateKeys.add(identifier + fields.next().getKey());
|
||||
Map.Entry<String, JsonNode> overrideEntry = fields.next();
|
||||
String state = identifier + "[" + overrideEntry.getKey() + "]";
|
||||
if (!BlockRegistries.JAVA_IDENTIFIERS.get().containsKey(state)) {
|
||||
throw new InvalidCustomMappingsFileException("Unknown Java block state: " + state + " for state_overrides.");
|
||||
}
|
||||
componentsMap.put(state, createCustomBlockComponents(overrideEntry.getValue(), state, name));
|
||||
}
|
||||
}
|
||||
if (!onlyOverrideStates) {
|
||||
// Create components for any remaining Java block states
|
||||
BlockRegistries.JAVA_IDENTIFIERS.get().keySet()
|
||||
.stream()
|
||||
.filter(s -> s.startsWith(identifier))
|
||||
.filter(Predicate.not(componentsMap::containsKey))
|
||||
.forEach(state -> componentsMap.put(state, createCustomBlockComponents(null, state, name)));
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (componentsMap.isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("Unknown Java block: " + identifier);
|
||||
}
|
||||
|
||||
// Create the data for the overall block
|
||||
CustomBlockDataBuilder customBlockDataBuilder = new CustomBlockDataBuilder();
|
||||
customBlockDataBuilder.name(name)
|
||||
CustomBlockData.Builder customBlockDataBuilder = new 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))
|
||||
// This property is use to display the default state when onlyOverrideStates is false
|
||||
.booleanProperty(String.format("%s:default", Constants.GEYSER_NAMESPACE));
|
||||
.components(createCustomBlockComponents(node, componentsMap.keySet().iterator().next(), name));
|
||||
|
||||
// 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();
|
||||
|
||||
// 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);
|
||||
if (componentsMap.size() == 1) {
|
||||
// There are no other block states, so skip creating properties and permutations
|
||||
CustomBlockData blockData = customBlockDataBuilder.build();
|
||||
return new CustomBlockMapping(blockData, Map.of(identifier, blockData.defaultBlockState()), identifier, !onlyOverrideStates);
|
||||
}
|
||||
return createCustomBlockMapping(customBlockDataBuilder, componentsMap, 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) {
|
||||
private CustomBlockMapping createCustomBlockMapping(CustomBlockData.Builder customBlockDataBuilder, Map<String, CustomBlockComponents> componentsMap, String identifier, boolean overrideItem) {
|
||||
Map<String, LinkedHashSet<String>> valuesMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
List<CustomBlockPermutation> permutations = new ArrayList<>();
|
||||
Map<String, Function<CustomBlockState.Builder, CustomBlockState>> blockStateBuilders = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
// 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)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
createCustomBlockStates(stateKeys, false, customBlockData, stateKeyStrings, stateKeyInts, stateKeyBools, states);
|
||||
|
||||
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();
|
||||
// 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));
|
||||
|
||||
CustomBlockState blockState = builder.build();
|
||||
|
||||
states.put(key, blockState);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
Map<String, LinkedHashSet<Integer>> intValuesMap = new HashMap<>();
|
||||
Map<String, Map<String, Integer>> stateKeyInts = new HashMap<>();
|
||||
|
||||
Set<String> booleanValuesSet = new HashSet<>();
|
||||
Map<String, Map<String, Boolean>> stateKeyBools = new HashMap<>();
|
||||
|
||||
|
||||
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
|
||||
// For each Java block state, extract the property values, create a CustomBlockPermutation,
|
||||
// and a CustomBlockState builder
|
||||
for (Map.Entry<String, CustomBlockComponents> entry : componentsMap.entrySet()) {
|
||||
String state = entry.getKey();
|
||||
String[] pairs = splitStateString(state);
|
||||
|
||||
for (String pair : pairs) {
|
||||
// Get the property and value individually
|
||||
String[] parts = pair.split("=");
|
||||
String[] conditions = new String[pairs.length];
|
||||
Function<CustomBlockState.Builder, CustomBlockState.Builder> blockStateBuilder = Function.identity();
|
||||
|
||||
for (int i = 0; i < pairs.length; i++) {
|
||||
String[] parts = pairs[i].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<>());
|
||||
propertyMap.put(property, Boolean.parseBoolean(value));
|
||||
stateKeyBools.put(state, propertyMap);
|
||||
} 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);
|
||||
}
|
||||
values.add(intValue);
|
||||
Map<String, Integer> propertyMap = stateKeyInts.getOrDefault(state, new HashMap<>());
|
||||
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);
|
||||
}
|
||||
values.add(value);
|
||||
Map<String, String> propertyMap = stateKeyStrings.getOrDefault(state, new HashMap<>());
|
||||
propertyMap.put(property, value);
|
||||
stateKeyStrings.put(state, propertyMap);
|
||||
}
|
||||
valuesMap.computeIfAbsent(property, k -> new LinkedHashSet<>())
|
||||
.add(value);
|
||||
|
||||
conditions[i] = String.format("q.block_property('%s') == '%s'", property, value);
|
||||
blockStateBuilder = blockStateBuilder.andThen(builder -> builder.stringProperty(property, value));
|
||||
}
|
||||
|
||||
permutations.add(new CustomBlockPermutation(entry.getValue(), String.join(" && ", conditions)));
|
||||
blockStateBuilders.put(state, blockStateBuilder.andThen(CustomBlockState.Builder::build));
|
||||
}
|
||||
// We should now have all of the maps
|
||||
return new BlockPropertyTypeMaps(stringValuesMap, stateKeyStrings, intValuesMap, stateKeyInts, booleanValuesSet, stateKeyBools);
|
||||
|
||||
valuesMap.forEach((key, value) -> customBlockDataBuilder.stringProperty(key, new ArrayList<>(value)));
|
||||
|
||||
CustomBlockData customBlockData = customBlockDataBuilder
|
||||
.permutations(permutations)
|
||||
.build();
|
||||
// Build CustomBlockStates for each Java block state we wish to override
|
||||
Map<String, CustomBlockState> states = blockStateBuilders.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().apply(customBlockData.blockStateBuilder())));
|
||||
|
||||
return new CustomBlockMapping(customBlockData, states, identifier, overrideItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Creates a {@link CustomBlockComponents} object for the passed state override or base block node, Java block state identifier, and custom block name
|
||||
* @param node the state override or base block {@link JsonNode}
|
||||
* @param stateKey the Java block state identifier
|
||||
* @param name the name of the custom block
|
||||
* @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();
|
||||
BoxComponent boxComponent = createBoxComponent(id);
|
||||
CustomBlockComponents.Builder builder = new CustomBlockComponentsBuilder()
|
||||
.collisionBox(boxComponent)
|
||||
.selectionBox(boxComponent);
|
||||
|
||||
BoxComponent selectionBox = boxComponent;
|
||||
if (node.has("selection_box")) {
|
||||
JsonNode selectionBoxNode = node.get("selection_box");
|
||||
if (selectionBoxNode.isObject()) {
|
||||
if (selectionBoxNode.has("origin") && selectionBoxNode.has("size")) {
|
||||
JsonNode origin = selectionBoxNode.get("origin");
|
||||
int originX = origin.get(0).intValue();
|
||||
int originY = origin.get(1).intValue();
|
||||
int originZ = origin.get(2).intValue();
|
||||
|
||||
JsonNode size = selectionBoxNode.get("size");
|
||||
int sizeX = size.get(0).intValue();
|
||||
int sizeY = size.get(1).intValue();
|
||||
int sizeZ = size.get(2).intValue();
|
||||
|
||||
selectionBox = new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
}
|
||||
if (node == null) {
|
||||
// No other components were defined
|
||||
return builder.build();
|
||||
}
|
||||
builder.selectionBox(selectionBox);
|
||||
|
||||
BoxComponent collisionBox = boxComponent;
|
||||
if (node.has("collision_box")) {
|
||||
JsonNode collisionBoxNode = node.get("collision_box");
|
||||
if (collisionBoxNode.isObject()) {
|
||||
if (collisionBoxNode.has("origin") && collisionBoxNode.has("size")) {
|
||||
JsonNode origin = collisionBoxNode.get("origin");
|
||||
int originX = origin.get(0).intValue();
|
||||
int originY = origin.get(1).intValue();
|
||||
int originZ = origin.get(2).intValue();
|
||||
|
||||
JsonNode size = collisionBoxNode.get("size");
|
||||
int sizeX = size.get(0).intValue();
|
||||
int sizeY = size.get(1).intValue();
|
||||
int sizeZ = size.get(2).intValue();
|
||||
|
||||
collisionBox = new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
}
|
||||
BoxComponent selectionBox = createBoxComponent(node.get("selection_box"));
|
||||
if (selectionBox != null) {
|
||||
builder.selectionBox(selectionBox);
|
||||
}
|
||||
BoxComponent collisionBox = createBoxComponent(node.get("collision_box"));
|
||||
if (collisionBox != null) {
|
||||
builder.collisionBox(collisionBox);
|
||||
}
|
||||
builder.collisionBox(collisionBox);
|
||||
|
||||
// Ideally we would just be able to calculate the right value for this, but it seems that hardness value on bedrock does not follow Java
|
||||
// As such this might as well just be configured for now if people so choose
|
||||
|
@ -542,34 +383,28 @@ 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()) {
|
||||
ArrayNode tagsArray = (ArrayNode) tags;
|
||||
Set<String> tagsSet = new HashSet<>();
|
||||
tagsArray.forEach(tag -> tagsSet.add(tag.asText()));
|
||||
builder.tags(tagsSet);
|
||||
}
|
||||
// Ideally we could programmatically extract the tags here https://wiki.bedrock.dev/blocks/block-tags.html
|
||||
// This would let us automatically apply the correct vanilla tags to blocks
|
||||
// However, its worth noting that vanilla tools do not currently honor these tags anyway
|
||||
if (node.get("tags") instanceof ArrayNode tags) {
|
||||
Set<String> tagsSet = new ObjectOpenHashSet<>();
|
||||
tags.forEach(tag -> tagsSet.add(tag.asText()));
|
||||
builder.tags(tagsSet);
|
||||
}
|
||||
|
||||
CustomBlockComponents components = builder.build();
|
||||
|
||||
return components;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link BoxComponent} for the passed collision box index
|
||||
* @param id the collision box index
|
||||
* Creates a {@link BoxComponent} based on a Java block's collision
|
||||
* @param javaId the block's Java ID
|
||||
* @return the {@link BoxComponent}
|
||||
*/
|
||||
private BoxComponent createBoxComponent(int id) {
|
||||
private BoxComponent createBoxComponent(int javaId) {
|
||||
// Some blocks (e.g. plants) have no collision box
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(id);
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(javaId);
|
||||
if (blockCollision == null) {
|
||||
return new BoxComponent(0, 0, 0, 0, 0, 0);
|
||||
return BoxComponent.EMPTY_BOX;
|
||||
}
|
||||
|
||||
BoundingBox boundingBox = blockCollision.getBoundingBoxes()[0];
|
||||
|
@ -578,7 +413,7 @@ 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
|
||||
// Unfortunately we need to clamp the values here to 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
|
||||
|
@ -591,9 +426,31 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
float sizeY = MathUtils.clamp((float) boundingBox.getSizeY() * 16, 0, 16);
|
||||
float sizeZ = MathUtils.clamp((float) boundingBox.getSizeZ() * 16, 0, 16);
|
||||
|
||||
BoxComponent boxComponent = new BoxComponent(cornerX, cornerY, cornerZ, sizeX, sizeY, sizeZ);
|
||||
return new BoxComponent(cornerX, cornerY, cornerZ, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
|
||||
return boxComponent;
|
||||
/**
|
||||
* Creates a {@link BoxComponent} from a JSON Node
|
||||
* @param node the JSON node
|
||||
* @return the {@link BoxComponent}
|
||||
*/
|
||||
private BoxComponent createBoxComponent(JsonNode node) {
|
||||
if (node != null && node.isObject()) {
|
||||
if (node.has("origin") && node.has("size")) {
|
||||
JsonNode origin = node.get("origin");
|
||||
float originX = origin.get(0).floatValue();
|
||||
float originY = origin.get(1).floatValue();
|
||||
float originZ = origin.get(2).floatValue();
|
||||
|
||||
JsonNode size = node.get("size");
|
||||
float sizeX = size.get(0).floatValue();
|
||||
float sizeY = size.get(1).floatValue();
|
||||
float sizeZ = size.get(2).floatValue();
|
||||
|
||||
return new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -637,15 +494,13 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
List<PlacementConditions> 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
|
||||
// Each condition effectively separated into two arrays: one of allowed faces, and one of blocks/block Molang queries
|
||||
node.forEach(condition -> {
|
||||
Set<Face> faces = new HashSet<>();
|
||||
Set<Face> faces = EnumSet.noneOf(Face.class);
|
||||
if (condition.has("allowed_faces")) {
|
||||
JsonNode allowedFaces = condition.get("allowed_faces");
|
||||
if (allowedFaces.isArray()) {
|
||||
allowedFaces.forEach(face -> {
|
||||
faces.add(Face.valueOf(face.asText().toUpperCase()));
|
||||
});
|
||||
allowedFaces.forEach(face -> faces.add(Face.valueOf(face.asText().toUpperCase())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -672,51 +527,16 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
return 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];
|
||||
|
||||
for (int i = 0; i < conditions.length; i++) {
|
||||
String[] keyval = conditions[i].split("=", 2);
|
||||
|
||||
if (keyval[1].equals("true")) {
|
||||
queries[i] = String.format("q.block_property('%1$s') == %2$s", keyval[0], 1);
|
||||
} else if (keyval[1].equals("false")) {
|
||||
queries[i] = String.format("q.block_property('%1$s') == %2$s", keyval[0], 0);
|
||||
} else if (CharMatcher.inRange('0', '9').matchesAllOf(keyval[1])) {
|
||||
queries[i] = String.format("q.block_property('%1$s') == %2$s", keyval[0], Integer.parseInt(keyval[1]));
|
||||
} else {
|
||||
queries[i] = String.format("q.block_property('%1$s') == '%2$s'", keyval[0], keyval[1]);
|
||||
}
|
||||
}
|
||||
|
||||
String query = String.join(" && ", queries);
|
||||
|
||||
// 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("]");
|
||||
|
||||
String cleanStates = state.substring(openBracketIndex + 1, closeBracketIndex);
|
||||
|
||||
String[] pairs = cleanStates.split("\\s*,\\s*");
|
||||
|
||||
return pairs;
|
||||
String states = state.substring(openBracketIndex + 1, state.length() - 1);
|
||||
return states.split(",");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue