From 61f5db1059bf85891f17926c832034d4398a2ae2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 29 Nov 2024 14:01:25 +0000 Subject: [PATCH 001/118] First work on a new API (wow that's a lot of TODOs) --- api/build.gradle.kts | 9 + .../custom/v2/CustomItemBedrockOptions.java | 127 +++++ .../item/custom/v2/CustomItemDefinition.java | 76 +++ .../GeyserCustomItemBedrockOptions.java | 105 ++++ .../custom/GeyserCustomItemDefinition.java | 62 +++ .../loader/ProviderRegistryLoader.java | 9 + .../mappings/MappingsConfigReader.java | 8 +- .../components/DataComponentReader.java | 49 ++ .../components/DataComponentReaders.java | 53 ++ .../components/readers/ConsumableReader.java | 62 +++ .../mappings/versions/MappingsReader.java | 6 +- .../mappings/versions/MappingsReader_v1.java | 13 +- .../mappings/versions/MappingsReader_v2.java | 157 ++++++ .../CustomItemRegistryPopulator.java | 6 +- .../CustomItemRegistryPopulator_v2.java | 486 ++++++++++++++++++ .../populator/ItemRegistryPopulator.java | 37 +- .../geyser/registry/type/ItemMapping.java | 8 +- .../translator/item/CustomItemTranslator.java | 23 +- 18 files changed, 1260 insertions(+), 36 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java diff --git a/api/build.gradle.kts b/api/build.gradle.kts index eac02ebeb4c..af0973f0672 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -8,6 +8,15 @@ plugins { dependencies { api(libs.base.api) api(libs.math) + + // Adventure text serialization + api(libs.bundles.adventure) + + // TODO? can we exclude more + api(libs.mcprotocollib) { + exclude("io.netty", "netty-all") + exclude("net.raphimc", "MinecraftAuth") + } } version = property("version")!! diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java new file mode 100644 index 00000000000..24f3003b27b --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; + +import java.util.OptionalInt; +import java.util.Set; + +/** + * This is used to store options for a custom item that can't be described using item components. + */ +public interface CustomItemBedrockOptions { + + /** + * Gets the item's icon. When not present, {@code .} is used. + * + * @return the item's icon + */ + @Nullable + String icon(); + + /** + * Gets if the item is allowed to be put into the offhand. + * + * @return true if the item is allowed to be used in the offhand, false otherwise + */ + boolean allowOffhand(); + + /** + * Gets if the item should be displayed as handheld, like a tool. + * + * @return true if the item should be displayed as handheld, false otherwise + */ + boolean displayHandheld(); + + /** + * Gets the item's creative category, or tab id. + * + * @return the item's creative category + */ + @NonNull + OptionalInt creativeCategory(); + + /** + * Gets the item's creative group. + * + * @return the item's creative group + */ + @Nullable + String creativeGroup(); + + /** + * Gets the item's texture size. This is to resize the item if the texture is not 16x16. + * + * @return the item's texture size + */ + int textureSize(); + + /** + * Gets the item's render offsets. If it is null, the item will be rendered normally, with no offsets. + * + * @return the item's render offsets + */ + @Nullable + CustomRenderOffsets renderOffsets(); + + /** + * Gets the item's set of tags that can be used in Molang. + * Equivalent to "tag:some_tag" + * + * @return the item's tags, if they exist + */ + @NonNull + Set tags(); + + static Builder builder() { + return GeyserApi.api().provider(Builder.class); + } + + interface Builder { + + Builder icon(@Nullable String icon); + + Builder allowOffhand(boolean allowOffhand); + + Builder displayHandheld(boolean displayHandheld); + + Builder creativeCategory(int creativeCategory); + + Builder creativeGroup(@Nullable String creativeGroup); + + Builder textureSize(int textureSize); + + Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); + + Builder tags(@Nullable Set tags); + + CustomItemBedrockOptions build(); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java new file mode 100644 index 00000000000..3162ecc8645 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2; + +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +/** + * This is used to store data for a custom item. + * + * V2. TODO. + */ +public interface CustomItemDefinition { + + /** + * Gets the item model this definition is for. This model can't be in the Minecraft namespace. + */ + @NonNull Key model(); // TODO name?? + + default String name() { + return model().namespace() + "_" + model().value(); + } // TODO, also display name ? also, rename to identifier + + default String icon() { + return bedrockOptions().icon() == null ? name() : bedrockOptions().icon(); + } // TODO + + // TODO predicate + + // TODO bedrock options + + @NonNull CustomItemBedrockOptions bedrockOptions(); + + // TODO components + + @NonNull DataComponents components(); + + static Builder builder(Key itemModel) { + return GeyserApi.api().provider(Builder.class, itemModel); + } + + interface Builder { + + Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); + + // TODO do we want another format for this? + Builder components(@NonNull DataComponents components); + + CustomItemDefinition build(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java new file mode 100644 index 00000000000..1d3d456525e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.item.custom; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; + +import java.util.HashSet; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.Set; + +public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, @NonNull OptionalInt creativeCategory, @Nullable String creativeGroup, + int textureSize, @Nullable CustomRenderOffsets renderOffsets, @NonNull Set tags) implements CustomItemBedrockOptions { + + public static class Builder implements CustomItemBedrockOptions.Builder { + private String icon = null; + private boolean allowOffhand = true; + private boolean displayHandheld = false; + private OptionalInt creativeCategory = OptionalInt.empty(); + private String creativeGroup = null; + private int textureSize = 16; + private CustomRenderOffsets renderOffsets = null; + private Set tags = new HashSet<>(); + + @Override + public Builder icon(@Nullable String icon) { + this.icon = icon; + return this; + } + + @Override + public Builder allowOffhand(boolean allowOffhand) { + this.allowOffhand = allowOffhand; + return this; + } + + @Override + public Builder displayHandheld(boolean displayHandheld) { + this.displayHandheld = displayHandheld; + return this; + } + + @Override + public Builder creativeCategory(int creativeCategory) { + this.creativeCategory = OptionalInt.of(creativeCategory); + // TODO validation? + return this; + } + + @Override + public Builder creativeGroup(@Nullable String creativeGroup) { + this.creativeGroup = creativeGroup; + return this; + } + + @Override + public Builder textureSize(int textureSize) { + this.textureSize = textureSize; + return this; + } + + @Override + public Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets) { + this.renderOffsets = renderOffsets; + return this; + } + + @Override + public Builder tags(@Nullable Set tags) { + this.tags = Objects.requireNonNullElseGet(tags, Set::of); + return this; + } + + @Override + public CustomItemBedrockOptions build() { + return new GeyserCustomItemBedrockOptions(icon, allowOffhand, displayHandheld, creativeCategory, creativeGroup, textureSize, renderOffsets, tags); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java new file mode 100644 index 00000000000..2df03326849 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.item.custom; + +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +public record GeyserCustomItemDefinition(@NonNull Key model, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { + + public static class Builder implements CustomItemDefinition.Builder { + private final Key model; + private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); + private DataComponents components; + + public Builder(Key model) { + this.model = model; + } + + @Override + public CustomItemDefinition.Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options) { + this.bedrockOptions = options.build(); + return this; + } + + @Override + public CustomItemDefinition.Builder components(@NonNull DataComponents components) { + this.components = components; + return this; + } + + @Override + public CustomItemDefinition build() { + return new GeyserCustomItemDefinition(model, bedrockOptions, components); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 94de0c29858..ddb923fee34 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.registry.loader; +import net.kyori.adventure.key.Key; import org.geysermc.geyser.api.bedrock.camera.CameraFade; import org.geysermc.geyser.api.bedrock.camera.CameraPosition; import org.geysermc.geyser.api.block.custom.CustomBlockData; @@ -39,6 +40,8 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.impl.camera.GeyserCameraFade; import org.geysermc.geyser.impl.camera.GeyserCameraPosition; @@ -47,6 +50,8 @@ import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; +import org.geysermc.geyser.item.custom.GeyserCustomItemBedrockOptions; +import org.geysermc.geyser.item.custom.GeyserCustomItemDefinition; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; import org.geysermc.geyser.level.block.GeyserCustomBlockData; import org.geysermc.geyser.level.block.GeyserGeometryComponent; @@ -84,6 +89,10 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.Builder()); providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder()); + // items v2 + providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Key) args[0])); + providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); + // cameras providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java index d09e0b5a1e1..b7d7f1abd82 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java @@ -30,10 +30,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; import org.geysermc.geyser.registry.mappings.versions.MappingsReader; import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1; +import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v2; import java.io.IOException; import java.nio.file.Files; @@ -46,6 +47,7 @@ public class MappingsConfigReader { public MappingsConfigReader() { this.mappingReaders.put(1, new MappingsReader_v1()); + this.mappingReaders.put(2, new MappingsReader_v2()); } public Path[] getCustomMappingsFiles() { @@ -73,7 +75,7 @@ public boolean ensureMappingsDirectory(Path mappingsDirectory) { return true; } - public void loadItemMappingsFromJson(BiConsumer consumer) { + public void loadItemMappingsFromJson(BiConsumer consumer) { if (!ensureMappingsDirectory(this.customMappingsDirectory)) { return; } @@ -121,7 +123,7 @@ public int getFormatVersion(JsonNode mappingsRoot, Path file) { return formatVersion; } - public void readItemMappingsFromJson(Path file, BiConsumer consumer) { + public void readItemMappingsFromJson(Path file, BiConsumer consumer) { JsonNode mappingsRoot = getMappingsRoot(file); if (mappingsRoot == null) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java new file mode 100644 index 00000000000..4113093d51d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; + +@Getter +public abstract class DataComponentReader { + private final DataComponentType type; + + protected DataComponentReader(DataComponentType type) { + this.type = type; + } + + protected abstract V readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException; + + DataComponent> read(JsonNode node) throws InvalidCustomMappingsFileException { + // TODO primitives?? + return type.getDataComponentFactory().create(type, readDataComponent(node)); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java new file mode 100644 index 00000000000..560b4480b14 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components; + +import com.fasterxml.jackson.databind.JsonNode; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.readers.ConsumableReader; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +import java.util.HashMap; +import java.util.Map; + +public class DataComponentReaders { + private static final Map> READERS = new HashMap<>(); + + public static void readDataComponent(DataComponents components, Key key, @NonNull JsonNode node) throws InvalidCustomMappingsFileException { + DataComponentReader reader = READERS.get(key); + if (reader == null) { + throw new InvalidCustomMappingsFileException("Unknown data component " + key); + } + components.getDataComponents().put(reader.getType(), reader.read(node)); + } + + static { + READERS.put(MinecraftKey.key("consumable"), new ConsumableReader()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java new file mode 100644 index 00000000000..8a3ff9a6c8f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; + +import java.util.List; + +public class ConsumableReader extends DataComponentReader { + + public ConsumableReader() { + super(DataComponentType.CONSUMABLE); + } + + @Override + protected Consumable readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { + if (!node.isObject()) { + throw new InvalidCustomMappingsFileException("Expected consumable component to be an object"); + } + + float consumeSeconds = 1.6F; + if (node.has("consume_seconds")) { + consumeSeconds = (float) node.get("consume_seconds").asDouble(); + } + + Consumable.ItemUseAnimation animation = Consumable.ItemUseAnimation.EAT; + if (node.has("animation")) { + animation = Consumable.ItemUseAnimation.valueOf(node.get("animation").asText().toUpperCase()); // TODO maybe not rely on the enum + } + + return new Consumable(consumeSeconds, animation, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); // TODO + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java index b2bdd5a0130..a89773800e4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java @@ -27,8 +27,8 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; @@ -36,10 +36,10 @@ import java.util.function.BiConsumer; public abstract class MappingsReader { - public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); + public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); - public abstract CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException; + public abstract CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException; public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; protected @Nullable CustomRenderOffsets fromJsonNode(JsonNode node) { 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 b5e25a4ba5c..802ad31373b 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 @@ -39,6 +39,7 @@ 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.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; @@ -67,8 +68,9 @@ * A class responsible for reading custom item and block mappings from a JSON file */ public class MappingsReader_v1 extends MappingsReader { + @Override - public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { this.readItemMappingsV1(file, mappingsRoot, consumer); } @@ -85,7 +87,7 @@ public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer consumer) { JsonNode itemsNode = mappingsRoot.get("items"); if (itemsNode != null && itemsNode.isObject()) { @@ -93,7 +95,7 @@ public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer { try { - CustomItemData customItemData = this.readItemMappingEntry(data); + CustomItemDefinition customItemData = this.readItemMappingEntry(data); consumer.accept(entry.getKey(), customItemData); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); @@ -158,7 +160,7 @@ private CustomItemOptions readItemCustomItemOptions(JsonNode node) { } @Override - public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { + public CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { if (node == null || !node.isObject()) { throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); } @@ -213,7 +215,8 @@ public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMa customItemData.tags(tagsSet); } - return customItemData.build(); + //return customItemData.build(); + return null; // TODO } /** diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java new file mode 100644 index 00000000000..d7dedf289bc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.versions; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; +import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Set; +import java.util.function.BiConsumer; + +public class MappingsReader_v2 extends MappingsReader { + + @Override + public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + readItemMappingsV2(file, mappingsRoot, consumer); + } + + public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + JsonNode itemModels = mappingsRoot.get("items"); + + if (itemModels != null && itemModels.isObject()) { + itemModels.fields().forEachRemaining(entry -> { + if (entry.getValue().isArray()) { + entry.getValue().forEach(data -> { + try { + CustomItemDefinition customItemDefinition = readItemMappingEntry(data); + consumer.accept(entry.getKey(), customItemDefinition); + } catch (InvalidCustomMappingsFileException e) { + GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); + } + }); + } + }); + } + } + + @Override + public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + // TODO + } + + @Override + public CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { + if (node == null || !node.isObject()) { + throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); + } + + JsonNode model = node.get("model"); + if (model == null || !model.isTextual() || model.asText().isEmpty()) { + throw new InvalidCustomMappingsFileException("An item entry has no model"); + } + CustomItemDefinition.Builder builder = CustomItemDefinition.builder(Key.key(model.asText())); + + // TODO name, predicate + + builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); + + DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? + JsonNode componentsNode = node.get("components"); + if (componentsNode != null && componentsNode.isObject()) { + componentsNode.fields().forEachRemaining(entry -> { + try { + DataComponentReaders.readDataComponent(components, Key.key(entry.getKey()), entry.getValue()); + } catch (InvalidCustomMappingsFileException e) { + GeyserImpl.getInstance().getLogger().error("Error reading component " + entry.getKey() + " for item model " + model.textValue(), e); + } + }); + } + builder.components(components); + + return builder.build(); + } + + private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node) { + CustomItemBedrockOptions.Builder builder = CustomItemBedrockOptions.builder(); + if (node == null || !node.isObject()) { + return builder; + } + + if (node.has("icon")) { + builder.icon(node.get("icon").asText()); + } + + if (node.has("creative_category")) { + builder.creativeCategory(node.get("creative_category").asInt()); + } + + if (node.has("creative_group")) { + builder.creativeGroup(node.get("creative_group").asText()); + } + + if (node.has("allow_offhand")) { + builder.allowOffhand(node.get("allow_offhand").asBoolean()); + } + + if (node.has("display_handheld")) { + builder.displayHandheld(node.get("display_handheld").asBoolean()); + } + + if (node.has("texture_size")) { + builder.textureSize(node.get("texture_size").asInt()); + } + + if (node.has("render_offsets")) { + JsonNode tmpNode = node.get("render_offsets"); + + builder.renderOffsets(fromJsonNode(tmpNode)); + } + + if (node.get("tags") instanceof ArrayNode tags) { + Set tagsSet = new ObjectOpenHashSet<>(); + tags.forEach(tag -> tagsSet.add(tag.asText())); + builder.tags(tagsSet); + } + + return builder; + } + + @Override + public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { + return null; // TODO + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 0a9c93980a7..69821c04504 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -56,9 +56,9 @@ public static void populate(Map items, Multimap { - if (CustomItemRegistryPopulator.initialCheck(key, item, items)) { - customItems.get(key).add(item); - } + //if (CustomItemRegistryPopulator.initialCheck(key, item, items)) { + //customItems.get(key).add(item); + //} // TODO }); GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java new file mode 100644 index 00000000000..c5f690f2ab1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.google.common.collect.Multimap; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.nbt.NbtType; +import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; +import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; +import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.item.GeyserCustomMappingData; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.components.WearableSlot; +import org.geysermc.geyser.item.type.ArmorItem; +import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.registry.mappings.MappingsConfigReader; +import org.geysermc.geyser.registry.type.GeyserMappingItem; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ConsumeEffect; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; +import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class CustomItemRegistryPopulator_v2 { + + public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { + // TODO + System.out.println("reading mappings"); + MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); + // Load custom items from mappings files + mappingsConfigReader.loadItemMappingsFromJson((id, item) -> { + if (initialCheck(item, items)) { + System.out.println("read item " + id + " item " + item); + customItems.get(id).add(item); + } + }); + + int customItemCount = customItems.size() + nonVanillaCustomItems.size(); + if (customItemCount > 0) { + GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items"); + } + } + + public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, + CustomItemDefinition customItemDefinition, int bedrockId, int protocolVersion) { + ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); + + NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId, protocolVersion); + ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); + + return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); + } + + static boolean initialCheck(CustomItemDefinition item, Map mappings) { + // TODO check if there's already a same model without predicate and this hasn't a predicate either + String name = item.name(); // TODO rename to identifier + if (name.isEmpty()) { + GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); + } else if (Character.isDigit(name.charAt(0))) { + // As of 1.19.31 + GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!"); + } + return true; + } + + private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item javaItem, GeyserMappingItem mapping, + String customItemName, int customItemId, int protocolVersion) { + NbtMapBuilder builder = NbtMap.builder() + .putString("name", customItemName) + .putInt("id", customItemId); + + NbtMapBuilder itemProperties = NbtMap.builder(); + NbtMapBuilder componentBuilder = NbtMap.builder(); + + DataComponents components = patchDataComponents(javaItem, customItemDefinition); + setupBasicItemInfo(customItemDefinition.name(), customItemDefinition, components, itemProperties, componentBuilder, protocolVersion); + + boolean canDestroyInCreative = true; + if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. + canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, javaItem.attackDamage()); + } + itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); + if (equippable != null) { + computeArmorProperties(equippable, itemProperties, componentBuilder); + } + + Consumable consumable = components.get(DataComponentType.CONSUMABLE); + if (consumable != null) { + FoodProperties foodProperties = components.get(DataComponentType.FOOD); + computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder); + } + + // TODO block item/runtime ID, entity placer, chargeable, throwable, item cooldown, hat hardcoded on java, + + computeRenderOffsets(false, customItemDefinition.bedrockOptions(), componentBuilder); + + componentBuilder.putCompound("item_properties", itemProperties.build()); + builder.putCompound("components", componentBuilder.build()); + + return builder; + } + + private static void setupBasicItemInfo(String name, CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) { + CustomItemBedrockOptions options = definition.bedrockOptions(); + NbtMap iconMap = NbtMap.builder() + .putCompound("textures", NbtMap.builder() + .putString("default", definition.icon()) + .build()) + .build(); + itemProperties.putCompound("minecraft:icon", iconMap); + + if (options.creativeCategory().isPresent()) { + itemProperties.putInt("creative_category", options.creativeCategory().getAsInt()); + + if (options.creativeGroup() != null) { + itemProperties.putString("creative_group", options.creativeGroup()); + } + } + + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", name).build()); // TODO + + // Add a Geyser tag to the item, allowing Molang queries + addItemTag(componentBuilder, "geyser:is_custom"); + + // Add other defined tags to the item + Set tags = options.tags(); + for (String tag : tags) { + if (tag != null && !tag.isBlank()) { + addItemTag(componentBuilder, tag); + } + } + + itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); + itemProperties.putBoolean("hand_equipped", options.displayHandheld()); + + int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); + int stackSize = maxDamage > 0 ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's + + itemProperties.putInt("max_stack_size", stackSize); + if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here? + componentBuilder.putCompound("minecraft:durability", NbtMap.builder() + .putCompound("damage_chance", NbtMap.builder() + .putInt("max", 1) + .putInt("min", 1) + .build()) + .putInt("max_durability", maxDamage) + .build()); + itemProperties.putBoolean("use_duration", true); + } + } + + // TODO minecraft java tool component - also needs work elsewhere to calculate correct break speed (server authorised block breaking) + private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) { + boolean canDestroyInCreative = true; + float miningSpeed = 1.0f; + + // This means client side the tool can never destroy a block + // This works because the molang '1' for tags will be true for all blocks and the speed will be 0 + // We want this since we calculate break speed server side in BedrockActionTranslator + List speed = new ArrayList<>(List.of( + NbtMap.builder() + .putCompound("block", NbtMap.builder() + .putString("tags", "1") + .build()) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putInt("speed", 0) + .build() + )); + + componentBuilder.putCompound("minecraft:digger", + NbtMap.builder() + .putList("destroy_speeds", NbtType.COMPOUND, speed) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putBoolean("use_efficiency", true) + .build() + ); + + if (toolType.equals("sword")) { + miningSpeed = 1.5f; + canDestroyInCreative = false; + } + + itemProperties.putBoolean("hand_equipped", true); + itemProperties.putFloat("mining_speed", miningSpeed); + + // This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil + itemProperties.putInt("enchantable_value", 1); + itemProperties.putString("enchantable_slot", toolType); + + // Adds a "attack damage" indicator. Purely visual! + if (attackDamage > 0) { + itemProperties.putInt("damage", attackDamage); + } + + return canDestroyInCreative; + } + + private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + int protectionValue = 0; + // TODO protection value + switch (equippable.slot()) { + case BOOTS -> { + componentBuilder.putString("minecraft:render_offsets", "boots"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_feet"); + itemProperties.putInt("enchantable_value", 15); + } + case CHESTPLATE -> { + componentBuilder.putString("minecraft:render_offsets", "chestplates"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_torso"); + itemProperties.putInt("enchantable_value", 15); + } + case LEGGINGS -> { + componentBuilder.putString("minecraft:render_offsets", "leggings"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_legs"); + itemProperties.putInt("enchantable_value", 15); + } + case HELMET -> { + componentBuilder.putString("minecraft:render_offsets", "helmets"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_head"); + itemProperties.putInt("enchantable_value", 15); + } + } + } + + private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks + itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); // TODO check and confirm + // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds + // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively + itemProperties.putInt("use_animation", 0); // TODO + // this component is required to allow the eat animation to play + componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); + } + + private static void computeRenderOffsets(boolean isHat, CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) { + if (isHat) { + componentBuilder.remove("minecraft:render_offsets"); + componentBuilder.putString("minecraft:render_offsets", "helmets"); + + componentBuilder.remove("minecraft:wearable"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); + } + + CustomRenderOffsets renderOffsets = bedrockOptions.renderOffsets(); + if (renderOffsets != null) { + componentBuilder.remove("minecraft:render_offsets"); + componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); + } else if (bedrockOptions.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { + float scale1 = (float) (0.075 / (bedrockOptions.textureSize() / 16f)); + float scale2 = (float) (0.125 / (bedrockOptions.textureSize() / 16f)); + float scale3 = (float) (0.075 / (bedrockOptions.textureSize() / 16f * 2.4f)); + + componentBuilder.putCompound("minecraft:render_offsets", + NbtMap.builder().putCompound("main_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) + .putCompound("off_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); + } + } + + private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) { + NbtMapBuilder builder = NbtMap.builder(); + + CustomRenderOffsets.Hand mainHand = renderOffsets.mainHand(); + if (mainHand != null) { + NbtMap nbt = toNbtMap(mainHand); + if (nbt != null) { + builder.putCompound("main_hand", nbt); + } + } + CustomRenderOffsets.Hand offhand = renderOffsets.offhand(); + if (offhand != null) { + NbtMap nbt = toNbtMap(offhand); + if (nbt != null) { + builder.putCompound("off_hand", nbt); + } + } + + return builder.build(); + } + + private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.Hand hand) { + NbtMap firstPerson = toNbtMap(hand.firstPerson()); + NbtMap thirdPerson = toNbtMap(hand.thirdPerson()); + + if (firstPerson == null && thirdPerson == null) { + return null; + } + + NbtMapBuilder builder = NbtMap.builder(); + if (firstPerson != null) { + builder.putCompound("first_person", firstPerson); + } + if (thirdPerson != null) { + builder.putCompound("third_person", thirdPerson); + } + + return builder.build(); + } + + private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.@Nullable Offset offset) { + if (offset == null) { + return null; + } + + CustomRenderOffsets.OffsetXYZ position = offset.position(); + CustomRenderOffsets.OffsetXYZ rotation = offset.rotation(); + CustomRenderOffsets.OffsetXYZ scale = offset.scale(); + + if (position == null && rotation == null && scale == null) { + return null; + } + + NbtMapBuilder builder = NbtMap.builder(); + if (position != null) { + builder.putList("position", NbtType.FLOAT, toList(position)); + } + if (rotation != null) { + builder.putList("rotation", NbtType.FLOAT, toList(rotation)); + } + if (scale != null) { + builder.putList("scale", NbtType.FLOAT, toList(scale)); + } + + return builder.build(); + } + + private static List toList(CustomRenderOffsets.OffsetXYZ xyz) { + return List.of(xyz.x(), xyz.y(), xyz.z()); + } + + private static NbtMap xyzToScaleList(float x, float y, float z) { + return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); + } + + // TODO this needs to be a simpler method once we just load default vanilla components from mappings or something + private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { + DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? + + components.put(DataComponentType.MAX_STACK_SIZE, javaItem.maxStackSize()); + components.put(DataComponentType.MAX_DAMAGE, javaItem.maxDamage()); + + Consumable consumable = getItemConsumable(javaItem); + if (consumable != null) { + components.put(DataComponentType.CONSUMABLE, consumable); + } + + if (canAlwaysEat(javaItem)) { + components.put(DataComponentType.FOOD, new FoodProperties(0, 0, true)); + } + + if (javaItem.glint()) { + components.put(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE, true); + } + + // TODO repairable? + + if (javaItem instanceof ArmorItem armor) { // TODO equippable + } + + components.put(DataComponentType.RARITY, javaItem.rarity().ordinal()); + + components.getDataComponents().putAll(definition.components().getDataComponents()); + return components; + } + + private static Consumable getItemConsumable(Item item) { + if (item == Items.APPLE || item == Items.BAKED_POTATO || item == Items.BEETROOT || item == Items.BEETROOT_SOUP || item == Items.BREAD + || item == Items.CARROT || item == Items.CHORUS_FRUIT || item == Items.COOKED_CHICKEN || item == Items.COOKED_COD + || item == Items.COOKED_MUTTON || item == Items.COOKED_PORKCHOP || item == Items.COOKED_RABBIT || item == Items.COOKED_SALMON + || item == Items.COOKIE || item == Items.ENCHANTED_GOLDEN_APPLE || item == Items.GOLDEN_APPLE || item == Items.GLOW_BERRIES + || item == Items.GOLDEN_CARROT || item == Items.MELON_SLICE || item == Items.MUSHROOM_STEW || item == Items.POISONOUS_POTATO + || item == Items.POTATO || item == Items.PUFFERFISH || item == Items.PUMPKIN_PIE || item == Items.RABBIT_STEW + || item == Items.BEEF || item == Items.CHICKEN || item == Items.COD || item == Items.MUTTON || item == Items.PORKCHOP + || item == Items.RABBIT || item == Items.ROTTEN_FLESH || item == Items.SPIDER_EYE || item == Items.COOKED_BEEF + || item == Items.SUSPICIOUS_STEW || item == Items.SWEET_BERRIES || item == Items.TROPICAL_FISH) { + return Consumables.DEFAULT_FOOD; + } else if (item == Items.POTION) { + return Consumables.DEFAULT_DRINK; + } else if (item == Items.HONEY_BOTTLE) { + return Consumables.HONEY_BOTTLE; + } else if (item == Items.OMINOUS_BOTTLE) { + return Consumables.OMINOUS_BOTTLE; + } else if (item == Items.DRIED_KELP) { + return Consumables.DRIED_KELP; + } + return null; + } + + private static boolean canAlwaysEat(Item item) { + return item == Items.CHORUS_FRUIT || item == Items.ENCHANTED_GOLDEN_APPLE || item == Items.GOLDEN_APPLE || item == Items.HONEY_BOTTLE || item == Items.SUSPICIOUS_STEW; + } + + @SuppressWarnings("unchecked") + private static void addItemTag(NbtMapBuilder builder, String tag) { + List tagList = (List) builder.get("item_tags"); + if (tagList == null) { + builder.putList("item_tags", NbtType.STRING, tag); + } else { + // NbtList is immutable + if (!tagList.contains(tag)) { + tagList = new ArrayList<>(tagList); + tagList.add(tag); + builder.putList("item_tags", NbtType.STRING, tagList); + } + } + } + + private static final class Consumables { + private static final Consumable DEFAULT_FOOD = new Consumable(1.6F, Consumable.ItemUseAnimation.EAT, BuiltinSound.ENTITY_GENERIC_EAT, true, List.of()); + private static final Consumable DEFAULT_DRINK = new Consumable(1.6F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ENTITY_GENERIC_DRINK, false, List.of()); + private static final Consumable HONEY_BOTTLE = new Consumable(2.0F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ITEM_HONEY_BOTTLE_DRINK, false, List.of()); + private static final Consumable OMINOUS_BOTTLE = new Consumable(2.0F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ITEM_HONEY_BOTTLE_DRINK, + false, List.of(new ConsumeEffect.PlaySound(BuiltinSound.ITEM_OMINOUS_BOTTLE_DISPOSE))); + private static final Consumable DRIED_KELP = new Consumable(0.8F, Consumable.ItemUseAnimation.EAT, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); + } +} 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 1da3b0e66d8..33e6b974636 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 @@ -61,9 +61,8 @@ import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData; -import org.geysermc.geyser.api.item.custom.CustomItemData; -import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; @@ -148,11 +147,12 @@ public static void populate() { // List values here is important compared to HashSet - we need to preserve the order of what's given to us // (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom // of the list first, then ascends. - Multimap customItems = MultimapBuilder.hashKeys().arrayListValues().build(); + Multimap customItems = MultimapBuilder.hashKeys().arrayListValues().build(); List nonVanillaCustomItems = customItemsAllowed ? new ObjectArrayList<>() : Collections.emptyList(); if (customItemsAllowed) { - CustomItemRegistryPopulator.populate(items, customItems, nonVanillaCustomItems); + //CustomItemRegistryPopulator.populate(items, customItems, nonVanillaCustomItems); // TODO + CustomItemRegistryPopulator_v2.populate(items, customItems, nonVanillaCustomItems); } // We can reduce some operations as Java information is the same across all palette versions @@ -455,15 +455,15 @@ public static void populate() { } // Add the custom item properties, if applicable - List> customItemOptions; - Collection customItemsToLoad = customItems.get(javaItem.javaIdentifier()); + List> customItemDefinitions; + Collection customItemsToLoad = customItems.get(javaItem.javaIdentifier()); if (customItemsAllowed && !customItemsToLoad.isEmpty()) { - customItemOptions = new ObjectArrayList<>(customItemsToLoad.size()); + customItemDefinitions = new ObjectArrayList<>(customItemsToLoad.size()); - for (CustomItemData customItem : customItemsToLoad) { + for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.name(); + String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.name(); // TODO bedrock identifier + non vanilla stuff if (!registeredItemNames.add(customItemName)) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); @@ -471,11 +471,10 @@ public static void populate() { continue; } - GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( - customItemName, javaItem, mappingItem, customItem, customProtocolId, palette.protocolVersion - ); + GeyserCustomMappingData customMapping = CustomItemRegistryPopulator_v2.registerCustomItem( + customItemName, javaItem, mappingItem, customItem, customProtocolId, palette.protocolVersion); - if (customItem.creativeCategory().isPresent()) { + if (customItem.bedrockOptions().creativeCategory().isPresent()) { creativeItems.add(ItemData.builder() .netId(creativeNetId.incrementAndGet()) .definition(customMapping.itemDefinition()) @@ -486,18 +485,19 @@ public static void populate() { // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemOptions.add(Pair.of(customItem.customItemOptions(), customMapping.itemDefinition())); + customItemDefinitions.add(Pair.of(customItem, customMapping.itemDefinition())); registry.put(customMapping.integerId(), customMapping.itemDefinition()); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); } // Important for later to find the best match and accurately replicate Java behavior - Collections.reverse(customItemOptions); + Collections.reverse(customItemDefinitions); } else { - customItemOptions = Collections.emptyList(); + customItemDefinitions = Collections.emptyList(); } - mappingBuilder.customItemOptions(customItemOptions); + mappingBuilder.customItemDefinitions(customItemDefinitions); + mappingBuilder.customItemOptions(Collections.emptyList()); ItemMapping mapping = mappingBuilder.build(); @@ -525,6 +525,7 @@ public static void populate() { .bedrockData(0) .bedrockBlockDefinition(null) .customItemOptions(Collections.emptyList()) + .customItemDefinitions(Collections.emptyList()) .build(); lightBlocks.put(lightBlock.getRuntimeId(), lightBlockEntry); } @@ -542,6 +543,7 @@ public static void populate() { .bedrockData(0) .bedrockBlockDefinition(null) .customItemOptions(Collections.emptyList()) + .customItemDefinitions(Collections.emptyList()) .build(); if (customItemsAllowed) { @@ -557,6 +559,7 @@ public static void populate() { .bedrockData(0) .bedrockBlockDefinition(null) .customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart + .customItemDefinitions(Collections.emptyList()) // TODO do not remove the above todo .build()); creativeItems.add(ItemData.builder() diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index 8a2c77f280a..db10914a859 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -34,6 +34,7 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.geysermc.geyser.api.item.custom.CustomItemOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; @@ -54,6 +55,7 @@ public class ItemMapping { null, null, Collections.emptyList(), + Collections.emptyList(), Items.AIR ); @@ -72,8 +74,12 @@ public class ItemMapping { String translationString; + @Deprecated + @NonNull + List> customItemOptions; // TODO remove + @NonNull - List> customItemOptions; + List> customItemDefinitions; @NonNull Item javaItem; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 91eee389558..9b74898bf15 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -25,17 +25,17 @@ package org.geysermc.geyser.translator.item; +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import it.unimi.dsi.fastutil.Pair; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.geysermc.geyser.api.item.custom.CustomItemOptions; -import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.registry.type.ItemMapping; import java.util.List; -import java.util.OptionalInt; /** * This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator. @@ -47,6 +47,21 @@ public static ItemDefinition getCustomItem(DataComponents components, ItemMappin if (components == null) { return null; } + + List> customItems = mapping.getCustomItemDefinitions(); + if (customItems.isEmpty()) { + return null; + } + + Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); // TODO fallback onto default item model (when thats done by chris) + + for (Pair customModel : customItems) { // TODO Predicates + if (customModel.first().model().equals(itemModel)) { + return customModel.second(); + } + } + return null; + /* List> customMappings = mapping.getCustomItemOptions(); if (customMappings.isEmpty()) { return null; @@ -99,7 +114,7 @@ public static ItemDefinition getCustomItem(DataComponents components, ItemMappin return mappingTypes.value(); } - return null; + return null;*/ } /* These two functions are based off their Mojmap equivalents from 1.19.2 */ From ea6c3c67f069b8b31ba93f51828c8ede1342fbc9 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 29 Nov 2024 15:16:04 +0000 Subject: [PATCH 002/118] Add component readers for all the planned components --- .../components/DataComponentReader.java | 6 ++ .../components/DataComponentReaders.java | 10 ++++ .../components/readers/ConsumableReader.java | 7 +-- .../components/readers/EquippableReader.java | 55 +++++++++++++++++ .../components/readers/FoodReader.java | 48 +++++++++++++++ .../readers/IntComponentReader.java | 59 +++++++++++++++++++ .../components/readers/UseCooldownReader.java | 59 +++++++++++++++++++ .../CustomItemRegistryPopulator_v2.java | 1 - 8 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index 4113093d51d..1baab15488f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -46,4 +46,10 @@ protected DataComponentReader(DataComponentType type) { // TODO primitives?? return type.getDataComponentFactory().create(type, readDataComponent(node)); } + + protected static void requireObject(JsonNode node) throws InvalidCustomMappingsFileException { + if (!node.isObject()) { + throw new InvalidCustomMappingsFileException("Expected an object"); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 560b4480b14..5ec00708fc2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -30,7 +30,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.readers.ConsumableReader; +import org.geysermc.geyser.registry.mappings.components.readers.EquippableReader; +import org.geysermc.geyser.registry.mappings.components.readers.FoodReader; +import org.geysermc.geyser.registry.mappings.components.readers.IntComponentReader; +import org.geysermc.geyser.registry.mappings.components.readers.UseCooldownReader; import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.HashMap; @@ -49,5 +54,10 @@ public static void readDataComponent(DataComponents components, Key key, @NonNul static { READERS.put(MinecraftKey.key("consumable"), new ConsumableReader()); + READERS.put(MinecraftKey.key("equippable"), new EquippableReader()); + READERS.put(MinecraftKey.key("food"), new FoodReader()); + READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponentType.MAX_DAMAGE, 0)); + READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponentType.MAX_STACK_SIZE, 0, 99)); + READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java index 8a3ff9a6c8f..2b2ce144075 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -43,10 +43,7 @@ public ConsumableReader() { @Override protected Consumable readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { - if (!node.isObject()) { - throw new InvalidCustomMappingsFileException("Expected consumable component to be an object"); - } - + requireObject(node); float consumeSeconds = 1.6F; if (node.has("consume_seconds")) { consumeSeconds = (float) node.get("consume_seconds").asDouble(); @@ -57,6 +54,6 @@ protected Consumable readDataComponent(@NonNull JsonNode node) throws InvalidCus animation = Consumable.ItemUseAnimation.valueOf(node.get("animation").asText().toUpperCase()); // TODO maybe not rely on the enum } - return new Consumable(consumeSeconds, animation, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); // TODO + return new Consumable(consumeSeconds, animation, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); // TODO are sound and particles supported on bedrock? } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java new file mode 100644 index 00000000000..8c5e1d6c361 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; +import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; + +public class EquippableReader extends DataComponentReader { + + public EquippableReader() { + super(DataComponentType.EQUIPPABLE); + } + + @Override + protected Equippable readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { + requireObject(node); + + JsonNode slot = node.get("slot"); + if (slot == null || !slot.isTextual()) { + throw new InvalidCustomMappingsFileException("Expected slot to be helmet, chestplate, leggings or boots"); + } + + return new Equippable(EquipmentSlot.valueOf(slot.asText().toUpperCase()), BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, + null, null, null, false, false, false); // Other properties are unused + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java new file mode 100644 index 00000000000..011c0210ed2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; + +public class FoodReader extends DataComponentReader { + + public FoodReader() { + super(DataComponentType.FOOD); + } + + @Override + protected FoodProperties readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { + requireObject(node); + + JsonNode canAlwaysEat = node.get("can_always_eat"); + return new FoodProperties(0, 0, canAlwaysEat != null && canAlwaysEat.asBoolean()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java new file mode 100644 index 00000000000..1d54e3314c3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; + +public class IntComponentReader extends DataComponentReader { + private final int minimum; + private final int maximum; + + public IntComponentReader(DataComponentType type, int minimum, int maximum) { + super(type); + this.minimum = minimum; + this.maximum = maximum; + } + + public IntComponentReader(DataComponentType type, int minimum) { + this(type, minimum, Integer.MAX_VALUE); + } + + @Override + protected Integer readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { + if (!node.isIntegralNumber()) { + throw new InvalidCustomMappingsFileException("Expected an integer number"); + } + int value = node.asInt(); + if (value < minimum || value > maximum) { + throw new InvalidCustomMappingsFileException("Expected integer to be in the range of [" + minimum + ", " + maximum + "]"); + } + return value; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java new file mode 100644 index 00000000000..583bddfc039 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; + +public class UseCooldownReader extends DataComponentReader { + + public UseCooldownReader() { + super(DataComponentType.USE_COOLDOWN); + } + + @Override + protected UseCooldown readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { + requireObject(node); + + JsonNode seconds = node.get("seconds"); + JsonNode cooldown_group = node.get("cooldown_group"); + + if (seconds == null || !seconds.isNumber()) { + throw new InvalidCustomMappingsFileException("Expected seconds to be a number"); + } + + if (cooldown_group == null || !cooldown_group.isTextual()) { + throw new InvalidCustomMappingsFileException("Expected cooldown group to be a resource location"); + } + + return new UseCooldown((float) seconds.asDouble(), Key.key(cooldown_group.asText())); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index c5f690f2ab1..a6c0dbc320c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -69,7 +69,6 @@ public static void populate(Map items, Multimap { if (initialCheck(item, items)) { - System.out.println("read item " + id + " item " + item); customItems.get(id).add(item); } }); From 0922b181f261348e9826f24222ba55212dd13800 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 29 Nov 2024 15:51:22 +0000 Subject: [PATCH 003/118] Fix equippable slot reading and add support for all consume animations --- .../components/readers/EquippableReader.java | 20 ++++++++-- .../CustomItemRegistryPopulator_v2.java | 40 +++++++++++-------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java index 8c5e1d6c361..b317955f657 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -34,7 +34,15 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; +import java.util.Map; + public class EquippableReader extends DataComponentReader { + private static final Map SLOTS = Map.of( + "head", EquipmentSlot.HELMET, + "chest", EquipmentSlot.CHESTPLATE, + "legs", EquipmentSlot.LEGGINGS, + "feet", EquipmentSlot.BOOTS + ); public EquippableReader() { super(DataComponentType.EQUIPPABLE); @@ -44,12 +52,16 @@ public EquippableReader() { protected Equippable readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { requireObject(node); - JsonNode slot = node.get("slot"); - if (slot == null || !slot.isTextual()) { - throw new InvalidCustomMappingsFileException("Expected slot to be helmet, chestplate, leggings or boots"); + JsonNode slotNode = node.get("slot"); + if (slotNode == null) { + throw new InvalidCustomMappingsFileException("Expected slot to be present"); + } + EquipmentSlot slot = SLOTS.get(slotNode.asText()); + if (slot == null) { + throw new InvalidCustomMappingsFileException("Expected slot to be head, chest, legs or feet"); } - return new Equippable(EquipmentSlot.valueOf(slot.asText().toUpperCase()), BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, + return new Equippable(slot, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, null, null, null, false, false, false); // Other properties are unused } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index a6c0dbc320c..3d987689409 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.registry.populator; import com.google.common.collect.Multimap; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -61,6 +60,18 @@ import java.util.Set; public class CustomItemRegistryPopulator_v2 { + // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. + private static final Map BEDROCK_ANIMATIONS = Map.of( + Consumable.ItemUseAnimation.NONE, 0, + Consumable.ItemUseAnimation.EAT, 1, + Consumable.ItemUseAnimation.DRINK, 2, + Consumable.ItemUseAnimation.BLOCK, 3, + Consumable.ItemUseAnimation.BOW, 4, + Consumable.ItemUseAnimation.SPEAR, 6, + Consumable.ItemUseAnimation.CROSSBOW, 9, + Consumable.ItemUseAnimation.SPYGLASS, 10, + Consumable.ItemUseAnimation.BRUSH, 12 + ); public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { // TODO @@ -256,34 +267,31 @@ private static void computeArmorProperties(Equippable equippable, /*String armor case BOOTS -> { componentBuilder.putString("minecraft:render_offsets", "boots"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_feet"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_feet"); + //itemProperties.putInt("enchantable_value", 15); TODO } case CHESTPLATE -> { componentBuilder.putString("minecraft:render_offsets", "chestplates"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_torso"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_torso"); + //itemProperties.putInt("enchantable_value", 15); TODO } case LEGGINGS -> { componentBuilder.putString("minecraft:render_offsets", "leggings"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_legs"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_legs"); + //itemProperties.putInt("enchantable_value", 15); TODO } case HELMET -> { componentBuilder.putString("minecraft:render_offsets", "helmets"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + //componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_head"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_head"); + //itemProperties.putInt("enchantable_value", 15); } } } @@ -291,9 +299,9 @@ private static void computeArmorProperties(Equippable equippable, /*String armor private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); // TODO check and confirm - // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds - // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively - itemProperties.putInt("use_animation", 0); // TODO + + itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); + // this component is required to allow the eat animation to play componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); } From 435b73df69da7664ceece4c3789f2b343dd89c73 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 29 Nov 2024 16:09:51 +0000 Subject: [PATCH 004/118] Add use cooldown support --- .../CustomItemRegistryPopulator_v2.java | 16 ++++++++++++++++ .../java/level/JavaCooldownTranslator.java | 17 +++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 3d987689409..c137b171104 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -51,12 +51,14 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; public class CustomItemRegistryPopulator_v2 { @@ -141,6 +143,11 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder); } + UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN); + if (useCooldown != null) { + computeUseCooldownProperties(useCooldown, componentBuilder); + } + // TODO block item/runtime ID, entity placer, chargeable, throwable, item cooldown, hat hardcoded on java, computeRenderOffsets(false, customItemDefinition.bedrockOptions(), componentBuilder); @@ -306,6 +313,15 @@ private static void computeConsumableProperties(Consumable consumable, boolean c componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); } + private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) { + Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null"); + componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder() + .putString("category", cooldown.cooldownGroup().asString()) + .putFloat("duration", cooldown.seconds()) + .build() + ); + } + private static void computeRenderOffsets(boolean isHat, CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) { if (isHat) { componentBuilder.remove("minecraft:render_offsets"); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java index 2b14f015fde..d854e71b12e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java @@ -45,23 +45,20 @@ public void translate(GeyserSession session, ClientboundCooldownPacket packet) { Key cooldownGroup = packet.getCooldownGroup(); Item item = Registries.JAVA_ITEM_IDENTIFIERS.get(cooldownGroup.asString()); - // Not every item, as of 1.19, appears to be server-driven. Just these two. + // Custom items can define an item cooldown using a custom cooldown group, which will be sent to the client if there's not a vanilla cooldown group + String cooldownCategory = cooldownGroup.asString(); + // Not every vanilla item, as of 1.19, appears to be server-driven. Just these two. // Use a map here if it gets too big. - String cooldownCategory; if (item == Items.GOAT_HORN) { cooldownCategory = "goat_horn"; } else if (item == Items.SHIELD) { cooldownCategory = "shield"; - } else { - cooldownCategory = null; } - if (cooldownCategory != null) { - PlayerStartItemCooldownPacket bedrockPacket = new PlayerStartItemCooldownPacket(); - bedrockPacket.setItemCategory(cooldownCategory); - bedrockPacket.setCooldownDuration(packet.getCooldownTicks()); - session.sendUpstreamPacket(bedrockPacket); - } + PlayerStartItemCooldownPacket bedrockPacket = new PlayerStartItemCooldownPacket(); + bedrockPacket.setItemCategory(cooldownCategory); + bedrockPacket.setCooldownDuration(packet.getCooldownTicks()); + session.sendUpstreamPacket(bedrockPacket); session.getWorldCache().setCooldown(cooldownGroup, packet.getCooldownTicks()); } From cce49fad0f32ba2b83461409c15a688dcd266007 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 08:55:30 +0000 Subject: [PATCH 005/118] Fix max stack size and max damage components --- .../mappings/components/DataComponentReader.java | 9 +++------ .../mappings/components/DataComponentReaders.java | 2 +- .../populator/CustomItemRegistryPopulator_v2.java | 6 +++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index 1baab15488f..9f00c6bfaff 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -26,13 +26,11 @@ package org.geysermc.geyser.registry.mappings.components; import com.fasterxml.jackson.databind.JsonNode; -import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; -@Getter public abstract class DataComponentReader { private final DataComponentType type; @@ -42,9 +40,8 @@ protected DataComponentReader(DataComponentType type) { protected abstract V readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException; - DataComponent> read(JsonNode node) throws InvalidCustomMappingsFileException { - // TODO primitives?? - return type.getDataComponentFactory().create(type, readDataComponent(node)); + void read(DataComponents components, JsonNode node) throws InvalidCustomMappingsFileException { + components.put(type, readDataComponent(node)); } protected static void requireObject(JsonNode node) throws InvalidCustomMappingsFileException { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 5ec00708fc2..7485217edda 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -49,7 +49,7 @@ public static void readDataComponent(DataComponents components, Key key, @NonNul if (reader == null) { throw new InvalidCustomMappingsFileException("Unknown data component " + key); } - components.getDataComponents().put(reader.getType(), reader.read(node)); + reader.read(components, node); } static { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index c137b171104..fe6bcead151 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -192,7 +192,9 @@ private static void setupBasicItemInfo(String name, CustomItemDefinition definit itemProperties.putBoolean("hand_equipped", options.displayHandheld()); int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); - int stackSize = maxDamage > 0 ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); + // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's itemProperties.putInt("max_stack_size", stackSize); if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here? @@ -445,8 +447,6 @@ private static DataComponents patchDataComponents(Item javaItem, CustomItemDefin components.put(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE, true); } - // TODO repairable? - if (javaItem instanceof ArmorItem armor) { // TODO equippable } From 1dd854a8b64c7cc5bc83ec86679bdc773e38fad8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 09:04:57 +0000 Subject: [PATCH 006/118] Implement block item and entity placer properties --- .../CustomItemRegistryPopulator_v2.java | 55 ++++++++++++------- .../populator/ItemRegistryPopulator.java | 2 +- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index fe6bcead151..df01eb0121d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -93,10 +93,10 @@ public static void populate(Map items, Multimap Date: Sat, 30 Nov 2024 09:24:53 +0000 Subject: [PATCH 007/118] Add documentation and change some stuff in the API --- .../item/custom/v2/BedrockCreativeTab.java | 34 +++++++++++ .../custom/v2/CustomItemBedrockOptions.java | 11 ++-- .../item/custom/v2/CustomItemDefinition.java | 59 ++++++++++++++----- .../CustomItemRegistryPopulator_v2.java | 4 +- .../populator/ItemRegistryPopulator.java | 2 +- 5 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java new file mode 100644 index 00000000000..658339ef495 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2; + +public enum BedrockCreativeTab { + NONE, + CONSTRUCTION, + NATURE, + EQUIPMENT, + ITEMS +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index 24f3003b27b..e9e295530cf 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -39,34 +39,35 @@ public interface CustomItemBedrockOptions { /** - * Gets the item's icon. When not present, {@code .} is used. + * Gets the item's icon. When not present, the item's Bedrock identifier is used. * * @return the item's icon + * @see CustomItemDefinition#icon() */ @Nullable String icon(); /** - * Gets if the item is allowed to be put into the offhand. + * If the item is allowed to be put into the offhand. Defaults to true. * * @return true if the item is allowed to be used in the offhand, false otherwise */ boolean allowOffhand(); /** - * Gets if the item should be displayed as handheld, like a tool. + * If the item should be displayed as handheld, like a tool. * * @return true if the item should be displayed as handheld, false otherwise */ boolean displayHandheld(); /** - * Gets the item's creative category, or tab id. + * The item's creative category. Defaults to {@code NONE}. * * @return the item's creative category */ @NonNull - OptionalInt creativeCategory(); + BedrockCreativeTab creativeCategory(); /** * Gets the item's creative group. diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 3162ecc8645..3b100aebacb 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -31,33 +31,62 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; /** - * This is used to store data for a custom item. - * - * V2. TODO. + * This is used to define a custom item and its properties. */ public interface CustomItemDefinition { /** - * Gets the item model this definition is for. This model can't be in the Minecraft namespace. + * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. If no namespace is given in the builder, the default + * namespace of the implementation is used. + * + * @implNote for Geyser, this is the {@code geyser_custom} namespace. */ - @NonNull Key model(); // TODO name?? + @NonNull Key bedrockIdentifier(); - default String name() { - return model().namespace() + "_" + model().value(); - } // TODO, also display name ? also, rename to identifier + /** + * The display name of the item. If none is set, the display name is taken from the item's Bedrock identifier. + */ + @NonNull String displayName(); - default String icon() { - return bedrockOptions().icon() == null ? name() : bedrockOptions().icon(); - } // TODO + /** + * The item model this definition is for. If the model is in the {@code minecraft} namespace, then the definition is required to have a predicate. + */ + @NonNull Key model(); - // TODO predicate + /** + * The icon used for this item. + * + *

If none is set in the item's Bedrock options, then the item's Bedrock identifier is used, + * the namespace separator replaced with {@code .} and the path separators ({@code /}) replaced with {@code _}.

+ */ + default @NonNull String icon() { + return bedrockOptions().icon() == null ? bedrockIdentifier().asString().replaceAll(":", ".").replaceAll("/", "_") : bedrockOptions().icon(); + } - // TODO bedrock options + // TODO predicate + /** + * The item's Bedrock options. These describe item properties that can't be described in item components, e.g. item texture size and if the item is allowed in the off-hand. + */ @NonNull CustomItemBedrockOptions bedrockOptions(); - // TODO components - + /** + * The item's data components. It is expected that the item always has these components on the server. If the components mismatch, bugs will occur. + * + *

Currently, the following components are supported:

+ * + *
    + *
  • {@code minecraft:consumable}
  • + *
  • {@code minecraft:equippable}
  • + *
  • {@code minecraft:food}
  • + *
  • {@code minecraft:max_damage}
  • + *
  • {@code minecraft:max_stack_size}
  • + *
  • {@code minecraft:use_cooldown}
  • + *
+ * + *

Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, + * and do not have to be specified here.

+ */ @NonNull DataComponents components(); static Builder builder(Key itemModel) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index df01eb0121d..17061fa8b92 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -104,7 +104,7 @@ public static GeyserCustomMappingData registerCustomItem(String customItemName, static boolean initialCheck(CustomItemDefinition item, Map mappings) { // TODO check if there's already a same model without predicate and this hasn't a predicate either - String name = item.name(); // TODO rename to identifier + String name = item.bedrockIdentifier(); // TODO rename to identifier if (name.isEmpty()) { GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); } else if (Character.isDigit(name.charAt(0))) { @@ -124,7 +124,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD NbtMapBuilder componentBuilder = NbtMap.builder(); DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition); - setupBasicItemInfo(customItemDefinition.name(), customItemDefinition, components, itemProperties, componentBuilder); + setupBasicItemInfo(customItemDefinition.bedrockIdentifier(), customItemDefinition, components, itemProperties, componentBuilder); boolean canDestroyInCreative = true; if (vanillaMapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. 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 8b201176e56..e2e6d5ee3f9 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 @@ -463,7 +463,7 @@ public static void populate() { for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.name(); // TODO bedrock identifier + non vanilla stuff + String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.bedrockIdentifier(); // TODO bedrock identifier + non vanilla stuff if (!registeredItemNames.add(customItemName)) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); From efae1f36659f0f7184a9e52d6ad649a13924bafc Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 09:34:54 +0000 Subject: [PATCH 008/118] Implement the changes in Geyser --- .../custom/v2/CustomItemBedrockOptions.java | 3 +-- .../item/custom/v2/CustomItemDefinition.java | 10 +++++---- .../GeyserCustomItemBedrockOptions.java | 11 +++++----- .../custom/GeyserCustomItemDefinition.java | 21 +++++++++++++++---- .../loader/ProviderRegistryLoader.java | 2 +- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index e9e295530cf..2f1de5673e4 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -30,7 +30,6 @@ import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; -import java.util.OptionalInt; import java.util.Set; /** @@ -113,7 +112,7 @@ interface Builder { Builder displayHandheld(boolean displayHandheld); - Builder creativeCategory(int creativeCategory); + Builder creativeCategory(BedrockCreativeTab creativeCategory); Builder creativeGroup(@Nullable String creativeGroup); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 3b100aebacb..3e66b2e484c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -36,10 +36,10 @@ public interface CustomItemDefinition { /** - * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. If no namespace is given in the builder, the default + * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. If the {@code minecraft} namespace is given in the builder, the default * namespace of the implementation is used. * - * @implNote for Geyser, this is the {@code geyser_custom} namespace. + * @implNote for Geyser, the default namespace is the {@code geyser_custom} namespace. */ @NonNull Key bedrockIdentifier(); @@ -89,12 +89,14 @@ public interface CustomItemDefinition { */ @NonNull DataComponents components(); - static Builder builder(Key itemModel) { - return GeyserApi.api().provider(Builder.class, itemModel); + static Builder builder(Key identifier, Key itemModel) { + return GeyserApi.api().provider(Builder.class, identifier, itemModel); } interface Builder { + Builder displayName(String displayName); + Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); // TODO do we want another format for this? diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java index 1d3d456525e..ee89e20ffc7 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java @@ -28,21 +28,21 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import java.util.HashSet; import java.util.Objects; -import java.util.OptionalInt; import java.util.Set; -public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, @NonNull OptionalInt creativeCategory, @Nullable String creativeGroup, +public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, @NonNull BedrockCreativeTab creativeCategory, @Nullable String creativeGroup, int textureSize, @Nullable CustomRenderOffsets renderOffsets, @NonNull Set tags) implements CustomItemBedrockOptions { public static class Builder implements CustomItemBedrockOptions.Builder { private String icon = null; private boolean allowOffhand = true; private boolean displayHandheld = false; - private OptionalInt creativeCategory = OptionalInt.empty(); + private BedrockCreativeTab creativeCategory = BedrockCreativeTab.NONE; private String creativeGroup = null; private int textureSize = 16; private CustomRenderOffsets renderOffsets = null; @@ -67,9 +67,8 @@ public Builder displayHandheld(boolean displayHandheld) { } @Override - public Builder creativeCategory(int creativeCategory) { - this.creativeCategory = OptionalInt.of(creativeCategory); - // TODO validation? + public Builder creativeCategory(BedrockCreativeTab creativeCategory) { + this.creativeCategory = creativeCategory; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 2df03326849..38bb22729ab 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -31,17 +31,30 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; -public record GeyserCustomItemDefinition(@NonNull Key model, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { +import java.util.HashMap; + +public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, + @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { + private final Key bedrockIdentifier; private final Key model; + private String displayName; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); - private DataComponents components; + private DataComponents components = new DataComponents(new HashMap<>()); - public Builder(Key model) { + public Builder(Key bedrockIdentifier, Key model) { + this.bedrockIdentifier = bedrockIdentifier; + this.displayName = bedrockIdentifier.asString(); this.model = model; } + @Override + public CustomItemDefinition.Builder displayName(String displayName) { + this.displayName = displayName; + return this; + } + @Override public CustomItemDefinition.Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options) { this.bedrockOptions = options.build(); @@ -56,7 +69,7 @@ public CustomItemDefinition.Builder components(@NonNull DataComponents component @Override public CustomItemDefinition build() { - return new GeyserCustomItemDefinition(model, bedrockOptions, components); + return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, bedrockOptions, components); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index ddb923fee34..b2351037fe9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -90,7 +90,7 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder()); // items v2 - providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Key) args[0])); + providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Key) args[0], (Key) args[1])); providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); // cameras From 8bbad9ddcd551af1712128d8fe82e5b4ae94f327 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 09:55:50 +0000 Subject: [PATCH 009/118] Implement the changes in Geyser part 2 --- .../mappings/versions/MappingsReader_v2.java | 22 ++++++++++++++--- .../CustomItemRegistryPopulator_v2.java | 24 +++++++++---------- .../populator/ItemRegistryPopulator.java | 5 ++-- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index d7dedf289bc..e35d1058884 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -29,7 +29,9 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.kyori.adventure.key.Key; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; @@ -79,13 +81,27 @@ public CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCu throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); } + JsonNode bedrockIdentifierNode = node.get("bedrock_identifier"); JsonNode model = node.get("model"); + + if (bedrockIdentifierNode == null || !bedrockIdentifierNode.isTextual() || bedrockIdentifierNode.asText().isEmpty()) { + throw new InvalidCustomMappingsFileException("An item entry has no bedrock identifier"); + } if (model == null || !model.isTextual() || model.asText().isEmpty()) { throw new InvalidCustomMappingsFileException("An item entry has no model"); } - CustomItemDefinition.Builder builder = CustomItemDefinition.builder(Key.key(model.asText())); - // TODO name, predicate + Key bedrockIdentifier = Key.key(bedrockIdentifierNode.asText()); + if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { + bedrockIdentifier = Key.key(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.value()); + } + CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, Key.key(model.asText())); + + if (node.has("display_name")) { + builder.displayName(node.get("display_name").asText()); + } + + // TODO predicate builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); @@ -116,7 +132,7 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node) { } if (node.has("creative_category")) { - builder.creativeCategory(node.get("creative_category").asInt()); + builder.creativeCategory(BedrockCreativeTab.valueOf(node.get("creative_category").asText().toUpperCase())); } if (node.has("creative_group")) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 17061fa8b92..1936ad5c64b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.registry.populator; import com.google.common.collect.Multimap; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -36,6 +37,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.item.GeyserCustomMappingData; @@ -77,7 +79,6 @@ public class CustomItemRegistryPopulator_v2 { public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { // TODO - System.out.println("reading mappings"); MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); // Load custom items from mappings files mappingsConfigReader.loadItemMappingsFromJson((id, item) -> { @@ -104,12 +105,9 @@ public static GeyserCustomMappingData registerCustomItem(String customItemName, static boolean initialCheck(CustomItemDefinition item, Map mappings) { // TODO check if there's already a same model without predicate and this hasn't a predicate either - String name = item.bedrockIdentifier(); // TODO rename to identifier - if (name.isEmpty()) { - GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); - } else if (Character.isDigit(name.charAt(0))) { - // As of 1.19.31 - GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!"); + Key name = item.bedrockIdentifier(); + if (name.namespace().equals(Key.MINECRAFT_NAMESPACE)) { + GeyserImpl.getInstance().getLogger().warning("Custom item namespace can't be minecraft"); } return true; } @@ -124,7 +122,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD NbtMapBuilder componentBuilder = NbtMap.builder(); DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition); - setupBasicItemInfo(customItemDefinition.bedrockIdentifier(), customItemDefinition, components, itemProperties, componentBuilder); + setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder); boolean canDestroyInCreative = true; if (vanillaMapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. @@ -166,7 +164,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD return builder; } - private static void setupBasicItemInfo(String name, CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + private static void setupBasicItemInfo(CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { CustomItemBedrockOptions options = definition.bedrockOptions(); NbtMap iconMap = NbtMap.builder() .putCompound("textures", NbtMap.builder() @@ -175,15 +173,15 @@ private static void setupBasicItemInfo(String name, CustomItemDefinition definit .build(); itemProperties.putCompound("minecraft:icon", iconMap); - if (options.creativeCategory().isPresent()) { - itemProperties.putInt("creative_category", options.creativeCategory().getAsInt()); + if (options.creativeCategory() != BedrockCreativeTab.NONE) { + itemProperties.putInt("creative_category", options.creativeCategory().ordinal()); if (options.creativeGroup() != null) { itemProperties.putString("creative_group", options.creativeGroup()); } } - componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", name).build()); // TODO + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build()); // TODO // Add a Geyser tag to the item, allowing Molang queries addItemTag(componentBuilder, "geyser:is_custom"); @@ -324,7 +322,7 @@ private static void computeArmorProperties(Equippable equippable, /*String armor private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks - itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); // TODO check and confirm + itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); 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 e2e6d5ee3f9..e5b8fcafc04 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 @@ -62,6 +62,7 @@ import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; @@ -463,7 +464,7 @@ public static void populate() { for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.bedrockIdentifier(); // TODO bedrock identifier + non vanilla stuff + String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().asString(); // TODO non vanilla stuff if (!registeredItemNames.add(customItemName)) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); @@ -474,7 +475,7 @@ public static void populate() { GeyserCustomMappingData customMapping = CustomItemRegistryPopulator_v2.registerCustomItem( customItemName, javaItem, mappingItem, customItem, customProtocolId); - if (customItem.bedrockOptions().creativeCategory().isPresent()) { + if (customItem.bedrockOptions().creativeCategory() != BedrockCreativeTab.NONE) { creativeItems.add(ItemData.builder() .netId(creativeNetId.incrementAndGet()) .definition(customMapping.itemDefinition()) From 7eeb5bbc90203ee35c299a803bfcae05873430e4 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 10:12:34 +0000 Subject: [PATCH 010/118] Remove old custom item options --- .../registry/populator/CustomItemRegistryPopulator.java | 2 +- .../registry/populator/CustomItemRegistryPopulator_v2.java | 2 +- .../geyser/registry/populator/ItemRegistryPopulator.java | 7 +------ .../org/geysermc/geyser/registry/type/ItemMapping.java | 5 ----- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 69821c04504..d311e161689 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -147,7 +147,7 @@ public boolean isValidRepairItem(Item other) { .toolType(customItemData.toolType()) .toolTier(customItemData.toolTier()) .translationString(customItemData.translationString()) - .customItemOptions(Collections.emptyList()) + .customItemDefinitions(Collections.emptyList()) .javaItem(item) .build(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 1936ad5c64b..3bc41cc54ab 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -181,7 +181,7 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp } } - componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build()); // TODO + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build()); // Add a Geyser tag to the item, allowing Molang queries addItemTag(componentBuilder, "geyser:is_custom"); 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 e5b8fcafc04..7044f7c2286 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 @@ -55,7 +55,6 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockData; @@ -498,7 +497,6 @@ public static void populate() { customItemDefinitions = Collections.emptyList(); } mappingBuilder.customItemDefinitions(customItemDefinitions); - mappingBuilder.customItemOptions(Collections.emptyList()); ItemMapping mapping = mappingBuilder.build(); @@ -525,7 +523,6 @@ public static void populate() { .bedrockDefinition(lightBlock) .bedrockData(0) .bedrockBlockDefinition(null) - .customItemOptions(Collections.emptyList()) .customItemDefinitions(Collections.emptyList()) .build(); lightBlocks.put(lightBlock.getRuntimeId(), lightBlockEntry); @@ -543,7 +540,6 @@ public static void populate() { .bedrockDefinition(lodestoneCompass) .bedrockData(0) .bedrockBlockDefinition(null) - .customItemOptions(Collections.emptyList()) .customItemDefinitions(Collections.emptyList()) .build(); @@ -559,8 +555,7 @@ public static void populate() { .bedrockDefinition(definition) .bedrockData(0) .bedrockBlockDefinition(null) - .customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart - .customItemDefinitions(Collections.emptyList()) // TODO do not remove the above todo + .customItemDefinitions(Collections.emptyList()) // TODO check for custom items with furnace minecart .build()); creativeItems.add(ItemData.builder() diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index db10914a859..fb1bc023dd1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -55,7 +55,6 @@ public class ItemMapping { null, null, Collections.emptyList(), - Collections.emptyList(), Items.AIR ); @@ -74,10 +73,6 @@ public class ItemMapping { String translationString; - @Deprecated - @NonNull - List> customItemOptions; // TODO remove - @NonNull List> customItemDefinitions; From 4514f552738c97e530917294a56144e9b8f96ab8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 10:23:07 +0000 Subject: [PATCH 011/118] Work on support for the old format --- .../api/item/custom/CustomItemData.java | 24 +++++++++++++++++++ .../api/item/custom/CustomItemOptions.java | 3 +++ .../mappings/versions/MappingsReader.java | 2 +- .../mappings/versions/MappingsReader_v1.java | 7 +++--- .../mappings/versions/MappingsReader_v2.java | 4 ++-- .../populator/ItemRegistryPopulator.java | 1 - 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 3b871cd74c5..0d4c704e875 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -25,16 +25,23 @@ package org.geysermc.geyser.api.item.custom; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import java.util.OptionalInt; import java.util.Set; /** * This is used to store data for a custom item. + * + * @deprecated use the new {@link org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition} */ +@Deprecated public interface CustomItemData { /** * Gets the item's name. @@ -118,6 +125,23 @@ static CustomItemData.Builder builder() { return GeyserApi.api().provider(CustomItemData.Builder.class); } + default CustomItemDefinition toDefinition(String javaItem) { + // TODO predicate + return CustomItemDefinition.builder(Key.key(javaItem), Key.key(javaItem)) + .displayName(displayName()) + .bedrockOptions(CustomItemBedrockOptions.builder() + .icon(icon()) + .allowOffhand(allowOffhand()) + .displayHandheld(displayHandheld()) + .creativeCategory(creativeCategory().isEmpty() ? BedrockCreativeTab.NONE : BedrockCreativeTab.values()[creativeCategory().getAsInt()]) + .creativeGroup(creativeGroup()) + .textureSize(textureSize()) + .renderOffsets(renderOffsets()) + .tags(tags()) + ) + .build(); + } + interface Builder { /** * Will also set the display name and icon to the provided parameter, if it is currently not set. diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java index 2ca19e20e56..cc9496cb331 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemOptions.java @@ -33,7 +33,10 @@ /** * This class represents the different ways you can register custom items + * + * @deprecated use the new {@link org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition}. */ +@Deprecated public interface CustomItemOptions { /** * Gets if the item should be unbreakable. diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java index a89773800e4..75827f04a0e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java @@ -39,7 +39,7 @@ public abstract class MappingsReader { public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); - public abstract CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException; + public abstract CustomItemDefinition readItemMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; protected @Nullable CustomRenderOffsets fromJsonNode(JsonNode node) { 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 802ad31373b..d09f204ccc6 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 @@ -95,7 +95,7 @@ public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer { try { - CustomItemDefinition customItemData = this.readItemMappingEntry(data); + CustomItemDefinition customItemData = this.readItemMappingEntry(entry.getKey(), data); consumer.accept(entry.getKey(), customItemData); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); @@ -160,7 +160,7 @@ private CustomItemOptions readItemCustomItemOptions(JsonNode node) { } @Override - public CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException { + public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { if (node == null || !node.isObject()) { throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); } @@ -215,8 +215,7 @@ public CustomItemDefinition readItemMappingEntry(JsonNode node) throws InvalidCu customItemData.tags(tagsSet); } - //return customItemData.build(); - return null; // TODO + return customItemData.build().toDefinition(identifier); } /** diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index e35d1058884..cedc51deb12 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -59,7 +59,7 @@ public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer { try { - CustomItemDefinition customItemDefinition = readItemMappingEntry(data); + CustomItemDefinition customItemDefinition = readItemMappingEntry(entry.getKey(), data); consumer.accept(entry.getKey(), customItemDefinition); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); @@ -76,7 +76,7 @@ public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer nonVanillaCustomItems = customItemsAllowed ? new ObjectArrayList<>() : Collections.emptyList(); if (customItemsAllowed) { - //CustomItemRegistryPopulator.populate(items, customItems, nonVanillaCustomItems); // TODO CustomItemRegistryPopulator_v2.populate(items, customItems, nonVanillaCustomItems); } From 8ea3c973f34c2dcaa2b048eb4ba9b7ac808c1514 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 30 Nov 2024 10:34:22 +0000 Subject: [PATCH 012/118] Start work on predicates, somewhat --- .../api/item/custom/v2/CustomItemDefinition.java | 10 +++++++++- .../registry/populator/ItemRegistryPopulator.java | 3 --- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 3e66b2e484c..1d55f43ef4f 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -50,6 +50,8 @@ public interface CustomItemDefinition { /** * The item model this definition is for. If the model is in the {@code minecraft} namespace, then the definition is required to have a predicate. + * + *

If multiple item definitions for a model are registered, then only one can have no predicate.

*/ @NonNull Key model(); @@ -63,7 +65,13 @@ public interface CustomItemDefinition { return bedrockOptions().icon() == null ? bedrockIdentifier().asString().replaceAll(":", ".").replaceAll("/", "_") : bedrockOptions().icon(); } - // TODO predicate + /** + * The predicate that has to match for this item to be used. These predicates are similar to the Java item model predicates. + * + *

If multiple predicates match, then the first registered item with a matching predicate is used. If no predicates match, then the item definition without a predicate + * is used, if any.

+ */ + void predicate(); /** * The item's Bedrock options. These describe item properties that can't be described in item components, e.g. item texture size and if the item is allowed in the off-hand. 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 fdaec724d69..b3afc38ecbd 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 @@ -489,9 +489,6 @@ public static void populate() { customIdMappings.put(customMapping.integerId(), customMapping.stringId()); } - - // Important for later to find the best match and accurately replicate Java behavior - Collections.reverse(customItemDefinitions); } else { customItemDefinitions = Collections.emptyList(); } From 7888956a369eb61a63f54b77075b01a6d386aed8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 1 Dec 2024 12:46:21 +0000 Subject: [PATCH 013/118] More work on predicates, I'll probably simplify it later --- .../item/custom/v2/CustomItemDefinition.java | 11 +- .../v2/predicate/CustomItemPredicate.java | 29 ++++ .../v2/predicate/ItemPredicateType.java | 49 +++++++ .../data/ConditionPredicateData.java | 41 ++++++ .../data/CustomModelDataPredicate.java | 29 ++++ .../v2/predicate/data/match/ChargeType.java | 32 +++++ .../data/match/MatchPredicateData.java | 29 ++++ .../data/match/MatchPredicateProperty.java | 52 +++++++ .../custom/GeyserCustomItemDefinition.java | 14 +- .../org/geysermc/geyser/item/type/Item.java | 2 +- .../geysermc/geyser/item/type/PotionItem.java | 2 +- .../geyser/item/type/ShulkerBoxItem.java | 2 +- .../mappings/versions/MappingsReader_v2.java | 90 +++++++++++- .../CustomItemRegistryPopulator_v2.java | 84 ++++++++++- .../geyser/session/cache/RegistryCache.java | 5 +- .../session/cache/registry/JavaRegistry.java | 12 +- .../cache/registry/RegistryEntryData.java | 31 ++++ .../cache/registry/SimpleJavaRegistry.java | 31 +++- .../translator/item/CustomItemTranslator.java | 132 ++++++++++-------- .../translator/item/ItemTranslator.java | 8 +- .../player/input/BedrockBlockActions.java | 2 +- 21 files changed, 603 insertions(+), 84 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java create mode 100644 core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryData.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 1d55f43ef4f..79dfe0270be 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -28,8 +28,11 @@ import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import java.util.List; + /** * This is used to define a custom item and its properties. */ @@ -66,12 +69,12 @@ public interface CustomItemDefinition { } /** - * The predicate that has to match for this item to be used. These predicates are similar to the Java item model predicates. + * The predicates that have to match for this item to be used. These predicates are similar to the Java item model predicates. * - *

If multiple predicates match, then the first registered item with a matching predicate is used. If no predicates match, then the item definition without a predicate + *

If all predicates match for multiple definitions, then the first registered item with all matching predicates is used. If no predicates match, then the item definition without any predicates * is used, if any.

*/ - void predicate(); + @NonNull List> predicates(); /** * The item's Bedrock options. These describe item properties that can't be described in item components, e.g. item texture size and if the item is allowed in the off-hand. @@ -105,6 +108,8 @@ interface Builder { Builder displayName(String displayName); + Builder predicate(@NonNull CustomItemPredicate predicate); + Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); // TODO do we want another format for this? diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java new file mode 100644 index 00000000000..e020a7dbc5d --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +public record CustomItemPredicate(ItemPredicateType type, T data) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java new file mode 100644 index 00000000000..6b733db9a7f --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateData; + +import java.util.HashMap; +import java.util.Map; + +public class ItemPredicateType { + private static final Map> TYPES = new HashMap<>(); + + public static final ItemPredicateType CONDITION = create("condition"); + public static final ItemPredicateType> MATCH = create("match"); + + public static ItemPredicateType getType(String name) { + return TYPES.get(name); + } + + private static ItemPredicateType create(String name) { + ItemPredicateType type = new ItemPredicateType<>(); + TYPES.put(name, type); + return type; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java new file mode 100644 index 00000000000..a5fa4f9ebfd --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate.data; + +// TODO maybe type should be a generic class with data, but this works for now +public record ConditionPredicateData(ConditionProperty property, boolean expected, int index) { + + public ConditionPredicateData(ConditionProperty property, boolean expected) { + this(property, expected, 0); + } + + // TODO maybe we can extend this + public enum ConditionProperty { + BROKEN, + DAMAGED, + CUSTOM_MODEL_DATA + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java new file mode 100644 index 00000000000..04646cc50ac --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate.data; + +public record CustomModelDataPredicate(T data, int index) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java new file mode 100644 index 00000000000..438cee772a4 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; + +public enum ChargeType { + NONE, + ARROW, + ROCKET +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java new file mode 100644 index 00000000000..82d077cafec --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; + +public record MatchPredicateData(MatchPredicateProperty property, T data) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java new file mode 100644 index 00000000000..5b7aa7e710e --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; + +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate; + +import java.util.HashMap; +import java.util.Map; + +// TODO can we do more? +public class MatchPredicateProperty { + private static final Map> PROPERTIES = new HashMap<>(); + + public static final MatchPredicateProperty CHARGE_TYPE = create("charge_type"); + public static final MatchPredicateProperty TRIM_MATERIAL = create("trim_material"); + public static final MatchPredicateProperty CONTEXT_DIMENSION = create("context_dimension"); + public static final MatchPredicateProperty> CUSTOM_MODEL_DATA = create("custom_model_data"); + + public static MatchPredicateProperty getProperty(String name) { + return PROPERTIES.get(name); + } + + private static MatchPredicateProperty create(String name) { + MatchPredicateProperty property = new MatchPredicateProperty<>(); + PROPERTIES.put(name, property); + return property; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 38bb22729ab..2edc1b013f3 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -29,16 +29,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; -public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, +public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, @NonNull List> predicates, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { private final Key bedrockIdentifier; private final Key model; + private final List> predicates = new ArrayList<>(); private String displayName; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); private DataComponents components = new DataComponents(new HashMap<>()); @@ -55,6 +59,12 @@ public CustomItemDefinition.Builder displayName(String displayName) { return this; } + @Override + public CustomItemDefinition.Builder predicate(@NonNull CustomItemPredicate predicate) { + predicates.add(predicate); + return this; + } + @Override public CustomItemDefinition.Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options) { this.bedrockOptions = options.build(); @@ -69,7 +79,7 @@ public CustomItemDefinition.Builder components(@NonNull DataComponents component @Override public CustomItemDefinition build() { - return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, bedrockOptions, components); + return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), bedrockOptions, components); } } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index 249936e5afa..881012b9891 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -125,7 +125,7 @@ public ItemData.Builder translateToBedrock(GeyserSession session, int count, Dat .damage(mapping.getBedrockData()) .count(count); - ItemTranslator.translateCustomItem(components, builder, mapping); + ItemTranslator.translateCustomItem(session, components, builder, mapping); return builder; } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java index 89e60b32506..1cfbdd68d01 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java @@ -49,7 +49,7 @@ public ItemData.Builder translateToBedrock(GeyserSession session, int count, Dat if (components == null) return super.translateToBedrock(session, count, components, mapping, mappings); PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS); if (potionContents != null) { - ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(components, mapping); + ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, components, mapping); if (customItemDefinition == null) { Potion potion = Potion.getByJavaId(potionContents.getPotionId()); if (potion != null) { diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java index c3b739adc45..f57733e7166 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java @@ -74,7 +74,7 @@ public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNul if (boxComponents != null) { // Check for custom items - ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(boxComponents, boxMapping); + ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, boxComponents, boxMapping); if (customItemDefinition != null) { bedrockIdentifier = customItemDefinition.getIdentifier(); bedrockData = 0; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index cedc51deb12..2a09a2d6b19 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -29,11 +29,19 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ItemPredicateType; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; @@ -101,7 +109,7 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod builder.displayName(node.get("display_name").asText()); } - // TODO predicate + readPredicates(builder, node.get("predicate")); builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); @@ -166,6 +174,86 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node) { return builder; } + private void readPredicates(CustomItemDefinition.Builder builder, JsonNode node) throws InvalidCustomMappingsFileException { + if (node == null) { + return; + } + + if (node.isObject()) { + readPredicate(builder, node); + } else if (node.isArray()) { + node.forEach(predicate -> { + try { + readPredicate(builder, predicate); + } catch (InvalidCustomMappingsFileException e) { + GeyserImpl.getInstance().getLogger().error("Error in reading predicate", e); // TODO log this better + } + }); + } else { + throw new InvalidCustomMappingsFileException("Expected predicate key to be a list of predicates or a predicate"); + } + } + + private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNode node) throws InvalidCustomMappingsFileException { + if (!node.isObject()) { + throw new InvalidCustomMappingsFileException("Expected predicate to be an object"); + } + + JsonNode typeNode = node.get("type"); + if (typeNode == null || !typeNode.isTextual()) { + throw new InvalidCustomMappingsFileException("Predicate missing type key"); + } + + ItemPredicateType type = ItemPredicateType.getType(typeNode.asText()); + JsonNode propertyNode = node.get("property"); + if (propertyNode == null || !propertyNode.isTextual()) { + throw new InvalidCustomMappingsFileException("Predicate missing property key"); + } + + if (type == ItemPredicateType.CONDITION) { + try { + ConditionPredicateData.ConditionProperty property = ConditionPredicateData.ConditionProperty.valueOf(propertyNode.asText().toUpperCase()); + JsonNode expected = node.get("expected"); + JsonNode index = node.get("index"); + + builder.predicate(new CustomItemPredicate<>(ItemPredicateType.CONDITION, new ConditionPredicateData(property, + expected == null || expected.asBoolean(), index == null || !index.isIntegralNumber() ? 0 : index.asInt()))); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("Unknown property " + propertyNode.asText()); + } + } else if (type == ItemPredicateType.MATCH) { + MatchPredicateProperty property = MatchPredicateProperty.getProperty(propertyNode.asText()); + if (property == null) { + throw new InvalidCustomMappingsFileException("Unknown property " + propertyNode.asText()); + } + + JsonNode value = node.get("value"); + if (value == null || !value.isTextual()) { + throw new InvalidCustomMappingsFileException("Predicate missing value key"); + } + + if (property == MatchPredicateProperty.CHARGE_TYPE) { + try { + ChargeType chargeType = ChargeType.valueOf(value.asText().toUpperCase()); + builder.predicate(new CustomItemPredicate<>(ItemPredicateType.MATCH, + new MatchPredicateData<>(MatchPredicateProperty.CHARGE_TYPE, chargeType))); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("Unknown charge type " + value.asText()); + } + } else if (property == MatchPredicateProperty.TRIM_MATERIAL || property == MatchPredicateProperty.CONTEXT_DIMENSION) { + builder.predicate(new CustomItemPredicate<>(ItemPredicateType.MATCH, + new MatchPredicateData<>((MatchPredicateProperty) property, Key.key(value.asText())))); // TODO + } else if (property == MatchPredicateProperty.CUSTOM_MODEL_DATA) { + JsonNode index = node.get("index"); + if (index == null || !index.isIntegralNumber()) { + throw new InvalidCustomMappingsFileException("Predicate missing index key"); + } + builder.predicate(new CustomItemPredicate<>(ItemPredicateType.MATCH, + new MatchPredicateData<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataPredicate<>(value.asText(), index.asInt())))); + } + } + } + @Override public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { return null; // TODO diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 3bc41cc54ab..1fc3e3c24e0 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -40,6 +40,7 @@ import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.WearableSlot; @@ -61,6 +62,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; public class CustomItemRegistryPopulator_v2 { @@ -82,7 +84,7 @@ public static void populate(Map items, Multimap { - if (initialCheck(item, items)) { + if (initialCheck(id, item, customItems, items)) { customItems.get(id).add(item); } }); @@ -103,15 +105,84 @@ public static GeyserCustomMappingData registerCustomItem(String customItemName, return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); } - static boolean initialCheck(CustomItemDefinition item, Map mappings) { + private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap registered, Map mappings) { // TODO check if there's already a same model without predicate and this hasn't a predicate either - Key name = item.bedrockIdentifier(); - if (name.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - GeyserImpl.getInstance().getLogger().warning("Custom item namespace can't be minecraft"); + if (!mappings.containsKey(identifier)) { + GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier()); + return false; } + Key bedrockIdentifier = item.bedrockIdentifier(); + if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { + GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft"); + return false; + } else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) { + GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be minecraft without a predicate"); + return false; + } + + for (Map.Entry entry : registered.entries()) { + if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) { + GeyserImpl.getInstance().getLogger().error("Duplicate custom item definition for Bedrock ID " + item.bedrockIdentifier()); + return false; + } + Optional error = checkPredicate(entry, identifier, item); + if (error.isPresent()) { + GeyserImpl.getInstance().getLogger().error("An existing item definition for the Java item " + identifier + " was already registered that conflicts with this one!"); + GeyserImpl.getInstance().getLogger().error("First entry: " + entry.getValue().bedrockIdentifier()); + GeyserImpl.getInstance().getLogger().error("Second entry: " + item.bedrockIdentifier()); + GeyserImpl.getInstance().getLogger().error(error.orElseThrow()); + } + } + return true; } + /** + * Returns an error message if there was a conflict, or an empty optional otherwise + */ + private static Optional checkPredicate(Map.Entry existing, String identifier, CustomItemDefinition item) { + // TODO this is probably wrong + // If the definitions are for different Java items or models then it doesn't matter + if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) { + return Optional.empty(); + } + // If they both don't have predicates they conflict + if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) { + return Optional.of("Both entries don't have predicates, the first must have a predicate"); + } + + // If a previously registered entry does have predicates, and this entry doesn't, then they also conflict + // Entries with predicates must always be first + if (existing.getValue().predicates().isEmpty() && !item.predicates().isEmpty()) { + return Optional.of("The first entry has no predicates, meaning that one will always be used"); + } else if (item.predicates().isEmpty()) { + return Optional.empty(); // Item definitions are correctly ordered + } + + // If all predicates of an existing entry also exist in a new entry, then the new entry is invalid + // This makes it required to order definitions correctly, so that "fallback predicates" are added last: + // + // A && B -> item1 + // A -> item2 + // + // Is the correct order, not + // + // A -> item2 + // A && B -> item1 + boolean existingHasAllPredicates = true; + for (CustomItemPredicate predicate : existing.getValue().predicates()) { + if (!item.predicates().contains(predicate)) { + existingHasAllPredicates = false; + break; + } + } + + if (existingHasAllPredicates) { + return Optional.of("Reorder your entries so that the one with the least amount of predicates is last"); + } + return Optional.empty(); + } + private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping, String customItemName, int customItemId) { NbtMapBuilder builder = NbtMap.builder() @@ -325,6 +396,9 @@ private static void computeConsumableProperties(Consumable consumable, boolean c itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); + componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() + .putString("value", consumable.animation().toString().toLowerCase()) + .build()); // TODO check // this component is required to allow the eat animation to play componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java index ecd293bff26..1f0de4444a5 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java @@ -51,6 +51,7 @@ import org.geysermc.geyser.session.cache.registry.JavaRegistry; import org.geysermc.geyser.session.cache.registry.JavaRegistryKey; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; +import org.geysermc.geyser.session.cache.registry.RegistryEntryData; import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry; import org.geysermc.geyser.text.ChatDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; @@ -189,7 +190,7 @@ private static void register(Key registry, Function builder = new ArrayList<>(entries.size()); + List> builder = new ArrayList<>(entries.size()); for (int i = 0; i < entries.size(); i++) { RegistryEntry entry = entries.get(i); // If the data is null, that's the server telling us we need to use our default values. @@ -203,7 +204,7 @@ private static void register(Key registry, Function(entry.getId(), cacheEntry)); } localCache.reset(builder); }); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java index d7c7782eadf..66f2531f556 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java @@ -39,15 +39,25 @@ public interface JavaRegistry { */ T byId(@NonNegative int id); + /** + * Looks up a registry entry by its ID, and returns it wrapped in {@link RegistryEntryData} so that its registered key is also known. The object can be null, or not present. + */ + RegistryEntryData entryById(@NonNegative int id); + /** * Reverse looks-up an object to return its network ID, or -1. */ int byValue(T value); + /** + * Reverse looks-up an object to return it wrapped in {@link RegistryEntryData}, or null. + */ + RegistryEntryData entryByValue(T value); + /** * Resets the objects by these IDs. */ - void reset(List values); + void reset(List> values); /** * All values of this registry, as a list. diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryData.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryData.java new file mode 100644 index 00000000000..91b04fd2eb0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryData.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache.registry; + +import net.kyori.adventure.key.Key; + +public record RegistryEntryData(Key key, T data) { +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java index 7b79a40be03..92495525b88 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java @@ -31,10 +31,18 @@ import java.util.List; public class SimpleJavaRegistry implements JavaRegistry { - protected final ObjectArrayList values = new ObjectArrayList<>(); + protected final ObjectArrayList> values = new ObjectArrayList<>(); @Override public T byId(@NonNegative int id) { + if (id < 0 || id >= this.values.size()) { + return null; + } + return this.values.get(id).data(); + } + + @Override + public RegistryEntryData entryById(@NonNegative int id) { if (id < 0 || id >= this.values.size()) { return null; } @@ -43,11 +51,26 @@ public T byId(@NonNegative int id) { @Override public int byValue(T value) { - return this.values.indexOf(value); + for (int i = 0; i < this.values.size(); i++) { + if (values.get(i).data().equals(value)) { + return i; + } + } + return -1; + } + + @Override + public RegistryEntryData entryByValue(T value) { + for (RegistryEntryData entry : this.values) { + if (entry.data().equals(value)) { + return entry; + } + } + return null; } @Override - public void reset(List values) { + public void reset(List> values) { this.values.clear(); this.values.addAll(values); this.values.trim(); @@ -55,7 +78,7 @@ public void reset(List values) { @Override public List values() { - return this.values; + return this.values.stream().map(RegistryEntryData::data).toList(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 9b74898bf15..23e37366515 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -26,8 +26,21 @@ package org.geysermc.geyser.translator.item; import net.kyori.adventure.key.Key; +import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ItemPredicateType; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.level.JavaDimension; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.registry.RegistryEntryData; import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ArmorTrim; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import it.unimi.dsi.fastutil.Pair; @@ -43,7 +56,7 @@ public final class CustomItemTranslator { @Nullable - public static ItemDefinition getCustomItem(DataComponents components, ItemMapping mapping) { + public static ItemDefinition getCustomItem(GeyserSession session, DataComponents components, ItemMapping mapping) { if (components == null) { return null; } @@ -55,79 +68,82 @@ public static ItemDefinition getCustomItem(DataComponents components, ItemMappin Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); // TODO fallback onto default item model (when thats done by chris) + // TODO check if definitions/predicates are in the correct order for (Pair customModel : customItems) { // TODO Predicates if (customModel.first().model().equals(itemModel)) { - return customModel.second(); + boolean allMatch = true; + for (CustomItemPredicate predicate : customModel.first().predicates()) { + if (!predicateMatches(session, predicate, components)) { + allMatch = false; + break; + } + } + if (allMatch) { + return customModel.second(); + } } } return null; - /* - List> customMappings = mapping.getCustomItemOptions(); - if (customMappings.isEmpty()) { - return null; - } - - int customModelData = components.getOrDefault(DataComponentType.CUSTOM_MODEL_DATA, 0); - boolean checkDamage = mapping.getJavaItem().maxDamage() > 0; - int damage = !checkDamage ? 0 : components.getOrDefault(DataComponentType.DAMAGE, 0); - boolean unbreakable = checkDamage && !isDamaged(components, damage); - - for (Pair mappingTypes : customMappings) { - CustomItemOptions options = mappingTypes.key(); - - // Code note: there may be two or more conditions that a custom item must follow, hence the "continues" - // here with the return at the end. - - // Implementation details: Java's predicate system works exclusively on comparing float numbers. - // A value doesn't necessarily have to match 100%; it just has to be the first to meet all predicate conditions. - // This is also why the order of iteration is important as the first to match will be the chosen display item. - // For example, if CustomModelData is set to 2f as the requirement, then the NBT can be any number greater or equal (2, 3, 4...) - // The same behavior exists for Damage (in fraction form instead of whole numbers), - // and Damaged/Unbreakable handles no damage as 0f and damaged as 1f. - - if (checkDamage) { - if (unbreakable && options.unbreakable() == TriState.FALSE) { - continue; - } + } - OptionalInt damagePredicate = options.damagePredicate(); - if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { - continue; + private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, DataComponents components) { + if (predicate.type() == ItemPredicateType.CONDITION) { + ConditionPredicateData data = (ConditionPredicateData) predicate.data(); + return switch (data.property()) { + case BROKEN -> nextDamageWillBreak(components); + case DAMAGED -> isDamaged(components); + case CUSTOM_MODEL_DATA -> false; // TODO 1.21.4 + }; + } else if (predicate.type() == ItemPredicateType.MATCH) { + MatchPredicateData data = (MatchPredicateData) predicate.data(); + + if (data.property() == MatchPredicateProperty.CHARGE_TYPE) { + ChargeType expected = (ChargeType) data.data(); + List charged = components.get(DataComponentType.CHARGED_PROJECTILES); + if (charged == null) { + return expected == ChargeType.NONE; + } else if (expected == ChargeType.ROCKET) { + for (ItemStack projectile : charged) { + if (projectile.getId() == Items.FIREWORK_ROCKET.javaId()) { + return true; + } + } + return false; } - } else { - if (options.unbreakable() != TriState.NOT_SET || options.damagePredicate().isPresent()) { - // These will never match on this item. 1.19.2 behavior - // Maybe move this to CustomItemRegistryPopulator since it'll be the same for every item? If so, add a test. - continue; + return true; + } else if (data.property() == MatchPredicateProperty.TRIM_MATERIAL) { + Key material = (Key) data.data(); + ArmorTrim trim = components.get(DataComponentType.TRIM); + if (trim == null || trim.material().isCustom()) { + return false; } + RegistryEntryData registered = session.getRegistryCache().trimMaterials().entryById(trim.material().id()); + return registered != null && registered.key().equals(material); + } else if (data.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { + Key dimension = (Key) data.data(); + RegistryEntryData registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType()); + return registered != null && dimension.equals(registered.key()); // TODO check if this works + } else if (data.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { + // TODO 1.21.4 + return false; } - - OptionalInt customModelDataOption = options.customModelData(); - if (customModelDataOption.isPresent() && customModelData < customModelDataOption.getAsInt()) { - continue; - } - - if (options.defaultItem()) { - return null; - } - - return mappingTypes.value(); } - return null;*/ + throw new IllegalStateException("Unimplemented predicate type"); } - /* These two functions are based off their Mojmap equivalents from 1.19.2 */ + /* These three functions are based off their Mojmap equivalents from 1.21.3 */ + + private static boolean nextDamageWillBreak(DataComponents components) { + return isDamageableItem(components) && components.getOrDefault(DataComponentType.DAMAGE, 0) >= components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) - 1; + } - private static boolean isDamaged(DataComponents components, int damage) { - return isDamagableItem(components) && damage > 0; + private static boolean isDamaged(DataComponents components) { + return isDamageableItem(components) && components.getOrDefault(DataComponentType.DAMAGE, 0) > 0; } - private static boolean isDamagableItem(DataComponents components) { - // mapping.getMaxDamage > 0 should also be checked (return false if not true) but we already check prior to this function - Boolean unbreakable = components.get(DataComponentType.UNBREAKABLE); - // Tag must either not be present or be set to false - return unbreakable == null || !unbreakable; + private static boolean isDamageableItem(DataComponents components) { + return components.getOrDefault(DataComponentType.UNBREAKABLE, false) && components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) > 0; } private CustomItemTranslator() { diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 3cfd00233f5..5951eea2f3b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -215,7 +215,7 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack translatePlayerHead(session, components, builder); } - translateCustomItem(components, builder, bedrockItem); + translateCustomItem(session, components, builder, bedrockItem); if (components != null) { // Translate the canDestroy and canPlaceOn Java components @@ -428,7 +428,7 @@ public static ItemDefinition getBedrockItemDefinition(GeyserSession session, @No } } - ItemDefinition definition = CustomItemTranslator.getCustomItem(itemStack.getComponents(), mapping); + ItemDefinition definition = CustomItemTranslator.getCustomItem(session, itemStack.getComponents(), mapping); if (definition == null) { // No custom item return itemDefinition; @@ -469,8 +469,8 @@ public static String getCustomName(GeyserSession session, DataComponents compone /** * Translates the custom model data of an item */ - public static void translateCustomItem(DataComponents components, ItemData.Builder builder, ItemMapping mapping) { - ItemDefinition definition = CustomItemTranslator.getCustomItem(components, mapping); + public static void translateCustomItem(GeyserSession session, DataComponents components, ItemData.Builder builder, ItemMapping mapping) { + ItemDefinition definition = CustomItemTranslator.getCustomItem(session, components, mapping); if (definition != null) { builder.definition(definition); builder.blockDefinition(null); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java index 061a04b7765..2d593dc0948 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java @@ -93,7 +93,7 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct // If the block is custom or the breaking item is custom, we must keep track of break time ourselves GeyserItemStack item = session.getPlayerInventory().getItemInHand(); ItemMapping mapping = item.getMapping(session); - ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(item.getComponents(), mapping) : null; + ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(session, item.getComponents(), mapping) : null; CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(blockState); SkullCache.Skull skull = session.getSkullCache().getSkulls().get(vector); From ce55d88ab83673976cc2e4d5f35038592e601519 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 1 Dec 2024 15:31:02 +0000 Subject: [PATCH 014/118] Clean up predicates a bit --- .../item/custom/v2/CustomItemDefinition.java | 5 +- .../v2/predicate/CustomItemPredicate.java | 2 +- .../v2/predicate/ItemPredicateType.java | 49 --------------- ...icateData.java => ConditionPredicate.java} | 8 +-- ...PredicateData.java => MatchPredicate.java} | 4 +- .../data/match/MatchPredicateProperty.java | 22 ++----- .../custom/GeyserCustomItemDefinition.java | 6 +- .../mappings/versions/MappingsReader_v2.java | 59 +++++++++---------- .../CustomItemRegistryPopulator.java | 2 +- .../CustomItemRegistryPopulator_v2.java | 45 +++++--------- .../populator/ItemRegistryPopulator.java | 18 +++--- .../geyser/registry/type/ItemMapping.java | 14 ++--- .../translator/item/CustomItemTranslator.java | 44 +++++++------- 13 files changed, 102 insertions(+), 176 deletions(-) delete mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/{ConditionPredicateData.java => ConditionPredicate.java} (87%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/{MatchPredicateData.java => MatchPredicate.java} (87%) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 79dfe0270be..5e8aa63c048 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -36,6 +36,7 @@ /** * This is used to define a custom item and its properties. */ +// TODO note that definitions will be sorted by predicates public interface CustomItemDefinition { /** @@ -74,7 +75,7 @@ public interface CustomItemDefinition { *

If all predicates match for multiple definitions, then the first registered item with all matching predicates is used. If no predicates match, then the item definition without any predicates * is used, if any.

*/ - @NonNull List> predicates(); + @NonNull List predicates(); /** * The item's Bedrock options. These describe item properties that can't be described in item components, e.g. item texture size and if the item is allowed in the off-hand. @@ -108,7 +109,7 @@ interface Builder { Builder displayName(String displayName); - Builder predicate(@NonNull CustomItemPredicate predicate); + Builder predicate(@NonNull CustomItemPredicate predicate); Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java index e020a7dbc5d..eab23e4495b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -25,5 +25,5 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; -public record CustomItemPredicate(ItemPredicateType type, T data) { +public interface CustomItemPredicate { // TODO this probably needs to be different since people can implement this } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java deleted file mode 100644 index 6b733db9a7f..00000000000 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ItemPredicateType.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2024 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom.v2.predicate; - -import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicateData; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateData; - -import java.util.HashMap; -import java.util.Map; - -public class ItemPredicateType { - private static final Map> TYPES = new HashMap<>(); - - public static final ItemPredicateType CONDITION = create("condition"); - public static final ItemPredicateType> MATCH = create("match"); - - public static ItemPredicateType getType(String name) { - return TYPES.get(name); - } - - private static ItemPredicateType create(String name) { - ItemPredicateType type = new ItemPredicateType<>(); - TYPES.put(name, type); - return type; - } -} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java similarity index 87% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java index a5fa4f9ebfd..7ea71e3a8de 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicateData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java @@ -25,12 +25,10 @@ package org.geysermc.geyser.api.item.custom.v2.predicate.data; -// TODO maybe type should be a generic class with data, but this works for now -public record ConditionPredicateData(ConditionProperty property, boolean expected, int index) { +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; - public ConditionPredicateData(ConditionProperty property, boolean expected) { - this(property, expected, 0); - } +// TODO maybe type should be a generic class with data, but this works for now +public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate { // TODO maybe we can extend this public enum ConditionProperty { diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java similarity index 87% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java index 82d077cafec..cbecc88f780 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java @@ -25,5 +25,7 @@ package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; -public record MatchPredicateData(MatchPredicateProperty property, T data) { +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; + +public record MatchPredicate(MatchPredicateProperty property, T data) implements CustomItemPredicate { } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java index 5b7aa7e710e..a512ff3f73f 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java @@ -28,25 +28,15 @@ import net.kyori.adventure.key.Key; import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate; -import java.util.HashMap; -import java.util.Map; - // TODO can we do more? public class MatchPredicateProperty { - private static final Map> PROPERTIES = new HashMap<>(); - - public static final MatchPredicateProperty CHARGE_TYPE = create("charge_type"); - public static final MatchPredicateProperty TRIM_MATERIAL = create("trim_material"); - public static final MatchPredicateProperty CONTEXT_DIMENSION = create("context_dimension"); - public static final MatchPredicateProperty> CUSTOM_MODEL_DATA = create("custom_model_data"); - public static MatchPredicateProperty getProperty(String name) { - return PROPERTIES.get(name); - } + public static final MatchPredicateProperty CHARGE_TYPE = create(); + public static final MatchPredicateProperty TRIM_MATERIAL = create(); + public static final MatchPredicateProperty CONTEXT_DIMENSION = create(); + public static final MatchPredicateProperty> CUSTOM_MODEL_DATA = create(); - private static MatchPredicateProperty create(String name) { - MatchPredicateProperty property = new MatchPredicateProperty<>(); - PROPERTIES.put(name, property); - return property; + private static MatchPredicateProperty create() { + return new MatchPredicateProperty<>(); } } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 2edc1b013f3..4e5b55c3537 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -36,13 +36,13 @@ import java.util.HashMap; import java.util.List; -public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, @NonNull List> predicates, +public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, @NonNull List predicates, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { private final Key bedrockIdentifier; private final Key model; - private final List> predicates = new ArrayList<>(); + private final List predicates = new ArrayList<>(); private String displayName; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); private DataComponents components = new DataComponents(new HashMap<>()); @@ -60,7 +60,7 @@ public CustomItemDefinition.Builder displayName(String displayName) { } @Override - public CustomItemDefinition.Builder predicate(@NonNull CustomItemPredicate predicate) { + public CustomItemDefinition.Builder predicate(@NonNull CustomItemPredicate predicate) { predicates.add(predicate); return this; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 2a09a2d6b19..d361d382f85 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -35,12 +35,10 @@ import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.ItemPredicateType; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; @@ -203,53 +201,50 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo if (typeNode == null || !typeNode.isTextual()) { throw new InvalidCustomMappingsFileException("Predicate missing type key"); } + String type = typeNode.asText(); - ItemPredicateType type = ItemPredicateType.getType(typeNode.asText()); JsonNode propertyNode = node.get("property"); if (propertyNode == null || !propertyNode.isTextual()) { throw new InvalidCustomMappingsFileException("Predicate missing property key"); } + String property = propertyNode.asText(); - if (type == ItemPredicateType.CONDITION) { + if (type.equals("condition")) { try { - ConditionPredicateData.ConditionProperty property = ConditionPredicateData.ConditionProperty.valueOf(propertyNode.asText().toUpperCase()); + ConditionPredicate.ConditionProperty conditionProperty = ConditionPredicate.ConditionProperty.valueOf(property.toUpperCase()); JsonNode expected = node.get("expected"); JsonNode index = node.get("index"); - builder.predicate(new CustomItemPredicate<>(ItemPredicateType.CONDITION, new ConditionPredicateData(property, - expected == null || expected.asBoolean(), index == null || !index.isIntegralNumber() ? 0 : index.asInt()))); + builder.predicate(new ConditionPredicate(conditionProperty, + expected == null || expected.asBoolean(), index == null || !index.isIntegralNumber() ? 0 : index.asInt())); } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown property " + propertyNode.asText()); + throw new InvalidCustomMappingsFileException("Unknown property " + property); } - } else if (type == ItemPredicateType.MATCH) { - MatchPredicateProperty property = MatchPredicateProperty.getProperty(propertyNode.asText()); - if (property == null) { - throw new InvalidCustomMappingsFileException("Unknown property " + propertyNode.asText()); - } - + } else if (type.equals("match")) { JsonNode value = node.get("value"); if (value == null || !value.isTextual()) { throw new InvalidCustomMappingsFileException("Predicate missing value key"); } - if (property == MatchPredicateProperty.CHARGE_TYPE) { - try { - ChargeType chargeType = ChargeType.valueOf(value.asText().toUpperCase()); - builder.predicate(new CustomItemPredicate<>(ItemPredicateType.MATCH, - new MatchPredicateData<>(MatchPredicateProperty.CHARGE_TYPE, chargeType))); - } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown charge type " + value.asText()); + switch (property) { + case "charge_type" -> { + try { + ChargeType chargeType = ChargeType.valueOf(value.asText().toUpperCase()); + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CHARGE_TYPE, chargeType)); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("Unknown charge type " + value.asText()); + } } - } else if (property == MatchPredicateProperty.TRIM_MATERIAL || property == MatchPredicateProperty.CONTEXT_DIMENSION) { - builder.predicate(new CustomItemPredicate<>(ItemPredicateType.MATCH, - new MatchPredicateData<>((MatchPredicateProperty) property, Key.key(value.asText())))); // TODO - } else if (property == MatchPredicateProperty.CUSTOM_MODEL_DATA) { - JsonNode index = node.get("index"); - if (index == null || !index.isIntegralNumber()) { - throw new InvalidCustomMappingsFileException("Predicate missing index key"); + case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, Key.key(value.asText()))); // TODO + case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, Key.key(value.asText()))); // TODO + case "custom_model_data" -> { + JsonNode index = node.get("index"); + if (index == null || !index.isIntegralNumber()) { + throw new InvalidCustomMappingsFileException("Predicate missing index key"); + } + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataPredicate<>(value.asText(), index.asInt()))); } - builder.predicate(new CustomItemPredicate<>(ItemPredicateType.MATCH, - new MatchPredicateData<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataPredicate<>(value.asText(), index.asInt())))); + default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index d311e161689..b51ed94780c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -147,7 +147,7 @@ public boolean isValidRepairItem(Item other) { .toolType(customItemData.toolType()) .toolTier(customItemData.toolTier()) .translationString(customItemData.translationString()) - .customItemDefinitions(Collections.emptyList()) + .customItemDefinitions(null) .javaItem(item) .build(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 1fc3e3c24e0..8ee2cf32af9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -116,7 +116,7 @@ private static boolean initialCheck(String identifier, CustomItemDefinition item GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft"); return false; } else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) { - GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be minecraft without a predicate"); + GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be in the minecraft namespace without a predicate"); return false; } @@ -140,46 +140,29 @@ private static boolean initialCheck(String identifier, CustomItemDefinition item /** * Returns an error message if there was a conflict, or an empty optional otherwise */ + // TODO maybe simplify this private static Optional checkPredicate(Map.Entry existing, String identifier, CustomItemDefinition item) { - // TODO this is probably wrong // If the definitions are for different Java items or models then it doesn't matter if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) { return Optional.empty(); } // If they both don't have predicates they conflict if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) { - return Optional.of("Both entries don't have predicates, the first must have a predicate"); - } - - // If a previously registered entry does have predicates, and this entry doesn't, then they also conflict - // Entries with predicates must always be first - if (existing.getValue().predicates().isEmpty() && !item.predicates().isEmpty()) { - return Optional.of("The first entry has no predicates, meaning that one will always be used"); - } else if (item.predicates().isEmpty()) { - return Optional.empty(); // Item definitions are correctly ordered - } - - // If all predicates of an existing entry also exist in a new entry, then the new entry is invalid - // This makes it required to order definitions correctly, so that "fallback predicates" are added last: - // - // A && B -> item1 - // A -> item2 - // - // Is the correct order, not - // - // A -> item2 - // A && B -> item1 - boolean existingHasAllPredicates = true; - for (CustomItemPredicate predicate : existing.getValue().predicates()) { - if (!item.predicates().contains(predicate)) { - existingHasAllPredicates = false; - break; + return Optional.of("Both entries don't have predicates, one must have a predicate"); + } + // If their predicates are equal then they also conflict + if (existing.getValue().predicates().size() == item.predicates().size()) { + boolean equal = true; + for (CustomItemPredicate predicate : existing.getValue().predicates()) { + if (!item.predicates().contains(predicate)) { + equal = false; + } + } + if (equal) { + return Optional.of("Both entries have the same predicates"); } } - if (existingHasAllPredicates) { - return Optional.of("Reorder your entries so that the one with the least amount of predicates is last"); - } return Optional.empty(); } 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 b3afc38ecbd..d792c2a9721 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 @@ -28,6 +28,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.SortedSetMultimap; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -40,6 +41,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -84,6 +86,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -454,10 +457,11 @@ public static void populate() { } // Add the custom item properties, if applicable - List> customItemDefinitions; + SortedSetMultimap> customItemDefinitions; Collection customItemsToLoad = customItems.get(javaItem.javaIdentifier()); if (customItemsAllowed && !customItemsToLoad.isEmpty()) { - customItemDefinitions = new ObjectArrayList<>(customItemsToLoad.size()); + customItemDefinitions = MultimapBuilder.hashKeys(customItemsToLoad.size()).treeSetValues(Comparator.comparingInt( + pair -> ((Pair) pair).first().predicates().size()).reversed()).build(); for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; @@ -484,13 +488,13 @@ public static void populate() { // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemDefinitions.add(Pair.of(customItem, customMapping.itemDefinition())); + customItemDefinitions.put(customItem.model(), Pair.of(customItem, customMapping.itemDefinition())); registry.put(customMapping.integerId(), customMapping.itemDefinition()); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); } } else { - customItemDefinitions = Collections.emptyList(); + customItemDefinitions = null; } mappingBuilder.customItemDefinitions(customItemDefinitions); @@ -519,7 +523,7 @@ public static void populate() { .bedrockDefinition(lightBlock) .bedrockData(0) .bedrockBlockDefinition(null) - .customItemDefinitions(Collections.emptyList()) + .customItemDefinitions(null) .build(); lightBlocks.put(lightBlock.getRuntimeId(), lightBlockEntry); } @@ -536,7 +540,7 @@ public static void populate() { .bedrockDefinition(lodestoneCompass) .bedrockData(0) .bedrockBlockDefinition(null) - .customItemDefinitions(Collections.emptyList()) + .customItemDefinitions(null) .build(); if (customItemsAllowed) { @@ -551,7 +555,7 @@ public static void populate() { .bedrockDefinition(definition) .bedrockData(0) .bedrockBlockDefinition(null) - .customItemDefinitions(Collections.emptyList()) // TODO check for custom items with furnace minecart + .customItemDefinitions(null) // TODO check for custom items with furnace minecart .build()); creativeItems.add(ItemData.builder() diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index fb1bc023dd1..f7f556c13ab 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -25,22 +25,20 @@ package org.geysermc.geyser.registry.type; +import com.google.common.collect.SortedSetMultimap; import it.unimi.dsi.fastutil.Pair; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.Value; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; -import java.util.Collections; -import java.util.List; - @Value @Builder @EqualsAndHashCode @@ -54,7 +52,7 @@ public class ItemMapping { null, null, null, - Collections.emptyList(), + null, Items.AIR ); @@ -73,8 +71,10 @@ public class ItemMapping { String translationString; - @NonNull - List> customItemDefinitions; + /** + * A map of item models and their custom item definitions, sorted from most predicates to least, which is important when matching predicates. + */ + SortedSetMultimap> customItemDefinitions; @NonNull Item javaItem; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 23e37366515..87ea8f5b866 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -25,14 +25,14 @@ package org.geysermc.geyser.translator.item; +import com.google.common.collect.Multimap; import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.ItemPredicateType; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateData; +import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.JavaDimension; @@ -48,6 +48,7 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.geysermc.geyser.registry.type.ItemMapping; +import java.util.Collection; import java.util.List; /** @@ -61,18 +62,22 @@ public static ItemDefinition getCustomItem(GeyserSession session, DataComponents return null; } - List> customItems = mapping.getCustomItemDefinitions(); - if (customItems.isEmpty()) { + Multimap> allCustomItems = mapping.getCustomItemDefinitions(); + if (allCustomItems == null) { return null; } Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); // TODO fallback onto default item model (when thats done by chris) + Collection> customItems = allCustomItems.get(itemModel); + if (customItems.isEmpty()) { + return null; + } // TODO check if definitions/predicates are in the correct order - for (Pair customModel : customItems) { // TODO Predicates + for (Pair customModel : customItems) { if (customModel.first().model().equals(itemModel)) { boolean allMatch = true; - for (CustomItemPredicate predicate : customModel.first().predicates()) { + for (CustomItemPredicate predicate : customModel.first().predicates()) { if (!predicateMatches(session, predicate, components)) { allMatch = false; break; @@ -86,19 +91,16 @@ public static ItemDefinition getCustomItem(GeyserSession session, DataComponents return null; } - private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, DataComponents components) { - if (predicate.type() == ItemPredicateType.CONDITION) { - ConditionPredicateData data = (ConditionPredicateData) predicate.data(); - return switch (data.property()) { + private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, DataComponents components) { + if (predicate instanceof ConditionPredicate condition) { + return switch (condition.property()) { case BROKEN -> nextDamageWillBreak(components); case DAMAGED -> isDamaged(components); case CUSTOM_MODEL_DATA -> false; // TODO 1.21.4 }; - } else if (predicate.type() == ItemPredicateType.MATCH) { - MatchPredicateData data = (MatchPredicateData) predicate.data(); - - if (data.property() == MatchPredicateProperty.CHARGE_TYPE) { - ChargeType expected = (ChargeType) data.data(); + } else if (predicate instanceof MatchPredicate match) { // TODO not much of a fun of the casts here, find a solution for the types? + if (match.property() == MatchPredicateProperty.CHARGE_TYPE) { + ChargeType expected = (ChargeType) match.data(); List charged = components.get(DataComponentType.CHARGED_PROJECTILES); if (charged == null) { return expected == ChargeType.NONE; @@ -111,19 +113,19 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica return false; } return true; - } else if (data.property() == MatchPredicateProperty.TRIM_MATERIAL) { - Key material = (Key) data.data(); + } else if (match.property() == MatchPredicateProperty.TRIM_MATERIAL) { + Key material = (Key) match.data(); ArmorTrim trim = components.get(DataComponentType.TRIM); if (trim == null || trim.material().isCustom()) { return false; } RegistryEntryData registered = session.getRegistryCache().trimMaterials().entryById(trim.material().id()); return registered != null && registered.key().equals(material); - } else if (data.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { - Key dimension = (Key) data.data(); + } else if (match.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { + Key dimension = (Key) match.data(); RegistryEntryData registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType()); return registered != null && dimension.equals(registered.key()); // TODO check if this works - } else if (data.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { + } else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { // TODO 1.21.4 return false; } From 52f2b9f1420c59649db0ea6c676c8e12299f896f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 2 Dec 2024 10:06:00 +0000 Subject: [PATCH 015/118] Add model grouping --- .../mappings/versions/MappingsReader_v2.java | 48 +++++++++++++++---- .../CustomItemRegistryPopulator_v2.java | 1 + 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index d361d382f85..a9011427372 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -64,11 +64,39 @@ public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer { if (entry.getValue().isArray()) { entry.getValue().forEach(data -> { - try { - CustomItemDefinition customItemDefinition = readItemMappingEntry(entry.getKey(), data); - consumer.accept(entry.getKey(), customItemDefinition); - } catch (InvalidCustomMappingsFileException e) { - GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); + // TODO better error handling + JsonNode type = data.get("type"); + if (type == null || !type.isTextual()) { + GeyserImpl.getInstance().getLogger().error("Error reading type in custom mappings file: " + file.toString()); + } else if (type.asText().equals("group")) { + JsonNode modelNode = data.get("model"); + if (modelNode == null || !modelNode.isTextual()) { + GeyserImpl.getInstance().getLogger().error("Error reading model in custom mappings file: " + file.toString()); + } else { + String model = modelNode.asText(); + JsonNode definitions = data.get("definitions"); + if (definitions == null || !definitions.isArray()) { + GeyserImpl.getInstance().getLogger().error("Error reading item definitions in custom mappings file: " + file.toString()); + } else { + definitions.forEach(definition -> { + try { + CustomItemDefinition customItemDefinition = readItemMappingEntry(model, definition); + consumer.accept(entry.getKey(), customItemDefinition); + } catch (InvalidCustomMappingsFileException e) { + GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); + } + }); + } + } + } else if (type.asText().equals("definition")) { + try { + CustomItemDefinition customItemDefinition = readItemMappingEntry(null, data); + consumer.accept(entry.getKey(), customItemDefinition); + } catch (InvalidCustomMappingsFileException e) { + GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); + } + } else { + GeyserImpl.getInstance().getLogger().error("Unknown type " + type.asText() + " in custom mappings file: " + file.toString()); } }); } @@ -88,12 +116,14 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod } JsonNode bedrockIdentifierNode = node.get("bedrock_identifier"); - JsonNode model = node.get("model"); + + JsonNode modelNode = node.get("model"); + String model = identifier != null || modelNode == null || !modelNode.isTextual() ? identifier : modelNode.asText(); if (bedrockIdentifierNode == null || !bedrockIdentifierNode.isTextual() || bedrockIdentifierNode.asText().isEmpty()) { throw new InvalidCustomMappingsFileException("An item entry has no bedrock identifier"); } - if (model == null || !model.isTextual() || model.asText().isEmpty()) { + if (model == null) { throw new InvalidCustomMappingsFileException("An item entry has no model"); } @@ -101,7 +131,7 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { bedrockIdentifier = Key.key(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.value()); } - CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, Key.key(model.asText())); + CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, Key.key(model)); if (node.has("display_name")) { builder.displayName(node.get("display_name").asText()); @@ -118,7 +148,7 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod try { DataComponentReaders.readDataComponent(components, Key.key(entry.getKey()), entry.getValue()); } catch (InvalidCustomMappingsFileException e) { - GeyserImpl.getInstance().getLogger().error("Error reading component " + entry.getKey() + " for item model " + model.textValue(), e); + GeyserImpl.getInstance().getLogger().error("Error reading component " + entry.getKey() + " for item model " + modelNode.textValue(), e); } }); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 8ee2cf32af9..94d1a5bbe3d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -81,6 +81,7 @@ public class CustomItemRegistryPopulator_v2 { public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { // TODO + // TODO better error handling? MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); // Load custom items from mappings files mappingsConfigReader.loadItemMappingsFromJson((id, item) -> { From 49f31c96615805a592fc938ba04ab17c702c743f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 10:17:48 +0000 Subject: [PATCH 016/118] Work on range dispatch predicate --- .../{data => }/ConditionPredicate.java | 5 +- .../v2/predicate/CustomItemPredicate.java | 2 +- ...cate.java => CustomModelDataProperty.java} | 4 +- .../{data/match => }/MatchPredicate.java | 4 +- .../v2/predicate/RangeDispatchPredicate.java | 37 +++++++++++++++ .../{data => }/match/ChargeType.java | 2 +- .../match/MatchPredicateProperty.java | 8 ++-- .../org/geysermc/geyser/item/type/Item.java | 2 +- .../geysermc/geyser/item/type/PotionItem.java | 2 +- .../geyser/item/type/ShulkerBoxItem.java | 2 +- .../mappings/versions/MappingsReader_v2.java | 14 +++--- .../translator/item/CustomItemTranslator.java | 47 +++++++++++++++---- .../translator/item/ItemTranslator.java | 8 ++-- .../player/input/BedrockBlockActions.java | 2 +- 14 files changed, 102 insertions(+), 37 deletions(-) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data => }/ConditionPredicate.java (86%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data/CustomModelDataPredicate.java => CustomModelDataProperty.java} (90%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data/match => }/MatchPredicate.java (90%) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data => }/match/ChargeType.java (94%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data => }/match/MatchPredicateProperty.java (88%) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java similarity index 86% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java index 7ea71e3a8de..8295777ead6 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java @@ -23,11 +23,8 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate.data; +package org.geysermc.geyser.api.item.custom.v2.predicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; - -// TODO maybe type should be a generic class with data, but this works for now public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate { // TODO maybe we can extend this diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java index eab23e4495b..abb61efbed0 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -25,5 +25,5 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; -public interface CustomItemPredicate { // TODO this probably needs to be different since people can implement this +public sealed interface CustomItemPredicate permits ConditionPredicate, MatchPredicate, RangeDispatchPredicate { // TODO maybe we need to move the predicate classes out of API } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java similarity index 90% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java index 04646cc50ac..799a0d91f30 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate.data; +package org.geysermc.geyser.api.item.custom.v2.predicate; -public record CustomModelDataPredicate(T data, int index) { +public record CustomModelDataProperty(T data, int index) { } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java similarity index 90% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java index cbecc88f780..eca29d2e4b6 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; +package org.geysermc.geyser.api.item.custom.v2.predicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; public record MatchPredicate(MatchPredicateProperty property, T data) implements CustomItemPredicate { } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java new file mode 100644 index 00000000000..1ecb4f618ad --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements CustomItemPredicate { + + // TODO check if we can change items while bedrock is using them, and if bedrock will continue to use them + public enum RangeDispatchProperty { + BUNDLE_FULLNESS, + DAMAGE, + COUNT, + CUSTOM_MODEL_DATA + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java similarity index 94% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java index 438cee772a4..950ee128a5d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; +package org.geysermc.geyser.api.item.custom.v2.predicate.match; public enum ChargeType { NONE, diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java similarity index 88% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java index a512ff3f73f..b9f0ee3dedd 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java @@ -23,10 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate.data.match; +package org.geysermc.geyser.api.item.custom.v2.predicate.match; import net.kyori.adventure.key.Key; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty; // TODO can we do more? public class MatchPredicateProperty { @@ -34,7 +34,9 @@ public class MatchPredicateProperty { public static final MatchPredicateProperty CHARGE_TYPE = create(); public static final MatchPredicateProperty TRIM_MATERIAL = create(); public static final MatchPredicateProperty CONTEXT_DIMENSION = create(); - public static final MatchPredicateProperty> CUSTOM_MODEL_DATA = create(); + public static final MatchPredicateProperty> CUSTOM_MODEL_DATA = create(); + + private MatchPredicateProperty() {} private static MatchPredicateProperty create() { return new MatchPredicateProperty<>(); diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index 881012b9891..d666f8818a6 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -125,7 +125,7 @@ public ItemData.Builder translateToBedrock(GeyserSession session, int count, Dat .damage(mapping.getBedrockData()) .count(count); - ItemTranslator.translateCustomItem(session, components, builder, mapping); + ItemTranslator.translateCustomItem(session, count, components, builder, mapping); return builder; } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java index 1cfbdd68d01..03508262bda 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java @@ -49,7 +49,7 @@ public ItemData.Builder translateToBedrock(GeyserSession session, int count, Dat if (components == null) return super.translateToBedrock(session, count, components, mapping, mappings); PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS); if (potionContents != null) { - ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, components, mapping); + ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, count, components, mapping); if (customItemDefinition == null) { Potion potion = Potion.getByJavaId(potionContents.getPotionId()); if (potion != null) { diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java index f57733e7166..2fca0fdca07 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java @@ -74,7 +74,7 @@ public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNul if (boxComponents != null) { // Check for custom items - ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, boxComponents, boxMapping); + ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, item.getAmount(), boxComponents, boxMapping); if (customItemDefinition != null) { bedrockIdentifier = customItemDefinition.getIdentifier(); bedrockData = 0; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index a9011427372..40a71188498 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -35,11 +35,11 @@ import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; +import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; @@ -270,9 +270,9 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo case "custom_model_data" -> { JsonNode index = node.get("index"); if (index == null || !index.isIntegralNumber()) { - throw new InvalidCustomMappingsFileException("Predicate missing index key"); + throw new InvalidCustomMappingsFileException("Predicate missing index key"); // TODO default to 0 } - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataPredicate<>(value.asText(), index.asInt()))); + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataProperty<>(value.asText(), index.asInt()))); } default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 87ea8f5b866..0e3d629c105 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -30,10 +30,11 @@ import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; +import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.session.GeyserSession; @@ -57,7 +58,7 @@ public final class CustomItemTranslator { @Nullable - public static ItemDefinition getCustomItem(GeyserSession session, DataComponents components, ItemMapping mapping) { + public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, DataComponents components, ItemMapping mapping) { if (components == null) { return null; } @@ -78,7 +79,7 @@ public static ItemDefinition getCustomItem(GeyserSession session, DataComponents if (customModel.first().model().equals(itemModel)) { boolean allMatch = true; for (CustomItemPredicate predicate : customModel.first().predicates()) { - if (!predicateMatches(session, predicate, components)) { + if (!predicateMatches(session, predicate, stackSize, components)) { allMatch = false; break; } @@ -91,7 +92,7 @@ public static ItemDefinition getCustomItem(GeyserSession session, DataComponents return null; } - private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, DataComponents components) { + private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, int stackSize, DataComponents components) { if (predicate instanceof ConditionPredicate condition) { return switch (condition.property()) { case BROKEN -> nextDamageWillBreak(components); @@ -124,18 +125,46 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica } else if (match.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { Key dimension = (Key) match.data(); RegistryEntryData registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType()); - return registered != null && dimension.equals(registered.key()); // TODO check if this works + return registered != null && dimension.equals(registered.key()); } else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { // TODO 1.21.4 return false; } + } else if (predicate instanceof RangeDispatchPredicate rangeDispatch) { + double propertyValue = switch (rangeDispatch.property()) { + case BUNDLE_FULLNESS -> { + List stacks = components.get(DataComponentType.BUNDLE_CONTENTS); + if (stacks == null) { + yield 0; + } + int bundleWeight = 0; + for (ItemStack stack : stacks) { + bundleWeight += stack.getAmount(); + } + yield bundleWeight; + } + case DAMAGE -> tryNormalize(rangeDispatch, components.get(DataComponentType.DAMAGE), components.get(DataComponentType.MAX_DAMAGE)); + case COUNT -> tryNormalize(rangeDispatch, stackSize, components.get(DataComponentType.MAX_STACK_SIZE)); + case CUSTOM_MODEL_DATA -> 0.0; // TODO 1.21.4 + } * rangeDispatch.scale(); + return propertyValue >= rangeDispatch.threshold(); } throw new IllegalStateException("Unimplemented predicate type"); } - /* These three functions are based off their Mojmap equivalents from 1.21.3 */ + private static double tryNormalize(RangeDispatchPredicate predicate, @Nullable Integer value, @Nullable Integer max) { + if (value == null) { + return 0.0; + } else if (max == null) { + return value; + } else if (!predicate.normalizeIfPossible()) { + return Math.min(value, max); + } + return Math.max(0.0, Math.min(1.0, (double) value / max)); + } + /* These three functions are based off their Mojmap equivalents from 1.21.3 */ private static boolean nextDamageWillBreak(DataComponents components) { return isDamageableItem(components) && components.getOrDefault(DataComponentType.DAMAGE, 0) >= components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) - 1; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 5951eea2f3b..a927fedf396 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -215,7 +215,7 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack translatePlayerHead(session, components, builder); } - translateCustomItem(session, components, builder, bedrockItem); + translateCustomItem(session, count, components, builder, bedrockItem); if (components != null) { // Translate the canDestroy and canPlaceOn Java components @@ -428,7 +428,7 @@ public static ItemDefinition getBedrockItemDefinition(GeyserSession session, @No } } - ItemDefinition definition = CustomItemTranslator.getCustomItem(session, itemStack.getComponents(), mapping); + ItemDefinition definition = CustomItemTranslator.getCustomItem(session, itemStack.getAmount(), itemStack.getComponents(), mapping); if (definition == null) { // No custom item return itemDefinition; @@ -469,8 +469,8 @@ public static String getCustomName(GeyserSession session, DataComponents compone /** * Translates the custom model data of an item */ - public static void translateCustomItem(GeyserSession session, DataComponents components, ItemData.Builder builder, ItemMapping mapping) { - ItemDefinition definition = CustomItemTranslator.getCustomItem(session, components, mapping); + public static void translateCustomItem(GeyserSession session, int stackSize, DataComponents components, ItemData.Builder builder, ItemMapping mapping) { + ItemDefinition definition = CustomItemTranslator.getCustomItem(session, stackSize, components, mapping); if (definition != null) { builder.definition(definition); builder.blockDefinition(null); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java index 2d593dc0948..e35679f80d2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java @@ -93,7 +93,7 @@ private static void handle(GeyserSession session, PlayerBlockActionData blockAct // If the block is custom or the breaking item is custom, we must keep track of break time ourselves GeyserItemStack item = session.getPlayerInventory().getItemInHand(); ItemMapping mapping = item.getMapping(session); - ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(session, item.getComponents(), mapping) : null; + ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(session, item.getAmount(), item.getComponents(), mapping) : null; CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(blockState); SkullCache.Skull skull = session.getSkullCache().getSkulls().get(vector); From 9139af2782cb296771ae88d761df7e48b0a27f4f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 10:26:32 +0000 Subject: [PATCH 017/118] Implement range dispatch predicates when converting old mappings to new one's --- .../api/item/custom/CustomItemData.java | 21 ++++++++++++++----- .../v2/predicate/ConditionPredicate.java | 8 +++++++ .../v2/predicate/RangeDispatchPredicate.java | 12 +++++++++++ .../mappings/versions/MappingsReader_v1.java | 2 +- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 0d4c704e875..11b46720a19 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -32,7 +32,9 @@ import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import java.util.List; import java.util.OptionalInt; import java.util.Set; @@ -125,9 +127,9 @@ static CustomItemData.Builder builder() { return GeyserApi.api().provider(CustomItemData.Builder.class); } - default CustomItemDefinition toDefinition(String javaItem) { - // TODO predicate - return CustomItemDefinition.builder(Key.key(javaItem), Key.key(javaItem)) + default CustomItemDefinition.Builder toDefinition(String javaItem) { + // TODO non vanilla, unbreakable predicate? + CustomItemDefinition.Builder definition = CustomItemDefinition.builder(Key.key(javaItem), Key.key(javaItem)) .displayName(displayName()) .bedrockOptions(CustomItemBedrockOptions.builder() .icon(icon()) @@ -138,8 +140,17 @@ default CustomItemDefinition toDefinition(String javaItem) { .textureSize(textureSize()) .renderOffsets(renderOffsets()) .tags(tags()) - ) - .build(); + ); + + CustomItemOptions options = customItemOptions(); + if (options.customModelData().isPresent()) { + definition.predicate(new RangeDispatchPredicate(RangeDispatchPredicate.RangeDispatchProperty.CUSTOM_MODEL_DATA, + options.customModelData().getAsInt(), 1.0, false, 0)); + } + if (options.damagePredicate().isPresent()) { + definition.predicate(new RangeDispatchPredicate(RangeDispatchPredicate.RangeDispatchProperty.DAMAGE, options.damagePredicate().getAsInt())); + } + return definition; } interface Builder { diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java index 8295777ead6..9099356157d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java @@ -27,6 +27,14 @@ public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate { + public ConditionPredicate(ConditionProperty property, boolean expected) { + this(property, expected, 0); + } + + public ConditionPredicate(ConditionProperty property) { + this(property, true); + } + // TODO maybe we can extend this public enum ConditionProperty { BROKEN, diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java index 1ecb4f618ad..575190ac78b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java @@ -27,6 +27,18 @@ public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements CustomItemPredicate { + public RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { + this(property, threshold, scale, normalizeIfPossible, 0); + } + + public RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale) { + this(property, threshold, scale, false); + } + + public RangeDispatchPredicate(RangeDispatchProperty property, double threshold) { + this(property, threshold, 1.0); + } + // TODO check if we can change items while bedrock is using them, and if bedrock will continue to use them public enum RangeDispatchProperty { BUNDLE_FULLNESS, 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 d09f204ccc6..b78dc40327f 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 @@ -215,7 +215,7 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod customItemData.tags(tagsSet); } - return customItemData.build().toDefinition(identifier); + return customItemData.build().toDefinition(identifier).build(); } /** From 4d09104ceb3b141f1519c333ab16a0c67298c2fa Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 10:36:37 +0000 Subject: [PATCH 018/118] Implement reading range dispatch predicates from mappings --- .../api/item/custom/CustomItemData.java | 1 - .../mappings/versions/MappingsReader_v2.java | 95 +++++++++++++------ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 11b46720a19..c5d96098438 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -34,7 +34,6 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; -import java.util.List; import java.util.OptionalInt; import java.util.Set; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 40a71188498..766a64b396c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -37,6 +37,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; @@ -239,43 +240,75 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo } String property = propertyNode.asText(); - if (type.equals("condition")) { - try { - ConditionPredicate.ConditionProperty conditionProperty = ConditionPredicate.ConditionProperty.valueOf(property.toUpperCase()); - JsonNode expected = node.get("expected"); - JsonNode index = node.get("index"); + // TODO helper methods to lessen code duplication + switch (type) { + case "condition" -> { + try { + ConditionPredicate.ConditionProperty conditionProperty = ConditionPredicate.ConditionProperty.valueOf(property.toUpperCase()); + JsonNode expected = node.get("expected"); + JsonNode index = node.get("index"); - builder.predicate(new ConditionPredicate(conditionProperty, - expected == null || expected.asBoolean(), index == null || !index.isIntegralNumber() ? 0 : index.asInt())); - } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown property " + property); - } - } else if (type.equals("match")) { - JsonNode value = node.get("value"); - if (value == null || !value.isTextual()) { - throw new InvalidCustomMappingsFileException("Predicate missing value key"); + builder.predicate(new ConditionPredicate(conditionProperty, + expected == null || expected.asBoolean(), index == null || !index.isIntegralNumber() ? 0 : index.asInt())); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("Unknown property " + property); + } } - - switch (property) { - case "charge_type" -> { - try { - ChargeType chargeType = ChargeType.valueOf(value.asText().toUpperCase()); - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CHARGE_TYPE, chargeType)); - } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown charge type " + value.asText()); - } + case "match" -> { + JsonNode valueNode = node.get("value"); + if (valueNode == null || !valueNode.isTextual()) { + throw new InvalidCustomMappingsFileException("Predicate missing value key"); } - case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, Key.key(value.asText()))); // TODO - case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, Key.key(value.asText()))); // TODO - case "custom_model_data" -> { - JsonNode index = node.get("index"); - if (index == null || !index.isIntegralNumber()) { - throw new InvalidCustomMappingsFileException("Predicate missing index key"); // TODO default to 0 + String value = valueNode.asText(); + + switch (property) { + case "charge_type" -> { + try { + ChargeType chargeType = ChargeType.valueOf(value.toUpperCase()); + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CHARGE_TYPE, chargeType)); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("Unknown charge type " + value); + } } - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataProperty<>(value.asText(), index.asInt()))); + case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, Key.key(value))); // TODO + case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, Key.key(value))); // TODO + case "custom_model_data" -> { + JsonNode indexNode = node.get("index"); + int index = 0; + if (indexNode != null && indexNode.isIntegralNumber()) { + index = indexNode.asInt(); + } + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataProperty<>(value, index))); + } + default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); + } + } + case "range_dispatch" -> { + JsonNode threshold = node.get("threshold"); + if (threshold == null || !threshold.isNumber()) { + throw new InvalidCustomMappingsFileException("Predicate missing threshold key"); + } + JsonNode scaleNode = node.get("scale"); + double scale = 1.0; + if (scaleNode != null && scaleNode.isNumber()) { + scale = scaleNode.asDouble(); + } + JsonNode normalizeNode = node.get("normalize"); + boolean normalizeIfPossible = normalizeNode != null && normalizeNode.booleanValue(); + JsonNode indexNode = node.get("index"); + int index = 0; + if (indexNode != null && indexNode.isIntegralNumber()) { + index = indexNode.asInt(); + } + + try { + RangeDispatchPredicate.RangeDispatchProperty rangeDispatchProperty = RangeDispatchPredicate.RangeDispatchProperty.valueOf(property.toUpperCase()); + builder.predicate(new RangeDispatchPredicate(rangeDispatchProperty, threshold.asDouble(), scale, normalizeIfPossible, index)); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("Unknown property " + property); } - default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } + default -> throw new InvalidCustomMappingsFileException("Unknown predicate type " + type); } } From 0346a80e4bc8ae2af2102908748aac89393f6348 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 11:02:10 +0000 Subject: [PATCH 019/118] Implement sorting for range dispatch predicates, needs testing --- .../populator/ItemRegistryPopulator.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) 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 d792c2a9721..6d04cb1b03e 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 @@ -65,6 +65,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; @@ -91,6 +93,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -460,8 +463,7 @@ public static void populate() { SortedSetMultimap> customItemDefinitions; Collection customItemsToLoad = customItems.get(javaItem.javaIdentifier()); if (customItemsAllowed && !customItemsToLoad.isEmpty()) { - customItemDefinitions = MultimapBuilder.hashKeys(customItemsToLoad.size()).treeSetValues(Comparator.comparingInt( - pair -> ((Pair) pair).first().predicates().size()).reversed()).build(); + customItemDefinitions = MultimapBuilder.hashKeys(customItemsToLoad.size()).treeSetValues(new CustomItemDefinitionComparator()).build(); for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; @@ -710,4 +712,34 @@ private static void registerFurnaceMinecart(int nextFreeBedrockId, ListFirst by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first, + * and then by the amount of predicates, from most to least.

+ */ + private static class CustomItemDefinitionComparator implements Comparator> { + + @Override + public int compare(Pair firstPair, Pair secondPair) { + CustomItemDefinition first = firstPair.first(); + CustomItemDefinition second = secondPair.first(); + if (first.equals(second)) { + return 0; + } + for (CustomItemPredicate predicate : first.predicates()) { + if (predicate instanceof RangeDispatchPredicate rangeDispatch) { + Optional other = second.predicates().stream() + .filter(otherPredicate -> otherPredicate instanceof RangeDispatchPredicate otherDispatch && otherDispatch.property() == rangeDispatch.property()) + .map(otherPredicate -> (RangeDispatchPredicate) otherPredicate) + .findFirst(); + if (other.isPresent()) { + return (int) (rangeDispatch.threshold() - other.orElseThrow().threshold()); + } + } + } + return first.predicates().size() - second.predicates().size(); + } + } } From c38bf262985095509e71ac9330bcc82d3d119373 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 11:09:33 +0000 Subject: [PATCH 020/118] Add model check to custom item comparator --- .../geyser/registry/populator/ItemRegistryPopulator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 6d04cb1b03e..c3609cf5941 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 @@ -718,6 +718,9 @@ private static void registerFurnaceMinecart(int nextFreeBedrockId, ListFirst by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first, * and then by the amount of predicates, from most to least.

+ * + *

This comparator regards 2 custom item definitions as the same if their model differs, since it is only checking for predicates, and those + * don't matter if their models are different.

*/ private static class CustomItemDefinitionComparator implements Comparator> { @@ -725,7 +728,7 @@ private static class CustomItemDefinitionComparator implements Comparator firstPair, Pair secondPair) { CustomItemDefinition first = firstPair.first(); CustomItemDefinition second = secondPair.first(); - if (first.equals(second)) { + if (first.equals(second) || !first.model().equals(second.model())) { return 0; } for (CustomItemPredicate predicate : first.predicates()) { From 79bf4afb876ceaceba515b28472fd707a6210ff9 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 11:34:46 +0000 Subject: [PATCH 021/118] Clean up predicates a bit --- .../CustomModelDataString.java} | 4 ++-- .../custom/v2/predicate/match/MatchPredicateProperty.java | 3 +-- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{CustomModelDataProperty.java => match/CustomModelDataString.java} (90%) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/CustomModelDataString.java similarity index 90% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/CustomModelDataString.java index 799a0d91f30..c4020105893 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/CustomModelDataString.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate; +package org.geysermc.geyser.api.item.custom.v2.predicate.match; -public record CustomModelDataProperty(T data, int index) { +public record CustomModelDataString(String value, int index) { } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java index b9f0ee3dedd..cd1348c5534 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.api.item.custom.v2.predicate.match; import net.kyori.adventure.key.Key; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty; // TODO can we do more? public class MatchPredicateProperty { @@ -34,7 +33,7 @@ public class MatchPredicateProperty { public static final MatchPredicateProperty CHARGE_TYPE = create(); public static final MatchPredicateProperty TRIM_MATERIAL = create(); public static final MatchPredicateProperty CONTEXT_DIMENSION = create(); - public static final MatchPredicateProperty> CUSTOM_MODEL_DATA = create(); + public static final MatchPredicateProperty CUSTOM_MODEL_DATA = create(); private MatchPredicateProperty() {} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 766a64b396c..fd4399b06db 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -36,7 +36,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; @@ -278,7 +278,7 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo if (indexNode != null && indexNode.isIntegralNumber()) { index = indexNode.asInt(); } - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataProperty<>(value, index))); + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataString(value, index))); } default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } From fd09a05aaf3780ea3f14f0a302b676907f17399a Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 6 Dec 2024 13:02:00 +0000 Subject: [PATCH 022/118] Update custom item translator for 1.21.4, implement custom model data --- .../CustomItemRegistryPopulator_v2.java | 71 +------ .../translator/item/CustomItemTranslator.java | 196 ++++++++++++------ core/src/main/resources/mappings | 2 +- 3 files changed, 142 insertions(+), 127 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 94d1a5bbe3d..4e875407331 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -42,23 +42,18 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.item.GeyserCustomMappingData; -import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.WearableSlot; -import org.geysermc.geyser.item.type.ArmorItem; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.ConsumeEffect; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; -import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -181,7 +176,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD boolean canDestroyInCreative = true; if (vanillaMapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.attackDamage()); + canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage()); } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); @@ -498,60 +493,9 @@ private static NbtMap xyzToScaleList(float x, float y, float z) { return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); } - // TODO this needs to be a simpler method once we just load default vanilla components from mappings or something + // TODO is this right? private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { - DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? - - components.put(DataComponentType.MAX_STACK_SIZE, javaItem.maxStackSize()); - components.put(DataComponentType.MAX_DAMAGE, javaItem.maxDamage()); - - Consumable consumable = getItemConsumable(javaItem); - if (consumable != null) { - components.put(DataComponentType.CONSUMABLE, consumable); - } - - if (canAlwaysEat(javaItem)) { - components.put(DataComponentType.FOOD, new FoodProperties(0, 0, true)); - } - - if (javaItem.glint()) { - components.put(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE, true); - } - - if (javaItem instanceof ArmorItem armor) { // TODO equippable - } - - components.put(DataComponentType.RARITY, javaItem.rarity().ordinal()); - - components.getDataComponents().putAll(definition.components().getDataComponents()); - return components; - } - - private static Consumable getItemConsumable(Item item) { - if (item == Items.APPLE || item == Items.BAKED_POTATO || item == Items.BEETROOT || item == Items.BEETROOT_SOUP || item == Items.BREAD - || item == Items.CARROT || item == Items.CHORUS_FRUIT || item == Items.COOKED_CHICKEN || item == Items.COOKED_COD - || item == Items.COOKED_MUTTON || item == Items.COOKED_PORKCHOP || item == Items.COOKED_RABBIT || item == Items.COOKED_SALMON - || item == Items.COOKIE || item == Items.ENCHANTED_GOLDEN_APPLE || item == Items.GOLDEN_APPLE || item == Items.GLOW_BERRIES - || item == Items.GOLDEN_CARROT || item == Items.MELON_SLICE || item == Items.MUSHROOM_STEW || item == Items.POISONOUS_POTATO - || item == Items.POTATO || item == Items.PUFFERFISH || item == Items.PUMPKIN_PIE || item == Items.RABBIT_STEW - || item == Items.BEEF || item == Items.CHICKEN || item == Items.COD || item == Items.MUTTON || item == Items.PORKCHOP - || item == Items.RABBIT || item == Items.ROTTEN_FLESH || item == Items.SPIDER_EYE || item == Items.COOKED_BEEF - || item == Items.SUSPICIOUS_STEW || item == Items.SWEET_BERRIES || item == Items.TROPICAL_FISH) { - return Consumables.DEFAULT_FOOD; - } else if (item == Items.POTION) { - return Consumables.DEFAULT_DRINK; - } else if (item == Items.HONEY_BOTTLE) { - return Consumables.HONEY_BOTTLE; - } else if (item == Items.OMINOUS_BOTTLE) { - return Consumables.OMINOUS_BOTTLE; - } else if (item == Items.DRIED_KELP) { - return Consumables.DRIED_KELP; - } - return null; - } - - private static boolean canAlwaysEat(Item item) { - return item == Items.CHORUS_FRUIT || item == Items.ENCHANTED_GOLDEN_APPLE || item == Items.GOLDEN_APPLE || item == Items.HONEY_BOTTLE || item == Items.SUSPICIOUS_STEW; + return javaItem.gatherComponents(definition.components()); } @SuppressWarnings("unchecked") @@ -568,13 +512,4 @@ private static void addItemTag(NbtMapBuilder builder, String tag) { } } } - - private static final class Consumables { - private static final Consumable DEFAULT_FOOD = new Consumable(1.6F, Consumable.ItemUseAnimation.EAT, BuiltinSound.ENTITY_GENERIC_EAT, true, List.of()); - private static final Consumable DEFAULT_DRINK = new Consumable(1.6F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ENTITY_GENERIC_DRINK, false, List.of()); - private static final Consumable HONEY_BOTTLE = new Consumable(2.0F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ITEM_HONEY_BOTTLE_DRINK, false, List.of()); - private static final Consumable OMINOUS_BOTTLE = new Consumable(2.0F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ITEM_HONEY_BOTTLE_DRINK, - false, List.of(new ConsumeEffect.PlaySound(BuiltinSound.ITEM_OMINOUS_BOTTLE_DISPOSE))); - private static final Consumable DRIED_KELP = new Consumable(0.8F, Consumable.ItemUseAnimation.EAT, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); - } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index fdc90c21560..539044c87ec 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -25,103 +25,183 @@ package org.geysermc.geyser.translator.item; +import com.google.common.collect.Multimap; +import lombok.extern.slf4j.Slf4j; +import net.kyori.adventure.key.Key; +import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; +import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.level.JavaDimension; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.registry.RegistryEntryData; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ArmorTrim; import org.geysermc.mcprotocollib.protocol.data.game.item.component.CustomModelData; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import it.unimi.dsi.fastutil.Pair; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.geysermc.geyser.api.item.custom.CustomItemOptions; -import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.registry.type.ItemMapping; +import java.util.Collection; import java.util.List; -import java.util.OptionalInt; +import java.util.function.Function; /** * This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator. */ +@Slf4j public final class CustomItemTranslator { @Nullable - public static ItemDefinition getCustomItem(DataComponents components, ItemMapping mapping) { + public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, DataComponents components, ItemMapping mapping) { if (components == null) { return null; } - List> customMappings = mapping.getCustomItemOptions(); - if (customMappings.isEmpty()) { + + Multimap> allCustomItems = mapping.getCustomItemDefinitions(); + if (allCustomItems == null) { return null; } - // TODO 1.21.4 - float customModelDataInt = 0; - CustomModelData customModelData = components.get(DataComponentType.CUSTOM_MODEL_DATA); - if (customModelData != null) { - if (!customModelData.floats().isEmpty()) { - customModelDataInt = customModelData.floats().get(0); - } + Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); + System.out.println(itemModel + " is the model!"); + Collection> customItems = allCustomItems.get(itemModel); + if (customItems.isEmpty()) { + return null; } - boolean checkDamage = mapping.getJavaItem().defaultMaxDamage() > 0; - int damage = !checkDamage ? 0 : components.getOrDefault(DataComponentType.DAMAGE, 0); - boolean unbreakable = checkDamage && !isDamaged(components, damage); - - for (Pair mappingTypes : customMappings) { - CustomItemOptions options = mappingTypes.key(); - - // Code note: there may be two or more conditions that a custom item must follow, hence the "continues" - // here with the return at the end. - - // Implementation details: Java's predicate system works exclusively on comparing float numbers. - // A value doesn't necessarily have to match 100%; it just has to be the first to meet all predicate conditions. - // This is also why the order of iteration is important as the first to match will be the chosen display item. - // For example, if CustomModelData is set to 2f as the requirement, then the NBT can be any number greater or equal (2, 3, 4...) - // The same behavior exists for Damage (in fraction form instead of whole numbers), - // and Damaged/Unbreakable handles no damage as 0f and damaged as 1f. - - if (checkDamage) { - if (unbreakable && options.unbreakable() == TriState.FALSE) { - continue; + for (Pair customModel : customItems) { + if (customModel.first().model().equals(itemModel)) { + boolean allMatch = true; + for (CustomItemPredicate predicate : customModel.first().predicates()) { + if (!predicateMatches(session, predicate, stackSize, components)) { + allMatch = false; + break; + } } + if (allMatch) { + return customModel.second(); + } + } + } + return null; + } - OptionalInt damagePredicate = options.damagePredicate(); - if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { - continue; + private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, int stackSize, DataComponents components) { + if (predicate instanceof ConditionPredicate condition) { + return switch (condition.property()) { + case BROKEN -> nextDamageWillBreak(components); + case DAMAGED -> isDamaged(components); + case CUSTOM_MODEL_DATA -> getCustomBoolean(components, condition.index()); + } == condition.expected(); + } else if (predicate instanceof MatchPredicate match) { // TODO not much of a fan of the casts here, find a solution for the types? + if (match.property() == MatchPredicateProperty.CHARGE_TYPE) { + ChargeType expected = (ChargeType) match.data(); + List charged = components.get(DataComponentType.CHARGED_PROJECTILES); + if (charged == null) { + return expected == ChargeType.NONE; + } else if (expected == ChargeType.ROCKET) { + for (ItemStack projectile : charged) { + if (projectile.getId() == Items.FIREWORK_ROCKET.javaId()) { + return true; + } + } + return false; } - } else { - if (options.unbreakable() != TriState.NOT_SET || options.damagePredicate().isPresent()) { - // These will never match on this item. 1.19.2 behavior - // Maybe move this to CustomItemRegistryPopulator since it'll be the same for every item? If so, add a test. - continue; + return true; + } else if (match.property() == MatchPredicateProperty.TRIM_MATERIAL) { + Key material = (Key) match.data(); + ArmorTrim trim = components.get(DataComponentType.TRIM); + if (trim == null || trim.material().isCustom()) { + return false; } + RegistryEntryData registered = session.getRegistryCache().trimMaterials().entryById(trim.material().id()); + return registered != null && registered.key().equals(material); + } else if (match.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { + Key dimension = (Key) match.data(); + RegistryEntryData registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType()); + return registered != null && dimension.equals(registered.key()); + } else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { + CustomModelDataString expected = (CustomModelDataString) match.data(); + return expected.value().equals(getSafeCustomModelData(components, CustomModelData::strings, expected.index())); } + } else if (predicate instanceof RangeDispatchPredicate rangeDispatch) { + double propertyValue = switch (rangeDispatch.property()) { + case BUNDLE_FULLNESS -> { + List stacks = components.get(DataComponentType.BUNDLE_CONTENTS); + if (stacks == null) { + yield 0; + } + int bundleWeight = 0; + for (ItemStack stack : stacks) { + bundleWeight += stack.getAmount(); + } + yield bundleWeight; + } + case DAMAGE -> tryNormalize(rangeDispatch, components.get(DataComponentType.DAMAGE), components.get(DataComponentType.MAX_DAMAGE)); + case COUNT -> tryNormalize(rangeDispatch, stackSize, components.get(DataComponentType.MAX_STACK_SIZE)); + case CUSTOM_MODEL_DATA -> getCustomFloat(components, rangeDispatch.index()); + } * rangeDispatch.scale(); + return propertyValue >= rangeDispatch.threshold(); + } - OptionalInt customModelDataOption = options.customModelData(); - if (customModelDataOption.isPresent() && customModelDataInt < customModelDataOption.getAsInt()) { - continue; - } + throw new IllegalStateException("Unimplemented predicate type"); + } - if (options.defaultItem()) { - return null; - } + private static boolean getCustomBoolean(DataComponents components, int index) { + Boolean b = getSafeCustomModelData(components, CustomModelData::flags, index); + return b != null && b; + } - return mappingTypes.value(); - } + private static float getCustomFloat(DataComponents components, int index) { + Float f = getSafeCustomModelData(components, CustomModelData::floats, index); + return f == null ? 0.0F : f; + } + private static T getSafeCustomModelData(DataComponents components, Function> listGetter, int index) { + CustomModelData modelData = components.get(DataComponentType.CUSTOM_MODEL_DATA); + if (modelData == null || index < 0) { + return null; + } + List list = listGetter.apply(modelData); + if (index < list.size()) { + return list.get(index); + } return null; } - /* These two functions are based off their Mojmap equivalents from 1.19.2 */ + private static double tryNormalize(RangeDispatchPredicate predicate, @Nullable Integer value, @Nullable Integer max) { + if (value == null) { + return 0.0; + } else if (max == null) { + return value; + } else if (!predicate.normalizeIfPossible()) { + return Math.min(value, max); + } + return Math.max(0.0, Math.min(1.0, (double) value / max)); + } + + /* These three functions are based off their Mojmap equivalents from 1.21.3 */ + private static boolean nextDamageWillBreak(DataComponents components) { + return isDamageableItem(components) && components.getOrDefault(DataComponentType.DAMAGE, 0) >= components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) - 1; + } - private static boolean isDamaged(DataComponents components, int damage) { - return isDamagableItem(components) && damage > 0; + private static boolean isDamaged(DataComponents components) { + return isDamageableItem(components) && components.getOrDefault(DataComponentType.DAMAGE, 0) > 0; } - private static boolean isDamagableItem(DataComponents components) { - // mapping.getMaxDamage > 0 should also be checked (return false if not true) but we already check prior to this function - Boolean unbreakable = components.get(DataComponentType.UNBREAKABLE); - // Tag must either not be present or be set to false - return unbreakable == null || !unbreakable; + private static boolean isDamageableItem(DataComponents components) { + return components.getOrDefault(DataComponentType.UNBREAKABLE, false) && components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) > 0; } private CustomItemTranslator() { diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 8707dd144b2..452312f8831 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 8707dd144b20632f4a2f4b5497d8e5fb211e6c93 +Subproject commit 452312f88317cce019b8f336f485ffa7b2c19557 From 7fabf0c28d2a9808f4db47d631b3f0e9ecc5b308 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Wed, 11 Dec 2024 19:20:42 +0000 Subject: [PATCH 023/118] Implement/update register items event for new custom item definitions --- .../GeyserDefineCustomItemsEvent.java | 34 +++++++++++++++-- .../item/custom/v2/CustomItemDefinition.java | 4 +- .../GeyserDefineCustomItemsEventImpl.java | 38 +++---------------- .../CustomItemRegistryPopulator.java | 32 ---------------- .../CustomItemRegistryPopulator_v2.java | 27 +++++++++++++ 5 files changed, 64 insertions(+), 71 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index bd14aaf43c7..cacfa8eecde 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -29,8 +29,10 @@ import org.geysermc.event.Event; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -40,13 +42,27 @@ * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config. */ public interface GeyserDefineCustomItemsEvent extends Event { + /** * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. + * This will always return an empty map since the switch to custom item definitions, use {@link GeyserDefineCustomItemsEvent#getExistingCustomItemDefinitions()}. * + * @deprecated use {@link GeyserDefineCustomItemsEvent#getExistingCustomItemDefinitions()} * @return a multimap of all the already registered custom items */ + @Deprecated(forRemoval = true) + @NonNull + default Map> getExistingCustomItems() { + return Collections.emptyMap(); + } + + /** + * Gets a multimap of all the already registered custom item definitions indexed by the item's extended java item's identifier. + * + * @return a multimap of all the already registered custom item definitions + */ @NonNull - Map> getExistingCustomItems(); + Map> getExistingCustomItemDefinitions(); /** * Gets the list of the already registered non-vanilla custom items. @@ -58,14 +74,26 @@ public interface GeyserDefineCustomItemsEvent extends Event { /** * Registers a custom item with a base Java item. This is used to register items with custom textures and properties - * based on NBT data. + * based on NBT data. This method should not be used anymore, {@link CustomItemDefinition}s are preferred now and this method will convert {@code CustomItemData} to {@code CustomItemDefinition} internally. * + * @deprecated use {@link GeyserDefineCustomItemsEvent#register(String, CustomItemDefinition)} * @param identifier the base (java) item * @param customItemData the custom item data to register * @return if the item was registered */ + @Deprecated boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); + /** + * Registers a custom item with a base Java item. This is used to register items with custom textures and properties + * based on NBT data. + * + * @param identifier the base (java) item + * @param customItemDefinition the custom item definition to register + * @return if the item was registered + */ + boolean register(@NonNull String identifier, @NonNull CustomItemDefinition customItemDefinition); + /** * Registers a custom item with no base item. This is used for mods. * @@ -73,4 +101,4 @@ public interface GeyserDefineCustomItemsEvent extends Event { * @return if the item was registered */ boolean register(@NonNull NonVanillaCustomItemData customItemData); -} \ No newline at end of file +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 5e8aa63c048..5a644fda92e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -41,9 +41,7 @@ public interface CustomItemDefinition { /** * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. If the {@code minecraft} namespace is given in the builder, the default - * namespace of the implementation is used. - * - * @implNote for Geyser, the default namespace is the {@code geyser_custom} namespace. + * namespace of the implementation is used. For Geyser, the default namespace is the {@code geyser_custom} namespace. */ @NonNull Key bedrockIdentifier(); diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java index b9a059f19ac..d7492c7612f 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java @@ -28,8 +28,8 @@ import com.google.common.collect.Multimap; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent; -import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import java.util.Collection; import java.util.Collections; @@ -37,49 +37,21 @@ import java.util.Map; public abstract class GeyserDefineCustomItemsEventImpl implements GeyserDefineCustomItemsEvent { - private final Multimap customItems; + private final Multimap customItems; private final List nonVanillaCustomItems; - public GeyserDefineCustomItemsEventImpl(Multimap customItems, List nonVanillaCustomItems) { + public GeyserDefineCustomItemsEventImpl(Multimap customItems, List nonVanillaCustomItems) { this.customItems = customItems; this.nonVanillaCustomItems = nonVanillaCustomItems; } - /** - * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. - * - * @return a multimap of all the already registered custom items - */ @Override - public @NonNull Map> getExistingCustomItems() { - return Collections.unmodifiableMap(this.customItems.asMap()); + public @NonNull Map> getExistingCustomItemDefinitions() { + return Collections.unmodifiableMap(customItems.asMap()); } - /** - * Gets the list of the already registered non-vanilla custom items. - * - * @return the list of the already registered non-vanilla custom items - */ @Override public @NonNull List getExistingNonVanillaCustomItems() { return Collections.unmodifiableList(this.nonVanillaCustomItems); } - - /** - * Registers a custom item with a base Java item. This is used to register items with custom textures and properties - * based on NBT data. - * - * @param identifier the base (java) item - * @param customItemData the custom item data to register - * @return if the item was registered - */ - public abstract boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData); - - /** - * Registers a custom item with no base item. This is used for mods. - * - * @param customItemData the custom item data to register - * @return if the item was registered - */ - public abstract boolean register(@NonNull NonVanillaCustomItemData customItemData); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index c4bac70c5ff..4ae5b407c83 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.registry.populator; import com.google.common.collect.Multimap; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -39,7 +38,6 @@ import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.util.TriState; -import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.WearableSlot; @@ -52,7 +50,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -69,35 +66,6 @@ public static void populate(Map items, Multimap 0) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java index 4e875407331..9867474c231 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java @@ -27,6 +27,7 @@ import com.google.common.collect.Multimap; import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -35,12 +36,14 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.WearableSlot; import org.geysermc.geyser.item.type.Item; @@ -85,6 +88,30 @@ public static void populate(Map items, Multimap 0) { GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items"); From 04d7f486457fcdf29055c22a4b390e830c9e3ebb Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 10:41:33 +0000 Subject: [PATCH 024/118] More stuff in the custom item registry populator --- .../org/geysermc/geyser/item/type/Item.java | 4 - .../CustomItemRegistryPopulator.java | 506 ++++++++------- .../CustomItemRegistryPopulator_v1.java | 591 ++++++++++++++++++ .../CustomItemRegistryPopulator_v2.java | 542 ---------------- .../populator/ItemRegistryPopulator.java | 9 +- 5 files changed, 869 insertions(+), 783 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java delete mode 100644 core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index 2fc4e0aa6e8..19789e0860d 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -148,10 +148,6 @@ public ItemData.Builder translateToBedrock(GeyserSession session, int count, Dat .definition(mapping.getBedrockDefinition()) .damage(mapping.getBedrockData()) .count(count); - - ItemTranslator.translateCustomItem(session, count, components, builder, mapping); - - return builder; } public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 4ae5b407c83..93bb48fd617 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,8 @@ package org.geysermc.geyser.registry.populator; import com.google.common.collect.Multimap; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -37,35 +39,80 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; +import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; -import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.WearableSlot; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.GeyserMappingItem; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; public class CustomItemRegistryPopulator { - public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems) { + // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. + // TODO these don't seem to be applying correctly + private static final Map BEDROCK_ANIMATIONS = Map.of( + Consumable.ItemUseAnimation.NONE, 0, + Consumable.ItemUseAnimation.EAT, 1, + Consumable.ItemUseAnimation.DRINK, 2, + Consumable.ItemUseAnimation.BLOCK, 3, + Consumable.ItemUseAnimation.BOW, 4, + Consumable.ItemUseAnimation.SPEAR, 6, + Consumable.ItemUseAnimation.CROSSBOW, 9, + Consumable.ItemUseAnimation.SPYGLASS, 10, + Consumable.ItemUseAnimation.BRUSH, 12 + ); + + public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { + // TODO + // TODO better error handling? MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); // Load custom items from mappings files - mappingsConfigReader.loadItemMappingsFromJson((key, item) -> { - //if (CustomItemRegistryPopulator.initialCheck(key, item, items)) { - //customItems.get(key).add(item); - //} // TODO + mappingsConfigReader.loadItemMappingsFromJson((id, item) -> { + if (initialCheck(id, item, customItems, items)) { + customItems.get(id).add(item); + } }); + GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) { + + @Override + @Deprecated + public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { + return register(identifier, customItemData.toDefinition(identifier).build()); + } + + @Override + public boolean register(@NonNull String identifier, @NonNull CustomItemDefinition definition) { + if (initialCheck(identifier, definition, customItems, items)) { + customItems.get(identifier).add(definition); + return true; + } + return false; + } + + @Override + public boolean register(@NonNull NonVanillaCustomItemData customItemData) { + // TODO + return false; + } + }); int customItemCount = customItems.size() + nonVanillaCustomItems.size(); if (customItemCount > 0) { @@ -73,108 +120,133 @@ public static void populate(Map items, Multimap mappings) { + private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap registered, Map mappings) { + // TODO check if there's already a same model without predicate and this hasn't a predicate either if (!mappings.containsKey(identifier)) { - GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.name()); + GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier()); return false; } - if (!item.customItemOptions().hasCustomItemOptions()) { - GeyserImpl.getInstance().getLogger().error("The custom item " + item.name() + " has no registration types"); + Key bedrockIdentifier = item.bedrockIdentifier(); + if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { + GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft"); + return false; + } else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) { + GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be in the minecraft namespace without a predicate"); + return false; } - String name = item.name(); - if (name.isEmpty()) { - GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); - } else if (Character.isDigit(name.charAt(0))) { - // As of 1.19.31 - GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!"); + + for (Map.Entry entry : registered.entries()) { + if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) { + GeyserImpl.getInstance().getLogger().error("Duplicate custom item definition for Bedrock ID " + item.bedrockIdentifier()); + return false; + } + Optional error = checkPredicate(entry, identifier, item); + if (error.isPresent()) { + GeyserImpl.getInstance().getLogger().error("An existing item definition for the Java item " + identifier + " was already registered that conflicts with this one!"); + GeyserImpl.getInstance().getLogger().error("First entry: " + entry.getValue().bedrockIdentifier()); + GeyserImpl.getInstance().getLogger().error("Second entry: " + item.bedrockIdentifier()); + GeyserImpl.getInstance().getLogger().error(error.orElseThrow()); + } } + return true; } + /** + * Returns an error message if there was a conflict, or an empty optional otherwise + */ + // TODO maybe simplify this + private static Optional checkPredicate(Map.Entry existing, String identifier, CustomItemDefinition item) { + // If the definitions are for different Java items or models then it doesn't matter + if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) { + return Optional.empty(); + } + // If they both don't have predicates they conflict + if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) { + return Optional.of("Both entries don't have predicates, one must have a predicate"); + } + // If their predicates are equal then they also conflict + if (existing.getValue().predicates().size() == item.predicates().size()) { + boolean equal = true; + for (CustomItemPredicate predicate : existing.getValue().predicates()) { + if (!item.predicates().contains(predicate)) { + equal = false; + } + } + if (equal) { + return Optional.of("Both entries have the same predicates"); + } + } + + return Optional.empty(); + } + public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { - String customIdentifier = customItemData.identifier(); - - DataComponents components = new DataComponents(new HashMap<>()); - components.put(DataComponentType.MAX_STACK_SIZE, customItemData.stackSize()); - components.put(DataComponentType.MAX_DAMAGE, customItemData.maxDamage()); - - Item item = new Item(customIdentifier, Item.builder().components(components)); - Items.register(item, customItemData.javaId()); - - ItemMapping customItemMapping = ItemMapping.builder() - .bedrockDefinition(new SimpleItemDefinition(customIdentifier, customItemId, true)) - .bedrockData(0) - .bedrockBlockDefinition(null) - .toolType(customItemData.toolType()) - .translationString(customItemData.translationString()) - .customItemDefinitions(null) - .javaItem(item) - .build(); - - NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, - customItemData.isHat(), customItemData.displayHandheld(), protocolVersion); - ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build()); - - return new NonVanillaItemRegistration(componentItemData, item, customItemMapping); + // TODO + return null; } - private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping, - String customItemName, int customItemId, int protocolVersion) { - NbtMapBuilder builder = NbtMap.builder(); - builder.putString("name", customItemName) - .putInt("id", customItemId); + private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping, + String customItemName, int customItemId) { + NbtMapBuilder builder = NbtMap.builder() + .putString("name", customItemName) + .putInt("id", customItemId); NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder componentBuilder = NbtMap.builder(); - setupBasicItemInfo(javaItem.defaultMaxDamage(), javaItem.defaultMaxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion); + DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition); + setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder); boolean canDestroyInCreative = true; - if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, javaItem.defaultAttackDamage()); + if (vanillaMapping.getToolType() != null) { + canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage()); } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - if (mapping.getArmorType() != null) { - computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder); + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); + if (equippable != null) { + computeArmorProperties(equippable, itemProperties, componentBuilder); } - if (mapping.getFirstBlockRuntimeId() != null) { - computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder); + if (vanillaMapping.getFirstBlockRuntimeId() != null) { + computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder); } - if (mapping.isEdible()) { - computeConsumableProperties(itemProperties, componentBuilder, 1, false); + Consumable consumable = components.get(DataComponentType.CONSUMABLE); + if (consumable != null) { + FoodProperties foodProperties = components.get(DataComponentType.FOOD); + computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder); } - if (mapping.isEntityPlacer()) { + if (vanillaMapping.isEntityPlacer()) { computeEntityPlacerProperties(componentBuilder); } - switch (mapping.getBedrockIdentifier()) { + UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN); + if (useCooldown != null) { + computeUseCooldownProperties(useCooldown, componentBuilder); + } + + // TODO not really a fan of this switch statement + switch (vanillaMapping.getBedrockIdentifier()) { case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); - case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, mapping.getBedrockIdentifier(), protocolVersion); - case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> computeConsumableProperties(itemProperties, componentBuilder, 2, true); + case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, vanillaMapping.getBedrockIdentifier()); case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> - computeThrowableProperties(componentBuilder); + computeThrowableProperties(componentBuilder); } - // Hardcoded on Java, and should extend to the custom item - boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) - || javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD) - || javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD) - || javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD) - ); - computeRenderOffsets(isHat, customItemData, componentBuilder); + computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); // TODO check "hats" the hardcoded ones, once default components are here, check stack size componentBuilder.putCompound("item_properties", itemProperties.build()); builder.putCompound("components", componentBuilder.build()); @@ -184,100 +256,62 @@ private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, I private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName, int customItemId, boolean isHat, boolean displayHandheld, int protocolVersion) { - NbtMapBuilder builder = NbtMap.builder(); - builder.putString("name", customItemName) - .putInt("id", customItemId); - - NbtMapBuilder itemProperties = NbtMap.builder(); - NbtMapBuilder componentBuilder = NbtMap.builder(); - - setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder, protocolVersion); - - boolean canDestroyInCreative = true; - if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage()); - } - itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - - String armorType = customItemData.armorType(); - if (armorType != null) { - computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder); - } - - if (customItemData.isEdible()) { - computeConsumableProperties(itemProperties, componentBuilder, 1, customItemData.canAlwaysEat()); - } - - if (customItemData.isChargeable()) { - String tooltype = customItemData.toolType(); - if (tooltype == null) { - throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!"); - } - computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion); - } - - computeRenderOffsets(isHat, customItemData, componentBuilder); - - if (customItemData.isFoil()) { - itemProperties.putBoolean("foil", true); - } - - String block = customItemData.block(); - if (block != null) { - computeBlockItemProperties(block, componentBuilder); - } - - componentBuilder.putCompound("item_properties", itemProperties.build()); - builder.putCompound("components", componentBuilder.build()); - - return builder; + // TODO; + return null; } - private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) { + private static void setupBasicItemInfo(CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + CustomItemBedrockOptions options = definition.bedrockOptions(); NbtMap iconMap = NbtMap.builder() .putCompound("textures", NbtMap.builder() - .putString("default", customItemData.icon()) - .build()) + .putString("default", definition.icon()) + .build()) .build(); itemProperties.putCompound("minecraft:icon", iconMap); - if (customItemData.creativeCategory().isPresent()) { - itemProperties.putInt("creative_category", customItemData.creativeCategory().getAsInt()); + if (options.creativeCategory() != BedrockCreativeTab.NONE) { + itemProperties.putInt("creative_category", options.creativeCategory().ordinal()); - if (customItemData.creativeGroup() != null) { - itemProperties.putString("creative_group", customItemData.creativeGroup()); + if (options.creativeGroup() != null) { + itemProperties.putString("creative_group", options.creativeGroup()); } } - componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build()); + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build()); // Add a Geyser tag to the item, allowing Molang queries addItemTag(componentBuilder, "geyser:is_custom"); // Add other defined tags to the item - Set tags = customItemData.tags(); + Set tags = options.tags(); for (String tag : tags) { if (tag != null && !tag.isBlank()) { addItemTag(componentBuilder, tag); } } - itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand()); - itemProperties.putBoolean("hand_equipped", displayHandheld); + itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); + itemProperties.putBoolean("hand_equipped", options.displayHandheld()); + + int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); + // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's + itemProperties.putInt("max_stack_size", stackSize); - // Ignore durability if the item's predicate requires that it be unbreakable - if (maxDamage > 0 && customItemData.customItemOptions().unbreakable() != TriState.TRUE) { + if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here? componentBuilder.putCompound("minecraft:durability", NbtMap.builder() - .putCompound("damage_chance", NbtMap.builder() - .putInt("max", 1) - .putInt("min", 1) - .build()) - .putInt("max_durability", maxDamage) - .build()); + .putCompound("damage_chance", NbtMap.builder() + .putInt("max", 1) + .putInt("min", 1) + .build()) + .putInt("max_durability", maxDamage) + .build()); itemProperties.putBoolean("use_duration", true); } } + // TODO minecraft java tool component - also needs work elsewhere to calculate correct break speed (server authorised block breaking) /** * @return can destroy in creative */ @@ -291,33 +325,33 @@ private static boolean computeToolProperties(String toolType, NbtMapBuilder item List speed = new ArrayList<>(List.of( NbtMap.builder() .putCompound("block", NbtMap.builder() - .putString("tags", "1") - .build()) + .putString("tags", "1") + .build()) .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) .putInt("speed", 0) .build() )); - + componentBuilder.putCompound("minecraft:digger", NbtMap.builder() - .putList("destroy_speeds", NbtType.COMPOUND, speed) - .putCompound("on_dig", NbtMap.builder() + .putList("destroy_speeds", NbtType.COMPOUND, speed) + .putCompound("on_dig", NbtMap.builder() .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) + .putString("expression", "") + .putInt("version", -1) + .build()) .putString("event", "tool_durability") .putString("target", "self") .build()) - .putBoolean("use_efficiency", true) - .build() + .putBoolean("use_efficiency", true) + .build() ); if (toolType.equals("sword")) { @@ -340,39 +374,38 @@ private static boolean computeToolProperties(String toolType, NbtMapBuilder item return canDestroyInCreative; } - private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - switch (armorType) { - case "boots" -> { + private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + int protectionValue = 0; + // TODO protection value, check if it's just visual or not, also enchantable stuff + switch (equippable.slot()) { + case BOOTS -> { componentBuilder.putString("minecraft:render_offsets", "boots"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_feet"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_feet"); + //itemProperties.putInt("enchantable_value", 15); TODO } - case "chestplate" -> { + case CHESTPLATE -> { componentBuilder.putString("minecraft:render_offsets", "chestplates"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_torso"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_torso"); + //itemProperties.putInt("enchantable_value", 15); TODO } - case "leggings" -> { + case LEGGINGS -> { componentBuilder.putString("minecraft:render_offsets", "leggings"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_legs"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_legs"); + //itemProperties.putInt("enchantable_value", 15); TODO } - case "helmet" -> { + case HELMET -> { componentBuilder.putString("minecraft:render_offsets", "helmets"); componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + //componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - itemProperties.putString("enchantable_slot", "armor_head"); - itemProperties.putInt("enchantable_value", 15); + //itemProperties.putString("enchantable_slot", "armor_head"); + //itemProperties.putInt("enchantable_value", 15); } } } @@ -386,7 +419,8 @@ private static void computeBlockItemProperties(String blockItem, NbtMapBuilder c componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); } - private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping, int protocolVersion) { + // TODO this isn't right + private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping) { // setting high use_duration prevents the consume animation from playing itemProperties.putInt("use_duration", Integer.MAX_VALUE); // display item as tool (mainly for crossbow and bow) @@ -395,9 +429,9 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb itemProperties.putInt("enchantable_value", 1); componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() - .putFloat("use_duration", 100F) - .putFloat("movement_modifier", 0.35F) - .build()); + .putFloat("use_duration", 100F) + .putFloat("movement_modifier", 0.35F) + .build()); switch (mapping) { case "minecraft:bow" -> { @@ -405,19 +439,19 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb itemProperties.putInt("frame_count", 3); componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() - .putList("ammunition", NbtType.COMPOUND, List.of( - NbtMap.builder() - .putCompound("item", NbtMap.builder() - .putString("name", "minecraft:arrow") - .build()) - .putBoolean("use_offhand", true) - .putBoolean("search_inventory", true) - .build() - )) - .putFloat("max_draw_duration", 0f) - .putBoolean("charge_on_draw", true) - .putBoolean("scale_power_by_draw_duration", true) - .build()); + .putList("ammunition", NbtType.COMPOUND, List.of( + NbtMap.builder() + .putCompound("item", NbtMap.builder() + .putString("name", "minecraft:arrow") + .build()) + .putBoolean("use_offhand", true) + .putBoolean("search_inventory", true) + .build() + )) + .putFloat("max_draw_duration", 0f) + .putBoolean("charge_on_draw", true) + .putBoolean("scale_power_by_draw_duration", true) + .build()); componentBuilder.putInt("minecraft:use_duration", 999); } case "minecraft:trident" -> { @@ -429,40 +463,44 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb itemProperties.putInt("frame_count", 10); componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() - .putList("ammunition", NbtType.COMPOUND, List.of( - NbtMap.builder() - .putCompound("item", NbtMap.builder() - .putString("name", "minecraft:arrow") - .build()) - .putBoolean("use_offhand", true) - .putBoolean("search_inventory", true) - .build() - )) - .putFloat("max_draw_duration", 1f) - .putBoolean("charge_on_draw", true) - .putBoolean("scale_power_by_draw_duration", true) - .build()); + .putList("ammunition", NbtType.COMPOUND, List.of( + NbtMap.builder() + .putCompound("item", NbtMap.builder() + .putString("name", "minecraft:arrow") + .build()) + .putBoolean("use_offhand", true) + .putBoolean("search_inventory", true) + .build() + )) + .putFloat("max_draw_duration", 1f) + .putBoolean("charge_on_draw", true) + .putBoolean("scale_power_by_draw_duration", true) + .build()); componentBuilder.putInt("minecraft:use_duration", 999); } } } - private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) { + private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks - itemProperties.putInt("use_duration", 32); - // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds - // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively - itemProperties.putInt("use_animation", useAnimation); + itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); + + itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); + componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() + .putString("value", consumable.animation().toString().toLowerCase()) + .build()); // TODO check + // this component is required to allow the eat animation to play componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); } private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { // all items registered that place entities should be given this component to prevent double placement - // it is okay that the entity here does not match the actual one since we control what entity actually spawns + // it is okay that the entity here does not match the actual one since we control what entity actually spawns componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); } + // TODO this also probably isn't right private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { // allows item to be thrown when holding down right click (individual presses are required w/o this component) componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); @@ -471,31 +509,32 @@ private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build()); } - private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) { - if (isHat) { - componentBuilder.remove("minecraft:render_offsets"); - componentBuilder.putString("minecraft:render_offsets", "helmets"); - - componentBuilder.remove("minecraft:wearable"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - } + private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) { + Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null"); + componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder() + .putString("category", cooldown.cooldownGroup().asString()) + .putFloat("duration", cooldown.seconds()) + .build() + ); + } - CustomRenderOffsets renderOffsets = customItemData.renderOffsets(); + private static void computeRenderOffsets(CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) { + CustomRenderOffsets renderOffsets = bedrockOptions.renderOffsets(); if (renderOffsets != null) { componentBuilder.remove("minecraft:render_offsets"); componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); - } else if (customItemData.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { - float scale1 = (float) (0.075 / (customItemData.textureSize() / 16f)); - float scale2 = (float) (0.125 / (customItemData.textureSize() / 16f)); - float scale3 = (float) (0.075 / (customItemData.textureSize() / 16f * 2.4f)); + } else if (bedrockOptions.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { + float scale1 = (float) (0.075 / (bedrockOptions.textureSize() / 16f)); + float scale2 = (float) (0.125 / (bedrockOptions.textureSize() / 16f)); + float scale3 = (float) (0.075 / (bedrockOptions.textureSize() / 16f * 2.4f)); componentBuilder.putCompound("minecraft:render_offsets", - NbtMap.builder().putCompound("main_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) - .putCompound("off_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); + NbtMap.builder().putCompound("main_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) + .putCompound("off_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); } } @@ -570,6 +609,15 @@ private static List toList(CustomRenderOffsets.OffsetXYZ xyz) { return List.of(xyz.x(), xyz.y(), xyz.z()); } + private static NbtMap xyzToScaleList(float x, float y, float z) { + return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); + } + + // TODO is this right? + private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { + return javaItem.gatherComponents(definition.components()); + } + @SuppressWarnings("unchecked") private static void addItemTag(NbtMapBuilder builder, String tag) { List tagList = (List) builder.get("item_tags"); @@ -584,8 +632,4 @@ private static void addItemTag(NbtMapBuilder builder, String tag) { } } } - - private static NbtMap xyzToScaleList(float x, float y, float z) { - return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); - } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java new file mode 100644 index 00000000000..298fb5043ae --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.google.common.collect.Multimap; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.nbt.NbtType; +import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; +import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; +import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.item.custom.CustomItemData; +import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.item.GeyserCustomMappingData; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.components.WearableSlot; +import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.registry.mappings.MappingsConfigReader; +import org.geysermc.geyser.registry.type.GeyserMappingItem; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class CustomItemRegistryPopulator_v1 { + public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems) { + MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); + // Load custom items from mappings files + mappingsConfigReader.loadItemMappingsFromJson((key, item) -> { + //if (CustomItemRegistryPopulator.initialCheck(key, item, items)) { + //customItems.get(key).add(item); + //} // TODO + }); + + + int customItemCount = customItems.size() + nonVanillaCustomItems.size(); + if (customItemCount > 0) { + GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items"); + } + } + + public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId, int protocolVersion) { + ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); + + NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId, protocolVersion); + ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); + + return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); + } + + static boolean initialCheck(String identifier, CustomItemData item, Map mappings) { + if (!mappings.containsKey(identifier)) { + GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.name()); + return false; + } + if (!item.customItemOptions().hasCustomItemOptions()) { + GeyserImpl.getInstance().getLogger().error("The custom item " + item.name() + " has no registration types"); + } + String name = item.name(); + if (name.isEmpty()) { + GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); + } else if (Character.isDigit(name.charAt(0))) { + // As of 1.19.31 + GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!"); + } + return true; + } + + public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { + String customIdentifier = customItemData.identifier(); + + DataComponents components = new DataComponents(new HashMap<>()); + components.put(DataComponentType.MAX_STACK_SIZE, customItemData.stackSize()); + components.put(DataComponentType.MAX_DAMAGE, customItemData.maxDamage()); + + Item item = new Item(customIdentifier, Item.builder().components(components)); + Items.register(item, customItemData.javaId()); + + ItemMapping customItemMapping = ItemMapping.builder() + .bedrockDefinition(new SimpleItemDefinition(customIdentifier, customItemId, true)) + .bedrockData(0) + .bedrockBlockDefinition(null) + .toolType(customItemData.toolType()) + .translationString(customItemData.translationString()) + .customItemDefinitions(null) + .javaItem(item) + .build(); + + NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, + customItemData.isHat(), customItemData.displayHandheld(), protocolVersion); + ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build()); + + return new NonVanillaItemRegistration(componentItemData, item, customItemMapping); + } + + private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping, + String customItemName, int customItemId, int protocolVersion) { + NbtMapBuilder builder = NbtMap.builder(); + builder.putString("name", customItemName) + .putInt("id", customItemId); + + NbtMapBuilder itemProperties = NbtMap.builder(); + NbtMapBuilder componentBuilder = NbtMap.builder(); + + setupBasicItemInfo(javaItem.defaultMaxDamage(), javaItem.defaultMaxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion); + + boolean canDestroyInCreative = true; + if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. + canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, javaItem.defaultAttackDamage()); + } + itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + + if (mapping.getArmorType() != null) { + computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder); + } + + if (mapping.getFirstBlockRuntimeId() != null) { + computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder); + } + + if (mapping.isEdible()) { + computeConsumableProperties(itemProperties, componentBuilder, 1, false); + } + + if (mapping.isEntityPlacer()) { + computeEntityPlacerProperties(componentBuilder); + } + + switch (mapping.getBedrockIdentifier()) { + case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); + case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, mapping.getBedrockIdentifier(), protocolVersion); + case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> computeConsumableProperties(itemProperties, componentBuilder, 2, true); + case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> + computeThrowableProperties(componentBuilder); + } + + // Hardcoded on Java, and should extend to the custom item + boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) + || javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD) + || javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD) + || javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD) + ); + computeRenderOffsets(isHat, customItemData, componentBuilder); + + componentBuilder.putCompound("item_properties", itemProperties.build()); + builder.putCompound("components", componentBuilder.build()); + + return builder; + } + + private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName, + int customItemId, boolean isHat, boolean displayHandheld, int protocolVersion) { + NbtMapBuilder builder = NbtMap.builder(); + builder.putString("name", customItemName) + .putInt("id", customItemId); + + NbtMapBuilder itemProperties = NbtMap.builder(); + NbtMapBuilder componentBuilder = NbtMap.builder(); + + setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder, protocolVersion); + + boolean canDestroyInCreative = true; + if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here. + canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage()); + } + itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + + String armorType = customItemData.armorType(); + if (armorType != null) { + computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder); + } + + if (customItemData.isEdible()) { + computeConsumableProperties(itemProperties, componentBuilder, 1, customItemData.canAlwaysEat()); + } + + if (customItemData.isChargeable()) { + String tooltype = customItemData.toolType(); + if (tooltype == null) { + throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!"); + } + computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion); + } + + computeRenderOffsets(isHat, customItemData, componentBuilder); + + if (customItemData.isFoil()) { + itemProperties.putBoolean("foil", true); + } + + String block = customItemData.block(); + if (block != null) { + computeBlockItemProperties(block, componentBuilder); + } + + componentBuilder.putCompound("item_properties", itemProperties.build()); + builder.putCompound("components", componentBuilder.build()); + + return builder; + } + + private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) { + NbtMap iconMap = NbtMap.builder() + .putCompound("textures", NbtMap.builder() + .putString("default", customItemData.icon()) + .build()) + .build(); + itemProperties.putCompound("minecraft:icon", iconMap); + + if (customItemData.creativeCategory().isPresent()) { + itemProperties.putInt("creative_category", customItemData.creativeCategory().getAsInt()); + + if (customItemData.creativeGroup() != null) { + itemProperties.putString("creative_group", customItemData.creativeGroup()); + } + } + + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build()); + + // Add a Geyser tag to the item, allowing Molang queries + addItemTag(componentBuilder, "geyser:is_custom"); + + // Add other defined tags to the item + Set tags = customItemData.tags(); + for (String tag : tags) { + if (tag != null && !tag.isBlank()) { + addItemTag(componentBuilder, tag); + } + } + + itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand()); + itemProperties.putBoolean("hand_equipped", displayHandheld); + itemProperties.putInt("max_stack_size", stackSize); + // Ignore durability if the item's predicate requires that it be unbreakable + if (maxDamage > 0 && customItemData.customItemOptions().unbreakable() != TriState.TRUE) { + componentBuilder.putCompound("minecraft:durability", NbtMap.builder() + .putCompound("damage_chance", NbtMap.builder() + .putInt("max", 1) + .putInt("min", 1) + .build()) + .putInt("max_durability", maxDamage) + .build()); + itemProperties.putBoolean("use_duration", true); + } + } + + /** + * @return can destroy in creative + */ + private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) { + boolean canDestroyInCreative = true; + float miningSpeed = 1.0f; + + // This means client side the tool can never destroy a block + // This works because the molang '1' for tags will be true for all blocks and the speed will be 0 + // We want this since we calculate break speed server side in BedrockActionTranslator + List speed = new ArrayList<>(List.of( + NbtMap.builder() + .putCompound("block", NbtMap.builder() + .putString("tags", "1") + .build()) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putInt("speed", 0) + .build() + )); + + componentBuilder.putCompound("minecraft:digger", + NbtMap.builder() + .putList("destroy_speeds", NbtType.COMPOUND, speed) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putBoolean("use_efficiency", true) + .build() + ); + + if (toolType.equals("sword")) { + miningSpeed = 1.5f; + canDestroyInCreative = false; + } + + itemProperties.putBoolean("hand_equipped", true); + itemProperties.putFloat("mining_speed", miningSpeed); + + // This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil + itemProperties.putInt("enchantable_value", 1); + itemProperties.putString("enchantable_slot", toolType); + + // Adds a "attack damage" indicator. Purely visual! + if (attackDamage > 0) { + itemProperties.putInt("damage", attackDamage); + } + + return canDestroyInCreative; + } + + private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + switch (armorType) { + case "boots" -> { + componentBuilder.putString("minecraft:render_offsets", "boots"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_feet"); + itemProperties.putInt("enchantable_value", 15); + } + case "chestplate" -> { + componentBuilder.putString("minecraft:render_offsets", "chestplates"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_torso"); + itemProperties.putInt("enchantable_value", 15); + } + case "leggings" -> { + componentBuilder.putString("minecraft:render_offsets", "leggings"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_legs"); + itemProperties.putInt("enchantable_value", 15); + } + case "helmet" -> { + componentBuilder.putString("minecraft:render_offsets", "helmets"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); + componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); + + itemProperties.putString("enchantable_slot", "armor_head"); + itemProperties.putInt("enchantable_value", 15); + } + } + } + + private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) { + // carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here + // however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot + // it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome + + // all block items registered should be given this component to prevent double placement + componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); + } + + private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping, int protocolVersion) { + // setting high use_duration prevents the consume animation from playing + itemProperties.putInt("use_duration", Integer.MAX_VALUE); + // display item as tool (mainly for crossbow and bow) + itemProperties.putBoolean("hand_equipped", true); + // Make bows, tridents, and crossbows enchantable + itemProperties.putInt("enchantable_value", 1); + + componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() + .putFloat("use_duration", 100F) + .putFloat("movement_modifier", 0.35F) + .build()); + + switch (mapping) { + case "minecraft:bow" -> { + itemProperties.putString("enchantable_slot", "bow"); + itemProperties.putInt("frame_count", 3); + + componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() + .putList("ammunition", NbtType.COMPOUND, List.of( + NbtMap.builder() + .putCompound("item", NbtMap.builder() + .putString("name", "minecraft:arrow") + .build()) + .putBoolean("use_offhand", true) + .putBoolean("search_inventory", true) + .build() + )) + .putFloat("max_draw_duration", 0f) + .putBoolean("charge_on_draw", true) + .putBoolean("scale_power_by_draw_duration", true) + .build()); + componentBuilder.putInt("minecraft:use_duration", 999); + } + case "minecraft:trident" -> { + itemProperties.putString("enchantable_slot", "trident"); + componentBuilder.putInt("minecraft:use_duration", 999); + } + case "minecraft:crossbow" -> { + itemProperties.putString("enchantable_slot", "crossbow"); + itemProperties.putInt("frame_count", 10); + + componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() + .putList("ammunition", NbtType.COMPOUND, List.of( + NbtMap.builder() + .putCompound("item", NbtMap.builder() + .putString("name", "minecraft:arrow") + .build()) + .putBoolean("use_offhand", true) + .putBoolean("search_inventory", true) + .build() + )) + .putFloat("max_draw_duration", 1f) + .putBoolean("charge_on_draw", true) + .putBoolean("scale_power_by_draw_duration", true) + .build()); + componentBuilder.putInt("minecraft:use_duration", 999); + } + } + } + + private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) { + // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks + itemProperties.putInt("use_duration", 32); + // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds + // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively + itemProperties.putInt("use_animation", useAnimation); + // this component is required to allow the eat animation to play + componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); + } + + private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { + // all items registered that place entities should be given this component to prevent double placement + // it is okay that the entity here does not match the actual one since we control what entity actually spawns + componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); + } + + private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { + // allows item to be thrown when holding down right click (individual presses are required w/o this component) + componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); + // this must be set to something for the swing animation to play + // it is okay that the projectile here does not match the actual one since we control what entity actually spawns + componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build()); + } + + private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) { + if (isHat) { + componentBuilder.remove("minecraft:render_offsets"); + componentBuilder.putString("minecraft:render_offsets", "helmets"); + + componentBuilder.remove("minecraft:wearable"); + componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); + } + + CustomRenderOffsets renderOffsets = customItemData.renderOffsets(); + if (renderOffsets != null) { + componentBuilder.remove("minecraft:render_offsets"); + componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); + } else if (customItemData.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { + float scale1 = (float) (0.075 / (customItemData.textureSize() / 16f)); + float scale2 = (float) (0.125 / (customItemData.textureSize() / 16f)); + float scale3 = (float) (0.075 / (customItemData.textureSize() / 16f * 2.4f)); + + componentBuilder.putCompound("minecraft:render_offsets", + NbtMap.builder().putCompound("main_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) + .putCompound("off_hand", NbtMap.builder() + .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) + .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); + } + } + + private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) { + NbtMapBuilder builder = NbtMap.builder(); + + CustomRenderOffsets.Hand mainHand = renderOffsets.mainHand(); + if (mainHand != null) { + NbtMap nbt = toNbtMap(mainHand); + if (nbt != null) { + builder.putCompound("main_hand", nbt); + } + } + CustomRenderOffsets.Hand offhand = renderOffsets.offhand(); + if (offhand != null) { + NbtMap nbt = toNbtMap(offhand); + if (nbt != null) { + builder.putCompound("off_hand", nbt); + } + } + + return builder.build(); + } + + private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.Hand hand) { + NbtMap firstPerson = toNbtMap(hand.firstPerson()); + NbtMap thirdPerson = toNbtMap(hand.thirdPerson()); + + if (firstPerson == null && thirdPerson == null) { + return null; + } + + NbtMapBuilder builder = NbtMap.builder(); + if (firstPerson != null) { + builder.putCompound("first_person", firstPerson); + } + if (thirdPerson != null) { + builder.putCompound("third_person", thirdPerson); + } + + return builder.build(); + } + + private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.@Nullable Offset offset) { + if (offset == null) { + return null; + } + + CustomRenderOffsets.OffsetXYZ position = offset.position(); + CustomRenderOffsets.OffsetXYZ rotation = offset.rotation(); + CustomRenderOffsets.OffsetXYZ scale = offset.scale(); + + if (position == null && rotation == null && scale == null) { + return null; + } + + NbtMapBuilder builder = NbtMap.builder(); + if (position != null) { + builder.putList("position", NbtType.FLOAT, toList(position)); + } + if (rotation != null) { + builder.putList("rotation", NbtType.FLOAT, toList(rotation)); + } + if (scale != null) { + builder.putList("scale", NbtType.FLOAT, toList(scale)); + } + + return builder.build(); + } + + private static List toList(CustomRenderOffsets.OffsetXYZ xyz) { + return List.of(xyz.x(), xyz.y(), xyz.z()); + } + + @SuppressWarnings("unchecked") + private static void addItemTag(NbtMapBuilder builder, String tag) { + List tagList = (List) builder.get("item_tags"); + if (tagList == null) { + builder.putList("item_tags", NbtType.STRING, tag); + } else { + // NbtList is immutable + if (!tagList.contains(tag)) { + tagList = new ArrayList<>(tagList); + tagList.add(tag); + builder.putList("item_tags", NbtType.STRING, tagList); + } + } + } + + private static NbtMap xyzToScaleList(float x, float y, float z) { + return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java deleted file mode 100644 index 9867474c231..00000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v2.java +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright (c) 2024 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.registry.populator; - -import com.google.common.collect.Multimap; -import net.kyori.adventure.key.Key; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.nbt.NbtMapBuilder; -import org.cloudburstmc.nbt.NbtType; -import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; -import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.item.custom.CustomItemData; -import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; -import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; -import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; -import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; -import org.geysermc.geyser.item.GeyserCustomMappingData; -import org.geysermc.geyser.item.components.WearableSlot; -import org.geysermc.geyser.item.type.Item; -import org.geysermc.geyser.registry.mappings.MappingsConfigReader; -import org.geysermc.geyser.registry.type.GeyserMappingItem; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -public class CustomItemRegistryPopulator_v2 { - // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. - private static final Map BEDROCK_ANIMATIONS = Map.of( - Consumable.ItemUseAnimation.NONE, 0, - Consumable.ItemUseAnimation.EAT, 1, - Consumable.ItemUseAnimation.DRINK, 2, - Consumable.ItemUseAnimation.BLOCK, 3, - Consumable.ItemUseAnimation.BOW, 4, - Consumable.ItemUseAnimation.SPEAR, 6, - Consumable.ItemUseAnimation.CROSSBOW, 9, - Consumable.ItemUseAnimation.SPYGLASS, 10, - Consumable.ItemUseAnimation.BRUSH, 12 - ); - - public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { - // TODO - // TODO better error handling? - MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); - // Load custom items from mappings files - mappingsConfigReader.loadItemMappingsFromJson((id, item) -> { - if (initialCheck(id, item, customItems, items)) { - customItems.get(id).add(item); - } - }); - - GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) { - - @Override - @Deprecated - public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { - return register(identifier, customItemData.toDefinition(identifier).build()); - } - - @Override - public boolean register(@NonNull String identifier, @NonNull CustomItemDefinition definition) { - if (initialCheck(identifier, definition, customItems, items)) { - customItems.get(identifier).add(definition); - return true; - } - return false; - } - - @Override - public boolean register(@NonNull NonVanillaCustomItemData customItemData) { - // TODO - return false; - } - }); - - int customItemCount = customItems.size() + nonVanillaCustomItems.size(); - if (customItemCount > 0) { - GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items"); - } - } - - public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, - CustomItemDefinition customItemDefinition, int bedrockId) { - ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); - - NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId); - ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); - - return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); - } - - private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap registered, Map mappings) { - // TODO check if there's already a same model without predicate and this hasn't a predicate either - if (!mappings.containsKey(identifier)) { - GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier()); - return false; - } - Key bedrockIdentifier = item.bedrockIdentifier(); - if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft"); - return false; - } else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) { - GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be in the minecraft namespace without a predicate"); - return false; - } - - for (Map.Entry entry : registered.entries()) { - if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) { - GeyserImpl.getInstance().getLogger().error("Duplicate custom item definition for Bedrock ID " + item.bedrockIdentifier()); - return false; - } - Optional error = checkPredicate(entry, identifier, item); - if (error.isPresent()) { - GeyserImpl.getInstance().getLogger().error("An existing item definition for the Java item " + identifier + " was already registered that conflicts with this one!"); - GeyserImpl.getInstance().getLogger().error("First entry: " + entry.getValue().bedrockIdentifier()); - GeyserImpl.getInstance().getLogger().error("Second entry: " + item.bedrockIdentifier()); - GeyserImpl.getInstance().getLogger().error(error.orElseThrow()); - } - } - - return true; - } - - /** - * Returns an error message if there was a conflict, or an empty optional otherwise - */ - // TODO maybe simplify this - private static Optional checkPredicate(Map.Entry existing, String identifier, CustomItemDefinition item) { - // If the definitions are for different Java items or models then it doesn't matter - if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) { - return Optional.empty(); - } - // If they both don't have predicates they conflict - if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) { - return Optional.of("Both entries don't have predicates, one must have a predicate"); - } - // If their predicates are equal then they also conflict - if (existing.getValue().predicates().size() == item.predicates().size()) { - boolean equal = true; - for (CustomItemPredicate predicate : existing.getValue().predicates()) { - if (!item.predicates().contains(predicate)) { - equal = false; - } - } - if (equal) { - return Optional.of("Both entries have the same predicates"); - } - } - - return Optional.empty(); - } - - private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping, - String customItemName, int customItemId) { - NbtMapBuilder builder = NbtMap.builder() - .putString("name", customItemName) - .putInt("id", customItemId); - - NbtMapBuilder itemProperties = NbtMap.builder(); - NbtMapBuilder componentBuilder = NbtMap.builder(); - - DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition); - setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder); - - boolean canDestroyInCreative = true; - if (vanillaMapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage()); - } - itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - - Equippable equippable = components.get(DataComponentType.EQUIPPABLE); - if (equippable != null) { - computeArmorProperties(equippable, itemProperties, componentBuilder); - } - - if (vanillaMapping.getFirstBlockRuntimeId() != null) { - computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder); - } - - Consumable consumable = components.get(DataComponentType.CONSUMABLE); - if (consumable != null) { - FoodProperties foodProperties = components.get(DataComponentType.FOOD); - computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder); - } - - if (vanillaMapping.isEntityPlacer()) { - computeEntityPlacerProperties(componentBuilder); - } - - UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN); - if (useCooldown != null) { - computeUseCooldownProperties(useCooldown, componentBuilder); - } - - // TODO that switch statement - - computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); // TODO check "hats" the hardcoded ones, once default components are here, check stack size - - componentBuilder.putCompound("item_properties", itemProperties.build()); - builder.putCompound("components", componentBuilder.build()); - - return builder; - } - - private static void setupBasicItemInfo(CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - CustomItemBedrockOptions options = definition.bedrockOptions(); - NbtMap iconMap = NbtMap.builder() - .putCompound("textures", NbtMap.builder() - .putString("default", definition.icon()) - .build()) - .build(); - itemProperties.putCompound("minecraft:icon", iconMap); - - if (options.creativeCategory() != BedrockCreativeTab.NONE) { - itemProperties.putInt("creative_category", options.creativeCategory().ordinal()); - - if (options.creativeGroup() != null) { - itemProperties.putString("creative_group", options.creativeGroup()); - } - } - - componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build()); - - // Add a Geyser tag to the item, allowing Molang queries - addItemTag(componentBuilder, "geyser:is_custom"); - - // Add other defined tags to the item - Set tags = options.tags(); - for (String tag : tags) { - if (tag != null && !tag.isBlank()) { - addItemTag(componentBuilder, tag); - } - } - - itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); - itemProperties.putBoolean("hand_equipped", options.displayHandheld()); - - int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); - Equippable equippable = components.get(DataComponentType.EQUIPPABLE); - // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped - int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's - - itemProperties.putInt("max_stack_size", stackSize); - if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here? - componentBuilder.putCompound("minecraft:durability", NbtMap.builder() - .putCompound("damage_chance", NbtMap.builder() - .putInt("max", 1) - .putInt("min", 1) - .build()) - .putInt("max_durability", maxDamage) - .build()); - itemProperties.putBoolean("use_duration", true); - } - } - - // TODO minecraft java tool component - also needs work elsewhere to calculate correct break speed (server authorised block breaking) - private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) { - boolean canDestroyInCreative = true; - float miningSpeed = 1.0f; - - // This means client side the tool can never destroy a block - // This works because the molang '1' for tags will be true for all blocks and the speed will be 0 - // We want this since we calculate break speed server side in BedrockActionTranslator - List speed = new ArrayList<>(List.of( - NbtMap.builder() - .putCompound("block", NbtMap.builder() - .putString("tags", "1") - .build()) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putInt("speed", 0) - .build() - )); - - componentBuilder.putCompound("minecraft:digger", - NbtMap.builder() - .putList("destroy_speeds", NbtType.COMPOUND, speed) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putBoolean("use_efficiency", true) - .build() - ); - - if (toolType.equals("sword")) { - miningSpeed = 1.5f; - canDestroyInCreative = false; - } - - itemProperties.putBoolean("hand_equipped", true); - itemProperties.putFloat("mining_speed", miningSpeed); - - // This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil - itemProperties.putInt("enchantable_value", 1); - itemProperties.putString("enchantable_slot", toolType); - - // Adds a "attack damage" indicator. Purely visual! - if (attackDamage > 0) { - itemProperties.putInt("damage", attackDamage); - } - - return canDestroyInCreative; - } - - private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) { - // carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here - // however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot - // it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome - - // all block items registered should be given this component to prevent double placement - componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); - } - - private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - int protectionValue = 0; - // TODO protection value - switch (equippable.slot()) { - case BOOTS -> { - componentBuilder.putString("minecraft:render_offsets", "boots"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); - - //itemProperties.putString("enchantable_slot", "armor_feet"); - //itemProperties.putInt("enchantable_value", 15); TODO - } - case CHESTPLATE -> { - componentBuilder.putString("minecraft:render_offsets", "chestplates"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); - - //itemProperties.putString("enchantable_slot", "armor_torso"); - //itemProperties.putInt("enchantable_value", 15); TODO - } - case LEGGINGS -> { - componentBuilder.putString("minecraft:render_offsets", "leggings"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); - - //itemProperties.putString("enchantable_slot", "armor_legs"); - //itemProperties.putInt("enchantable_value", 15); TODO - } - case HELMET -> { - componentBuilder.putString("minecraft:render_offsets", "helmets"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - //componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - - //itemProperties.putString("enchantable_slot", "armor_head"); - //itemProperties.putInt("enchantable_value", 15); - } - } - } - - private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks - itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); - - itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); - componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() - .putString("value", consumable.animation().toString().toLowerCase()) - .build()); // TODO check - - // this component is required to allow the eat animation to play - componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); - } - - private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { - // all items registered that place entities should be given this component to prevent double placement - // it is okay that the entity here does not match the actual one since we control what entity actually spawns - componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); - } - - private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) { - Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null"); - componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder() - .putString("category", cooldown.cooldownGroup().asString()) - .putFloat("duration", cooldown.seconds()) - .build() - ); - } - - private static void computeRenderOffsets(CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) { - CustomRenderOffsets renderOffsets = bedrockOptions.renderOffsets(); - if (renderOffsets != null) { - componentBuilder.remove("minecraft:render_offsets"); - componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); - } else if (bedrockOptions.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { - float scale1 = (float) (0.075 / (bedrockOptions.textureSize() / 16f)); - float scale2 = (float) (0.125 / (bedrockOptions.textureSize() / 16f)); - float scale3 = (float) (0.075 / (bedrockOptions.textureSize() / 16f * 2.4f)); - - componentBuilder.putCompound("minecraft:render_offsets", - NbtMap.builder().putCompound("main_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) - .putCompound("off_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); - } - } - - private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) { - NbtMapBuilder builder = NbtMap.builder(); - - CustomRenderOffsets.Hand mainHand = renderOffsets.mainHand(); - if (mainHand != null) { - NbtMap nbt = toNbtMap(mainHand); - if (nbt != null) { - builder.putCompound("main_hand", nbt); - } - } - CustomRenderOffsets.Hand offhand = renderOffsets.offhand(); - if (offhand != null) { - NbtMap nbt = toNbtMap(offhand); - if (nbt != null) { - builder.putCompound("off_hand", nbt); - } - } - - return builder.build(); - } - - private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.Hand hand) { - NbtMap firstPerson = toNbtMap(hand.firstPerson()); - NbtMap thirdPerson = toNbtMap(hand.thirdPerson()); - - if (firstPerson == null && thirdPerson == null) { - return null; - } - - NbtMapBuilder builder = NbtMap.builder(); - if (firstPerson != null) { - builder.putCompound("first_person", firstPerson); - } - if (thirdPerson != null) { - builder.putCompound("third_person", thirdPerson); - } - - return builder.build(); - } - - private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.@Nullable Offset offset) { - if (offset == null) { - return null; - } - - CustomRenderOffsets.OffsetXYZ position = offset.position(); - CustomRenderOffsets.OffsetXYZ rotation = offset.rotation(); - CustomRenderOffsets.OffsetXYZ scale = offset.scale(); - - if (position == null && rotation == null && scale == null) { - return null; - } - - NbtMapBuilder builder = NbtMap.builder(); - if (position != null) { - builder.putList("position", NbtType.FLOAT, toList(position)); - } - if (rotation != null) { - builder.putList("rotation", NbtType.FLOAT, toList(rotation)); - } - if (scale != null) { - builder.putList("scale", NbtType.FLOAT, toList(scale)); - } - - return builder.build(); - } - - private static List toList(CustomRenderOffsets.OffsetXYZ xyz) { - return List.of(xyz.x(), xyz.y(), xyz.z()); - } - - private static NbtMap xyzToScaleList(float x, float y, float z) { - return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); - } - - // TODO is this right? - private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { - return javaItem.gatherComponents(definition.components()); - } - - @SuppressWarnings("unchecked") - private static void addItemTag(NbtMapBuilder builder, String tag) { - List tagList = (List) builder.get("item_tags"); - if (tagList == null) { - builder.putList("item_tags", NbtType.STRING, tag); - } else { - // NbtList is immutable - if (!tagList.contains(tag)) { - tagList = new ArrayList<>(tagList); - tagList.add(tag); - builder.putList("item_tags", NbtType.STRING, tagList); - } - } - } -} 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 d554a647e56..3c240ecd1c7 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 @@ -174,14 +174,11 @@ public static void populate() { boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); - // List values here is important compared to HashSet - we need to preserve the order of what's given to us - // (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom - // of the list first, then ascends. Multimap customItems = MultimapBuilder.hashKeys().arrayListValues().build(); List nonVanillaCustomItems = customItemsAllowed ? new ObjectArrayList<>() : Collections.emptyList(); if (customItemsAllowed) { - CustomItemRegistryPopulator_v2.populate(items, customItems, nonVanillaCustomItems); + CustomItemRegistryPopulator.populate(items, customItems, nonVanillaCustomItems); } // We can reduce some operations as Java information is the same across all palette versions @@ -494,7 +491,7 @@ public static void populate() { continue; } - GeyserCustomMappingData customMapping = CustomItemRegistryPopulator_v2.registerCustomItem( + GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( customItemName, javaItem, mappingItem, customItem, customProtocolId); if (customItem.bedrockOptions().creativeCategory() != BedrockCreativeTab.NONE) { @@ -597,7 +594,7 @@ public static void populate() { } int customItemId = nextFreeBedrockId++; - NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId, palette.protocolVersion); + NonVanillaItemRegistration registration = CustomItemRegistryPopulator_v1.registerCustomItem(customItem, customItemId, palette.protocolVersion); componentItemData.add(registration.componentItemData()); ItemMapping mapping = registration.mapping(); From 04fe043bbe4dc65a54923889b1f7ccc3ead24170 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 10:49:33 +0000 Subject: [PATCH 025/118] Add priority option to definitions, deprecate texture size and render offsets --- .../api/item/custom/v2/CustomItemBedrockOptions.java | 6 ++++++ .../api/item/custom/v2/CustomItemDefinition.java | 7 +++++++ .../item/custom/GeyserCustomItemBedrockOptions.java | 2 ++ .../item/custom/GeyserCustomItemDefinition.java | 11 +++++++++-- .../populator/CustomItemRegistryPopulator.java | 2 +- 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index 2f1de5673e4..51bcb9b6d4c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -79,16 +79,20 @@ public interface CustomItemBedrockOptions { /** * Gets the item's texture size. This is to resize the item if the texture is not 16x16. * + * @deprecated resizing is done with render offsets, which are deprecated in favour of attachables. * @return the item's texture size */ + @Deprecated int textureSize(); /** * Gets the item's render offsets. If it is null, the item will be rendered normally, with no offsets. * + * @deprecated attachables are now preferred instead of using render offsets. * @return the item's render offsets */ @Nullable + @Deprecated CustomRenderOffsets renderOffsets(); /** @@ -116,8 +120,10 @@ interface Builder { Builder creativeGroup(@Nullable String creativeGroup); + @Deprecated Builder textureSize(int textureSize); + @Deprecated Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); Builder tags(@Nullable Set tags); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 5a644fda92e..2057ad4876c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -75,6 +75,11 @@ public interface CustomItemDefinition { */ @NonNull List predicates(); + /** + * The priority of this definition. For all definitions for a single Java item model, definitions with a higher priority will be matched first. Defaults to 0. + */ + int priority(); + /** * The item's Bedrock options. These describe item properties that can't be described in item components, e.g. item texture size and if the item is allowed in the off-hand. */ @@ -111,6 +116,8 @@ interface Builder { Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); + Builder priority(int priority); + // TODO do we want another format for this? Builder components(@NonNull DataComponents components); diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java index ee89e20ffc7..df9f45935ff 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java @@ -79,12 +79,14 @@ public Builder creativeGroup(@Nullable String creativeGroup) { } @Override + @Deprecated public Builder textureSize(int textureSize) { this.textureSize = textureSize; return this; } @Override + @Deprecated public Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets) { this.renderOffsets = renderOffsets; return this; diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 4e5b55c3537..72085d0f246 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -37,12 +37,13 @@ import java.util.List; public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, @NonNull List predicates, - @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { + int priority, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { private final Key bedrockIdentifier; private final Key model; private final List predicates = new ArrayList<>(); + private int priority = 0; private String displayName; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); private DataComponents components = new DataComponents(new HashMap<>()); @@ -65,6 +66,12 @@ public CustomItemDefinition.Builder predicate(@NonNull CustomItemPredicate predi return this; } + @Override + public CustomItemDefinition.Builder priority(int priority) { + this.priority = priority; + return this; + } + @Override public CustomItemDefinition.Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options) { this.bedrockOptions = options.build(); @@ -79,7 +86,7 @@ public CustomItemDefinition.Builder components(@NonNull DataComponents component @Override public CustomItemDefinition build() { - return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), bedrockOptions, components); + return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), priority, bedrockOptions, components); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 93bb48fd617..8b2ad5ebee3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 5ecc61fc1d86b19a00f736233c6879afce78246f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 10:52:28 +0000 Subject: [PATCH 026/118] Implement priority option --- .../registry/populator/ItemRegistryPopulator.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 3c240ecd1c7..d580c1ecadd 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 @@ -731,8 +731,11 @@ private static void registerFurnaceMinecart(int nextFreeBedrockId, ListFirst by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first, - * and then by the amount of predicates, from most to least.

+ *
    + *
  1. First by checking their priority values.
  2. + *
  3. Then by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first.
  4. + *
  5. Lastly by the amount of predicates, from most to least.
  6. + *
* *

This comparator regards 2 custom item definitions as the same if their model differs, since it is only checking for predicates, and those * don't matter if their models are different.

@@ -746,6 +749,11 @@ public int compare(Pair firstPair, Pair other = second.predicates().stream() From 12499bfba64e4b0d351feeccb1d1829884ec5062 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 10:53:46 +0000 Subject: [PATCH 027/118] Read priority option from mappings --- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index fd4399b06db..691e7e8a795 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -140,6 +140,10 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod readPredicates(builder, node.get("predicate")); + if (node.has("priority")) { + builder.priority(node.get("priority").asInt()); + } + builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? From bca98c7d22a79dfeb9b87b41e9e83c6b5474cabc Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 10:55:01 +0000 Subject: [PATCH 028/118] Add unbreakable condition predicate --- .../geyser/api/item/custom/v2/predicate/ConditionPredicate.java | 1 + .../geysermc/geyser/translator/item/CustomItemTranslator.java | 1 + 2 files changed, 2 insertions(+) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java index 9099356157d..dc6ef518bd0 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java @@ -39,6 +39,7 @@ public ConditionPredicate(ConditionProperty property) { public enum ConditionProperty { BROKEN, DAMAGED, + UNBREAKABLE, CUSTOM_MODEL_DATA } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 539044c87ec..ffc75cd282b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -102,6 +102,7 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica return switch (condition.property()) { case BROKEN -> nextDamageWillBreak(components); case DAMAGED -> isDamaged(components); + case UNBREAKABLE -> components.getOrDefault(DataComponentType.UNBREAKABLE, false); case CUSTOM_MODEL_DATA -> getCustomBoolean(components, condition.index()); } == condition.expected(); } else if (predicate instanceof MatchPredicate match) { // TODO not much of a fan of the casts here, find a solution for the types? From 6e0e3c03dda85af04db7d6b956d9e4e5848617d6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 10:58:02 +0000 Subject: [PATCH 029/118] Use unbreakable predicate when converting v1 to v2 mappings --- .../org/geysermc/geyser/api/item/custom/CustomItemData.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index c5d96098438..34e5c53a8ae 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -32,8 +32,11 @@ import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.api.util.TriState; +import java.util.Objects; import java.util.OptionalInt; import java.util.Set; @@ -149,6 +152,9 @@ default CustomItemDefinition.Builder toDefinition(String javaItem) { if (options.damagePredicate().isPresent()) { definition.predicate(new RangeDispatchPredicate(RangeDispatchPredicate.RangeDispatchProperty.DAMAGE, options.damagePredicate().getAsInt())); } + if (options.unbreakable() != TriState.NOT_SET) { + definition.predicate(new ConditionPredicate(ConditionPredicate.ConditionProperty.UNBREAKABLE, Objects.requireNonNull(options.unbreakable().toBoolean()))); + } return definition; } From 774e55625d4dbee2487d4c5ee6c9995648daeafd Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 11:04:31 +0000 Subject: [PATCH 030/118] Remove BedrockCreativeTab in favour of CreativeCategory --- .../api/item/custom/CustomItemData.java | 4 +-- .../item/custom/v2/BedrockCreativeTab.java | 34 ------------------- .../custom/v2/CustomItemBedrockOptions.java | 5 +-- .../geyser/api/util/CreativeCategory.java | 15 ++++++++ .../GeyserCustomItemBedrockOptions.java | 8 ++--- .../mappings/versions/MappingsReader_v2.java | 4 +-- .../CustomItemRegistryPopulator.java | 6 ++-- .../populator/ItemRegistryPopulator.java | 4 +-- 8 files changed, 31 insertions(+), 49 deletions(-) delete mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 34e5c53a8ae..c4eedd32494 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -29,11 +29,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; -import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.TriState; import java.util.Objects; @@ -137,7 +137,7 @@ default CustomItemDefinition.Builder toDefinition(String javaItem) { .icon(icon()) .allowOffhand(allowOffhand()) .displayHandheld(displayHandheld()) - .creativeCategory(creativeCategory().isEmpty() ? BedrockCreativeTab.NONE : BedrockCreativeTab.values()[creativeCategory().getAsInt()]) + .creativeCategory(creativeCategory().isEmpty() ? CreativeCategory.NONE : CreativeCategory.values()[creativeCategory().getAsInt()]) .creativeGroup(creativeGroup()) .textureSize(textureSize()) .renderOffsets(renderOffsets()) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java deleted file mode 100644 index 658339ef495..00000000000 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/BedrockCreativeTab.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2024 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom.v2; - -public enum BedrockCreativeTab { - NONE, - CONSTRUCTION, - NATURE, - EQUIPMENT, - ITEMS -} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index 51bcb9b6d4c..bdf2de38316 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; +import org.geysermc.geyser.api.util.CreativeCategory; import java.util.Set; @@ -66,7 +67,7 @@ public interface CustomItemBedrockOptions { * @return the item's creative category */ @NonNull - BedrockCreativeTab creativeCategory(); + CreativeCategory creativeCategory(); /** * Gets the item's creative group. @@ -116,7 +117,7 @@ interface Builder { Builder displayHandheld(boolean displayHandheld); - Builder creativeCategory(BedrockCreativeTab creativeCategory); + Builder creativeCategory(CreativeCategory creativeCategory); Builder creativeGroup(@Nullable String creativeGroup); diff --git a/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java b/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java index 245eb9bc271..aa434fcaa06 100644 --- a/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java +++ b/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.util; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents the creative menu categories or tabs. @@ -62,4 +63,18 @@ public enum CreativeCategory { public int id() { return id; } + + /** + * Gets the creative category from its internal name. + * + * @return the creative category, or null if not found. + */ + public static @Nullable CreativeCategory fromName(String name) { + for (CreativeCategory category : values()) { + if (category.internalName.equals(name)) { + return category; + } + } + return null; + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java index df9f45935ff..9777fe3c7fb 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java @@ -28,21 +28,21 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; -import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; +import org.geysermc.geyser.api.util.CreativeCategory; import java.util.HashSet; import java.util.Objects; import java.util.Set; -public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, @NonNull BedrockCreativeTab creativeCategory, @Nullable String creativeGroup, +public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, @NonNull CreativeCategory creativeCategory, @Nullable String creativeGroup, int textureSize, @Nullable CustomRenderOffsets renderOffsets, @NonNull Set tags) implements CustomItemBedrockOptions { public static class Builder implements CustomItemBedrockOptions.Builder { private String icon = null; private boolean allowOffhand = true; private boolean displayHandheld = false; - private BedrockCreativeTab creativeCategory = BedrockCreativeTab.NONE; + private CreativeCategory creativeCategory = CreativeCategory.NONE; private String creativeGroup = null; private int textureSize = 16; private CustomRenderOffsets renderOffsets = null; @@ -67,7 +67,7 @@ public Builder displayHandheld(boolean displayHandheld) { } @Override - public Builder creativeCategory(BedrockCreativeTab creativeCategory) { + public Builder creativeCategory(CreativeCategory creativeCategory) { this.creativeCategory = creativeCategory; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 691e7e8a795..bd1cd64ceb5 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -32,7 +32,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; @@ -41,6 +40,7 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; +import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; @@ -173,7 +173,7 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node) { } if (node.has("creative_category")) { - builder.creativeCategory(BedrockCreativeTab.valueOf(node.get("creative_category").asText().toUpperCase())); + builder.creativeCategory(CreativeCategory.fromName(node.get("creative_category").asText())); } if (node.has("creative_group")) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 8b2ad5ebee3..7d9ba9b06ac 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -39,10 +39,10 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.WearableSlot; @@ -269,8 +269,8 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp .build(); itemProperties.putCompound("minecraft:icon", iconMap); - if (options.creativeCategory() != BedrockCreativeTab.NONE) { - itemProperties.putInt("creative_category", options.creativeCategory().ordinal()); + if (options.creativeCategory() != CreativeCategory.NONE) { + itemProperties.putInt("creative_category", options.creativeCategory().id()); if (options.creativeGroup() != null) { itemProperties.putString("creative_group", options.creativeGroup()); 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 d580c1ecadd..36ba15eead1 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 @@ -60,10 +60,10 @@ import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; @@ -494,7 +494,7 @@ public static void populate() { GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( customItemName, javaItem, mappingItem, customItem, customProtocolId); - if (customItem.bedrockOptions().creativeCategory() != BedrockCreativeTab.NONE) { + if (customItem.bedrockOptions().creativeCategory() != CreativeCategory.NONE) { creativeItems.add(ItemData.builder() .netId(creativeNetId.incrementAndGet()) .definition(customMapping.itemDefinition()) From 16859182120661d41301b28e40ac2e7ec9cc82d9 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 11:26:16 +0000 Subject: [PATCH 031/118] Remove adventure from API module --- api/build.gradle.kts | 5 +- .../api/item/custom/CustomItemData.java | 6 +- .../item/custom/v2/CustomItemDefinition.java | 10 +-- .../geysermc/geyser/api/util/Identifier.java | 88 +++++++++++++++++++ .../custom/GeyserCustomItemDefinition.java | 12 +-- .../loader/ProviderRegistryLoader.java | 3 +- .../mappings/versions/MappingsReader_v2.java | 14 +-- .../CustomItemRegistryPopulator.java | 3 +- .../populator/ItemRegistryPopulator.java | 9 +- 9 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/util/Identifier.java diff --git a/api/build.gradle.kts b/api/build.gradle.kts index af0973f0672..80ad07e69c6 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -9,10 +9,7 @@ dependencies { api(libs.base.api) api(libs.math) - // Adventure text serialization - api(libs.bundles.adventure) - - // TODO? can we exclude more + // TODO remove MCPL from API api(libs.mcprotocollib) { exclude("io.netty", "netty-all") exclude("net.raphimc", "MinecraftAuth") diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index c4eedd32494..7370b29bdce 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.api.item.custom; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; @@ -34,6 +33,7 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.api.util.TriState; import java.util.Objects; @@ -130,8 +130,8 @@ static CustomItemData.Builder builder() { } default CustomItemDefinition.Builder toDefinition(String javaItem) { - // TODO non vanilla, unbreakable predicate? - CustomItemDefinition.Builder definition = CustomItemDefinition.builder(Key.key(javaItem), Key.key(javaItem)) + // TODO non vanilla + CustomItemDefinition.Builder definition = CustomItemDefinition.builder(new Identifier(javaItem), new Identifier(javaItem)) .displayName(displayName()) .bedrockOptions(CustomItemBedrockOptions.builder() .icon(icon()) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 2057ad4876c..0896779cffa 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -25,10 +25,10 @@ package org.geysermc.geyser.api.item.custom.v2; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.List; @@ -43,7 +43,7 @@ public interface CustomItemDefinition { * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. If the {@code minecraft} namespace is given in the builder, the default * namespace of the implementation is used. For Geyser, the default namespace is the {@code geyser_custom} namespace. */ - @NonNull Key bedrockIdentifier(); + @NonNull Identifier bedrockIdentifier(); /** * The display name of the item. If none is set, the display name is taken from the item's Bedrock identifier. @@ -55,7 +55,7 @@ public interface CustomItemDefinition { * *

If multiple item definitions for a model are registered, then only one can have no predicate.

*/ - @NonNull Key model(); + @NonNull Identifier model(); /** * The icon used for this item. @@ -64,7 +64,7 @@ public interface CustomItemDefinition { * the namespace separator replaced with {@code .} and the path separators ({@code /}) replaced with {@code _}.

*/ default @NonNull String icon() { - return bedrockOptions().icon() == null ? bedrockIdentifier().asString().replaceAll(":", ".").replaceAll("/", "_") : bedrockOptions().icon(); + return bedrockOptions().icon() == null ? bedrockIdentifier().toString().replaceAll(":", ".").replaceAll("/", "_") : bedrockOptions().icon(); } /** @@ -104,7 +104,7 @@ public interface CustomItemDefinition { */ @NonNull DataComponents components(); - static Builder builder(Key identifier, Key itemModel) { + static Builder builder(Identifier identifier, Identifier itemModel) { return GeyserApi.api().provider(Builder.class, identifier, itemModel); } diff --git a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java new file mode 100644 index 00000000000..a767ff05af6 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.util; + +import java.util.function.Function; + +public final class Identifier { + private static final String DEFAULT_NAMESPACE = "minecraft"; + private final String namespace; + private final String path; + + public Identifier(String namespace, String path) { + this.namespace = namespace; + this.path = path; + validate(); + } + + public Identifier(String identifier) { + String[] split = identifier.split(":"); + if (split.length == 1) { + namespace = DEFAULT_NAMESPACE; + path = split[0]; + } else if (split.length == 2) { + namespace = split[0]; + path = split[1]; + } else { + throw new IllegalArgumentException("':' in identifier path: " + identifier); + } + validate(); + } + + private void validate() { + checkString(namespace, "namespace", Identifier::allowedInNamespace); + checkString(path, "path", Identifier::allowedInPath); + } + + public String namespace() { + return namespace; + } + + public String path() { + return path; + } + + @Override + public String toString() { + return namespace + ":" + path; + } + + private static void checkString(String string, String type, Function characterChecker) { + for (int i = 0; i < string.length(); i++) { + if (!characterChecker.apply(string.charAt(i))) { + throw new IllegalArgumentException("Illegal character in " + type + " " + string); + } + } + } + + private static boolean allowedInNamespace(char character) { + return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '.'; + } + + private static boolean allowedInPath(char character) { + return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '.' || character == '/'; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 72085d0f246..b6c2128b23c 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -25,32 +25,32 @@ package org.geysermc.geyser.item.custom; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -public record GeyserCustomItemDefinition(@NonNull Key bedrockIdentifier, String displayName, @NonNull Key model, @NonNull List predicates, +public record GeyserCustomItemDefinition(@NonNull Identifier bedrockIdentifier, String displayName, @NonNull Identifier model, @NonNull List predicates, int priority, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { - private final Key bedrockIdentifier; - private final Key model; + private final Identifier bedrockIdentifier; + private final Identifier model; private final List predicates = new ArrayList<>(); private int priority = 0; private String displayName; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); private DataComponents components = new DataComponents(new HashMap<>()); - public Builder(Key bedrockIdentifier, Key model) { + public Builder(Identifier bedrockIdentifier, Identifier model) { this.bedrockIdentifier = bedrockIdentifier; - this.displayName = bedrockIdentifier.asString(); + this.displayName = bedrockIdentifier.toString(); this.model = model; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index b2351037fe9..99796d0a1c2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -43,6 +43,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.pack.PathPackCodec; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.impl.camera.GeyserCameraFade; import org.geysermc.geyser.impl.camera.GeyserCameraPosition; import org.geysermc.geyser.event.GeyserEventRegistrar; @@ -90,7 +91,7 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder()); // items v2 - providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Key) args[0], (Key) args[1])); + providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Identifier) args[0], (Identifier) args[1])); providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); // cameras diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index bd1cd64ceb5..e8fc37ae6fe 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -41,9 +41,11 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.nio.file.Path; @@ -128,11 +130,11 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod throw new InvalidCustomMappingsFileException("An item entry has no model"); } - Key bedrockIdentifier = Key.key(bedrockIdentifierNode.asText()); + Identifier bedrockIdentifier = new Identifier(bedrockIdentifierNode.asText()); if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - bedrockIdentifier = Key.key(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.value()); + bedrockIdentifier = new Identifier(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.path()); } - CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, Key.key(model)); + CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, new Identifier(model)); if (node.has("display_name")) { builder.displayName(node.get("display_name").asText()); @@ -151,7 +153,7 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod if (componentsNode != null && componentsNode.isObject()) { componentsNode.fields().forEachRemaining(entry -> { try { - DataComponentReaders.readDataComponent(components, Key.key(entry.getKey()), entry.getValue()); + DataComponentReaders.readDataComponent(components, MinecraftKey.key(entry.getKey()), entry.getValue()); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error reading component " + entry.getKey() + " for item model " + modelNode.textValue(), e); } @@ -274,8 +276,8 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo throw new InvalidCustomMappingsFileException("Unknown charge type " + value); } } - case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, Key.key(value))); // TODO - case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, Key.key(value))); // TODO + case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, MinecraftKey.key(value))); // TODO + case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, MinecraftKey.key(value))); // TODO case "custom_model_data" -> { JsonNode indexNode = node.get("index"); int index = 0; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 7d9ba9b06ac..c02e88f5a71 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -43,6 +43,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.WearableSlot; @@ -136,7 +137,7 @@ private static boolean initialCheck(String identifier, CustomItemDefinition item GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier()); return false; } - Key bedrockIdentifier = item.bedrockIdentifier(); + Identifier bedrockIdentifier = item.bedrockIdentifier(); if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft"); return false; 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 36ba15eead1..f45be89bb41 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 @@ -64,6 +64,7 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; @@ -483,7 +484,7 @@ public static void populate() { for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().asString(); // TODO non vanilla stuff + String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().toString(); // TODO non vanilla stuff if (!registeredItemNames.add(customItemName)) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); @@ -505,7 +506,7 @@ public static void populate() { // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemDefinitions.put(customItem.model(), Pair.of(customItem, customMapping.itemDefinition())); + customItemDefinitions.put(identifierToKey(customItem.model()), Pair.of(customItem, customMapping.itemDefinition())); registry.put(customMapping.integerId(), customMapping.itemDefinition()); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); @@ -728,6 +729,10 @@ private static void registerFurnaceMinecart(int nextFreeBedrockId, List Date: Thu, 12 Dec 2024 11:29:58 +0000 Subject: [PATCH 032/118] Some fixes --- .../geyser/registry/loader/ProviderRegistryLoader.java | 1 - .../geyser/registry/populator/ItemRegistryPopulator.java | 1 + .../geysermc/geyser/translator/item/CustomItemTranslator.java | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 99796d0a1c2..6c4550ef564 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.registry.loader; -import net.kyori.adventure.key.Key; import org.geysermc.geyser.api.bedrock.camera.CameraFade; import org.geysermc.geyser.api.bedrock.camera.CameraPosition; import org.geysermc.geyser.api.block.custom.CustomBlockData; 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 f45be89bb41..40c3499b99c 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 @@ -729,6 +729,7 @@ private static void registerFurnaceMinecart(int nextFreeBedrockId, List customModel : customItems) { - if (customModel.first().model().equals(itemModel)) { + Key expectedModel = ItemRegistryPopulator.identifierToKey(customModel.first().model()); + if (expectedModel.equals(itemModel)) { boolean allMatch = true; for (CustomItemPredicate predicate : customModel.first().predicates()) { if (!predicateMatches(session, predicate, stackSize, components)) { From 2263cc8290d17c9a67c6c7ec288f339eb0607fb2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 11:31:49 +0000 Subject: [PATCH 033/118] Remove this --- .../CustomItemRegistryPopulator_v1.java | 591 ------------------ 1 file changed, 591 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java deleted file mode 100644 index 298fb5043ae..00000000000 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator_v1.java +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.registry.populator; - -import com.google.common.collect.Multimap; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.nbt.NbtMapBuilder; -import org.cloudburstmc.nbt.NbtType; -import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; -import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.item.custom.CustomItemData; -import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; -import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; -import org.geysermc.geyser.api.util.TriState; -import org.geysermc.geyser.item.GeyserCustomMappingData; -import org.geysermc.geyser.item.Items; -import org.geysermc.geyser.item.components.WearableSlot; -import org.geysermc.geyser.item.type.Item; -import org.geysermc.geyser.registry.mappings.MappingsConfigReader; -import org.geysermc.geyser.registry.type.GeyserMappingItem; -import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -public class CustomItemRegistryPopulator_v1 { - public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems) { - MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); - // Load custom items from mappings files - mappingsConfigReader.loadItemMappingsFromJson((key, item) -> { - //if (CustomItemRegistryPopulator.initialCheck(key, item, items)) { - //customItems.get(key).add(item); - //} // TODO - }); - - - int customItemCount = customItems.size() + nonVanillaCustomItems.size(); - if (customItemCount > 0) { - GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items"); - } - } - - public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId, int protocolVersion) { - ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); - - NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId, protocolVersion); - ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); - - return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); - } - - static boolean initialCheck(String identifier, CustomItemData item, Map mappings) { - if (!mappings.containsKey(identifier)) { - GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.name()); - return false; - } - if (!item.customItemOptions().hasCustomItemOptions()) { - GeyserImpl.getInstance().getLogger().error("The custom item " + item.name() + " has no registration types"); - } - String name = item.name(); - if (name.isEmpty()) { - GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?"); - } else if (Character.isDigit(name.charAt(0))) { - // As of 1.19.31 - GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!"); - } - return true; - } - - public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { - String customIdentifier = customItemData.identifier(); - - DataComponents components = new DataComponents(new HashMap<>()); - components.put(DataComponentType.MAX_STACK_SIZE, customItemData.stackSize()); - components.put(DataComponentType.MAX_DAMAGE, customItemData.maxDamage()); - - Item item = new Item(customIdentifier, Item.builder().components(components)); - Items.register(item, customItemData.javaId()); - - ItemMapping customItemMapping = ItemMapping.builder() - .bedrockDefinition(new SimpleItemDefinition(customIdentifier, customItemId, true)) - .bedrockData(0) - .bedrockBlockDefinition(null) - .toolType(customItemData.toolType()) - .translationString(customItemData.translationString()) - .customItemDefinitions(null) - .javaItem(item) - .build(); - - NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, - customItemData.isHat(), customItemData.displayHandheld(), protocolVersion); - ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build()); - - return new NonVanillaItemRegistration(componentItemData, item, customItemMapping); - } - - private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping, - String customItemName, int customItemId, int protocolVersion) { - NbtMapBuilder builder = NbtMap.builder(); - builder.putString("name", customItemName) - .putInt("id", customItemId); - - NbtMapBuilder itemProperties = NbtMap.builder(); - NbtMapBuilder componentBuilder = NbtMap.builder(); - - setupBasicItemInfo(javaItem.defaultMaxDamage(), javaItem.defaultMaxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion); - - boolean canDestroyInCreative = true; - if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, javaItem.defaultAttackDamage()); - } - itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - - if (mapping.getArmorType() != null) { - computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder); - } - - if (mapping.getFirstBlockRuntimeId() != null) { - computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder); - } - - if (mapping.isEdible()) { - computeConsumableProperties(itemProperties, componentBuilder, 1, false); - } - - if (mapping.isEntityPlacer()) { - computeEntityPlacerProperties(componentBuilder); - } - - switch (mapping.getBedrockIdentifier()) { - case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); - case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, mapping.getBedrockIdentifier(), protocolVersion); - case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> computeConsumableProperties(itemProperties, componentBuilder, 2, true); - case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> - computeThrowableProperties(componentBuilder); - } - - // Hardcoded on Java, and should extend to the custom item - boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) - || javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD) - || javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD) - || javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD) - ); - computeRenderOffsets(isHat, customItemData, componentBuilder); - - componentBuilder.putCompound("item_properties", itemProperties.build()); - builder.putCompound("components", componentBuilder.build()); - - return builder; - } - - private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName, - int customItemId, boolean isHat, boolean displayHandheld, int protocolVersion) { - NbtMapBuilder builder = NbtMap.builder(); - builder.putString("name", customItemName) - .putInt("id", customItemId); - - NbtMapBuilder itemProperties = NbtMap.builder(); - NbtMapBuilder componentBuilder = NbtMap.builder(); - - setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder, protocolVersion); - - boolean canDestroyInCreative = true; - if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage()); - } - itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - - String armorType = customItemData.armorType(); - if (armorType != null) { - computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder); - } - - if (customItemData.isEdible()) { - computeConsumableProperties(itemProperties, componentBuilder, 1, customItemData.canAlwaysEat()); - } - - if (customItemData.isChargeable()) { - String tooltype = customItemData.toolType(); - if (tooltype == null) { - throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!"); - } - computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion); - } - - computeRenderOffsets(isHat, customItemData, componentBuilder); - - if (customItemData.isFoil()) { - itemProperties.putBoolean("foil", true); - } - - String block = customItemData.block(); - if (block != null) { - computeBlockItemProperties(block, componentBuilder); - } - - componentBuilder.putCompound("item_properties", itemProperties.build()); - builder.putCompound("components", componentBuilder.build()); - - return builder; - } - - private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) { - NbtMap iconMap = NbtMap.builder() - .putCompound("textures", NbtMap.builder() - .putString("default", customItemData.icon()) - .build()) - .build(); - itemProperties.putCompound("minecraft:icon", iconMap); - - if (customItemData.creativeCategory().isPresent()) { - itemProperties.putInt("creative_category", customItemData.creativeCategory().getAsInt()); - - if (customItemData.creativeGroup() != null) { - itemProperties.putString("creative_group", customItemData.creativeGroup()); - } - } - - componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build()); - - // Add a Geyser tag to the item, allowing Molang queries - addItemTag(componentBuilder, "geyser:is_custom"); - - // Add other defined tags to the item - Set tags = customItemData.tags(); - for (String tag : tags) { - if (tag != null && !tag.isBlank()) { - addItemTag(componentBuilder, tag); - } - } - - itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand()); - itemProperties.putBoolean("hand_equipped", displayHandheld); - itemProperties.putInt("max_stack_size", stackSize); - // Ignore durability if the item's predicate requires that it be unbreakable - if (maxDamage > 0 && customItemData.customItemOptions().unbreakable() != TriState.TRUE) { - componentBuilder.putCompound("minecraft:durability", NbtMap.builder() - .putCompound("damage_chance", NbtMap.builder() - .putInt("max", 1) - .putInt("min", 1) - .build()) - .putInt("max_durability", maxDamage) - .build()); - itemProperties.putBoolean("use_duration", true); - } - } - - /** - * @return can destroy in creative - */ - private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) { - boolean canDestroyInCreative = true; - float miningSpeed = 1.0f; - - // This means client side the tool can never destroy a block - // This works because the molang '1' for tags will be true for all blocks and the speed will be 0 - // We want this since we calculate break speed server side in BedrockActionTranslator - List speed = new ArrayList<>(List.of( - NbtMap.builder() - .putCompound("block", NbtMap.builder() - .putString("tags", "1") - .build()) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putInt("speed", 0) - .build() - )); - - componentBuilder.putCompound("minecraft:digger", - NbtMap.builder() - .putList("destroy_speeds", NbtType.COMPOUND, speed) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putBoolean("use_efficiency", true) - .build() - ); - - if (toolType.equals("sword")) { - miningSpeed = 1.5f; - canDestroyInCreative = false; - } - - itemProperties.putBoolean("hand_equipped", true); - itemProperties.putFloat("mining_speed", miningSpeed); - - // This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil - itemProperties.putInt("enchantable_value", 1); - itemProperties.putString("enchantable_slot", toolType); - - // Adds a "attack damage" indicator. Purely visual! - if (attackDamage > 0) { - itemProperties.putInt("damage", attackDamage); - } - - return canDestroyInCreative; - } - - private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - switch (armorType) { - case "boots" -> { - componentBuilder.putString("minecraft:render_offsets", "boots"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - - itemProperties.putString("enchantable_slot", "armor_feet"); - itemProperties.putInt("enchantable_value", 15); - } - case "chestplate" -> { - componentBuilder.putString("minecraft:render_offsets", "chestplates"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - - itemProperties.putString("enchantable_slot", "armor_torso"); - itemProperties.putInt("enchantable_value", 15); - } - case "leggings" -> { - componentBuilder.putString("minecraft:render_offsets", "leggings"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - - itemProperties.putString("enchantable_slot", "armor_legs"); - itemProperties.putInt("enchantable_value", 15); - } - case "helmet" -> { - componentBuilder.putString("minecraft:render_offsets", "helmets"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - - itemProperties.putString("enchantable_slot", "armor_head"); - itemProperties.putInt("enchantable_value", 15); - } - } - } - - private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) { - // carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here - // however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot - // it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome - - // all block items registered should be given this component to prevent double placement - componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); - } - - private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping, int protocolVersion) { - // setting high use_duration prevents the consume animation from playing - itemProperties.putInt("use_duration", Integer.MAX_VALUE); - // display item as tool (mainly for crossbow and bow) - itemProperties.putBoolean("hand_equipped", true); - // Make bows, tridents, and crossbows enchantable - itemProperties.putInt("enchantable_value", 1); - - componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() - .putFloat("use_duration", 100F) - .putFloat("movement_modifier", 0.35F) - .build()); - - switch (mapping) { - case "minecraft:bow" -> { - itemProperties.putString("enchantable_slot", "bow"); - itemProperties.putInt("frame_count", 3); - - componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() - .putList("ammunition", NbtType.COMPOUND, List.of( - NbtMap.builder() - .putCompound("item", NbtMap.builder() - .putString("name", "minecraft:arrow") - .build()) - .putBoolean("use_offhand", true) - .putBoolean("search_inventory", true) - .build() - )) - .putFloat("max_draw_duration", 0f) - .putBoolean("charge_on_draw", true) - .putBoolean("scale_power_by_draw_duration", true) - .build()); - componentBuilder.putInt("minecraft:use_duration", 999); - } - case "minecraft:trident" -> { - itemProperties.putString("enchantable_slot", "trident"); - componentBuilder.putInt("minecraft:use_duration", 999); - } - case "minecraft:crossbow" -> { - itemProperties.putString("enchantable_slot", "crossbow"); - itemProperties.putInt("frame_count", 10); - - componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() - .putList("ammunition", NbtType.COMPOUND, List.of( - NbtMap.builder() - .putCompound("item", NbtMap.builder() - .putString("name", "minecraft:arrow") - .build()) - .putBoolean("use_offhand", true) - .putBoolean("search_inventory", true) - .build() - )) - .putFloat("max_draw_duration", 1f) - .putBoolean("charge_on_draw", true) - .putBoolean("scale_power_by_draw_duration", true) - .build()); - componentBuilder.putInt("minecraft:use_duration", 999); - } - } - } - - private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) { - // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks - itemProperties.putInt("use_duration", 32); - // this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds - // note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively - itemProperties.putInt("use_animation", useAnimation); - // this component is required to allow the eat animation to play - componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); - } - - private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { - // all items registered that place entities should be given this component to prevent double placement - // it is okay that the entity here does not match the actual one since we control what entity actually spawns - componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); - } - - private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { - // allows item to be thrown when holding down right click (individual presses are required w/o this component) - componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); - // this must be set to something for the swing animation to play - // it is okay that the projectile here does not match the actual one since we control what entity actually spawns - componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build()); - } - - private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) { - if (isHat) { - componentBuilder.remove("minecraft:render_offsets"); - componentBuilder.putString("minecraft:render_offsets", "helmets"); - - componentBuilder.remove("minecraft:wearable"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - } - - CustomRenderOffsets renderOffsets = customItemData.renderOffsets(); - if (renderOffsets != null) { - componentBuilder.remove("minecraft:render_offsets"); - componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); - } else if (customItemData.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { - float scale1 = (float) (0.075 / (customItemData.textureSize() / 16f)); - float scale2 = (float) (0.125 / (customItemData.textureSize() / 16f)); - float scale3 = (float) (0.075 / (customItemData.textureSize() / 16f * 2.4f)); - - componentBuilder.putCompound("minecraft:render_offsets", - NbtMap.builder().putCompound("main_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) - .putCompound("off_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); - } - } - - private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) { - NbtMapBuilder builder = NbtMap.builder(); - - CustomRenderOffsets.Hand mainHand = renderOffsets.mainHand(); - if (mainHand != null) { - NbtMap nbt = toNbtMap(mainHand); - if (nbt != null) { - builder.putCompound("main_hand", nbt); - } - } - CustomRenderOffsets.Hand offhand = renderOffsets.offhand(); - if (offhand != null) { - NbtMap nbt = toNbtMap(offhand); - if (nbt != null) { - builder.putCompound("off_hand", nbt); - } - } - - return builder.build(); - } - - private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.Hand hand) { - NbtMap firstPerson = toNbtMap(hand.firstPerson()); - NbtMap thirdPerson = toNbtMap(hand.thirdPerson()); - - if (firstPerson == null && thirdPerson == null) { - return null; - } - - NbtMapBuilder builder = NbtMap.builder(); - if (firstPerson != null) { - builder.putCompound("first_person", firstPerson); - } - if (thirdPerson != null) { - builder.putCompound("third_person", thirdPerson); - } - - return builder.build(); - } - - private static @Nullable NbtMap toNbtMap(CustomRenderOffsets.@Nullable Offset offset) { - if (offset == null) { - return null; - } - - CustomRenderOffsets.OffsetXYZ position = offset.position(); - CustomRenderOffsets.OffsetXYZ rotation = offset.rotation(); - CustomRenderOffsets.OffsetXYZ scale = offset.scale(); - - if (position == null && rotation == null && scale == null) { - return null; - } - - NbtMapBuilder builder = NbtMap.builder(); - if (position != null) { - builder.putList("position", NbtType.FLOAT, toList(position)); - } - if (rotation != null) { - builder.putList("rotation", NbtType.FLOAT, toList(rotation)); - } - if (scale != null) { - builder.putList("scale", NbtType.FLOAT, toList(scale)); - } - - return builder.build(); - } - - private static List toList(CustomRenderOffsets.OffsetXYZ xyz) { - return List.of(xyz.x(), xyz.y(), xyz.z()); - } - - @SuppressWarnings("unchecked") - private static void addItemTag(NbtMapBuilder builder, String tag) { - List tagList = (List) builder.get("item_tags"); - if (tagList == null) { - builder.putList("item_tags", NbtType.STRING, tag); - } else { - // NbtList is immutable - if (!tagList.contains(tag)) { - tagList = new ArrayList<>(tagList); - tagList.add(tag); - builder.putList("item_tags", NbtType.STRING, tagList); - } - } - } - - private static NbtMap xyzToScaleList(float x, float y, float z) { - return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); - } -} From 3fac2cd8f7b6d4b71e0c87d6a29be8a5bbf801e4 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 11:47:41 +0000 Subject: [PATCH 034/118] Reimplement unbreakable predicate check --- .../populator/CustomItemRegistryPopulator.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index c02e88f5a71..2dd71645835 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -41,6 +41,7 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; @@ -300,7 +301,7 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's itemProperties.putInt("max_stack_size", stackSize); - if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here? + if (maxDamage > 0 && !isUnbreakableItem(definition)) { componentBuilder.putCompound("minecraft:durability", NbtMap.builder() .putCompound("damage_chance", NbtMap.builder() .putInt("max", 1) @@ -614,6 +615,15 @@ private static NbtMap xyzToScaleList(float x, float y, float z) { return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build(); } + private static boolean isUnbreakableItem(CustomItemDefinition definition) { + for (CustomItemPredicate predicate : definition.predicates()) { + if (predicate instanceof ConditionPredicate condition && condition.property() == ConditionPredicate.ConditionProperty.UNBREAKABLE && condition.expected()) { + return true; + } + } + return false; + } + // TODO is this right? private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { return javaItem.gatherComponents(definition.components()); From 361df163b802f8f2e0dd458b359d6120e63d171f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 11:59:53 +0000 Subject: [PATCH 035/118] Fix build --- .../geyser/registry/populator/ItemRegistryPopulator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 40c3499b99c..01892eaa38a 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 @@ -585,6 +585,7 @@ public static void populate() { registerFurnaceMinecart(nextFreeBedrockId++, componentItemData, palette.protocolVersion); // Register any completely custom items given to us + // TODO broken as of right now IntSet registeredJavaIds = new IntOpenHashSet(); // Used to check for duplicate item java ids for (NonVanillaCustomItemData customItem : nonVanillaCustomItems) { if (!registeredJavaIds.add(customItem.javaId())) { @@ -595,7 +596,7 @@ public static void populate() { } int customItemId = nextFreeBedrockId++; - NonVanillaItemRegistration registration = CustomItemRegistryPopulator_v1.registerCustomItem(customItem, customItemId, palette.protocolVersion); + NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId, palette.protocolVersion); componentItemData.add(registration.componentItemData()); ItemMapping mapping = registration.mapping(); From b80c054f53069a54b204144175fd771e083deecc Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 13:57:32 +0000 Subject: [PATCH 036/118] Fix charge type predicate --- .../geysermc/geyser/translator/item/CustomItemTranslator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 8cdca4fc425..b25fe2eed82 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -75,7 +75,6 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, } Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); - System.out.println(itemModel + " is the model!"); Collection> customItems = allCustomItems.get(itemModel); if (customItems.isEmpty()) { return null; @@ -111,7 +110,7 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica if (match.property() == MatchPredicateProperty.CHARGE_TYPE) { ChargeType expected = (ChargeType) match.data(); List charged = components.get(DataComponentType.CHARGED_PROJECTILES); - if (charged == null) { + if (charged == null || charged.isEmpty()) { return expected == ChargeType.NONE; } else if (expected == ChargeType.ROCKET) { for (ItemStack projectile : charged) { From eb0b40c8b193d12f7d9ced111d76c57db56d06ed Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 14:30:28 +0000 Subject: [PATCH 037/118] I'm very confused at why this doesn't work --- .../geyser/item/GeyserCustomMappingData.java | 3 ++- .../CustomItemRegistryPopulator.java | 2 +- .../populator/ItemRegistryPopulator.java | 21 ++++++++++++------- .../geyser/registry/type/ItemMapping.java | 7 +++---- .../translator/item/CustomItemTranslator.java | 16 +++++++------- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java index d71f2e54863..26a15733eee 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java @@ -27,6 +27,7 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -public record GeyserCustomMappingData(ComponentItemData componentItemData, ItemDefinition itemDefinition, String stringId, int integerId) { +public record GeyserCustomMappingData(CustomItemDefinition definition, ComponentItemData componentItemData, ItemDefinition itemDefinition, String stringId, int integerId) { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 2dd71645835..1fd9450c80e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -129,7 +129,7 @@ public static GeyserCustomMappingData registerCustomItem(String customItemName, NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId); ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); - return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); + return new GeyserCustomMappingData(customItemDefinition, componentItemData, itemDefinition, customItemName, bedrockId); } private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap registered, Map mappings) { 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 01892eaa38a..ff2cb5610f9 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 @@ -29,7 +29,6 @@ import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.SortedSetMultimap; -import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; @@ -476,7 +475,7 @@ public static void populate() { } // Add the custom item properties, if applicable - SortedSetMultimap> customItemDefinitions; + SortedSetMultimap customItemDefinitions; Collection customItemsToLoad = customItems.get(javaItem.javaIdentifier()); if (customItemsAllowed && !customItemsToLoad.isEmpty()) { customItemDefinitions = MultimapBuilder.hashKeys(customItemsToLoad.size()).treeSetValues(new CustomItemDefinitionComparator()).build(); @@ -506,7 +505,15 @@ public static void populate() { // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemDefinitions.put(identifierToKey(customItem.model()), Pair.of(customItem, customMapping.itemDefinition())); + System.out.println("putting " + customItem.bedrockIdentifier() + " for model " + identifierToKey(customItem.model())); + for (GeyserCustomMappingData existing : customItemDefinitions.get(identifierToKey(customItem.model()))) { + System.out.println("existing - " + existing); + System.out.println("trying to add - " + customMapping); + System.out.println("trying to add " + (customMapping.equals(existing) ? "DOES" : "does NOT") + " equal existing"); + } + System.out.println("map size: " + customItemDefinitions.size()); + customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); + System.out.println("map size after: " + customItemDefinitions.size()); registry.put(customMapping.integerId(), customMapping.itemDefinition()); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); @@ -747,12 +754,12 @@ public static Key identifierToKey(Identifier identifier) { *

This comparator regards 2 custom item definitions as the same if their model differs, since it is only checking for predicates, and those * don't matter if their models are different.

*/ - private static class CustomItemDefinitionComparator implements Comparator> { + private static class CustomItemDefinitionComparator implements Comparator { @Override - public int compare(Pair firstPair, Pair secondPair) { - CustomItemDefinition first = firstPair.first(); - CustomItemDefinition second = secondPair.first(); + public int compare(GeyserCustomMappingData firstData, GeyserCustomMappingData secondData) { + CustomItemDefinition first = firstData.definition(); + CustomItemDefinition second = secondData.definition(); if (first.equals(second) || !first.model().equals(second.model())) { return 0; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java index aaa7e713360..e8713bfeeae 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.registry.type; import com.google.common.collect.SortedSetMultimap; -import it.unimi.dsi.fastutil.Pair; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -35,7 +34,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.Item; @@ -70,9 +69,9 @@ public class ItemMapping { String translationString; /** - * A map of item models and their custom item definitions, sorted from most predicates to least, which is important when matching predicates. + * A map of item models and all of their custom items, sorted from most definition predicates to least, which is important when matching predicates. */ - SortedSetMultimap> customItemDefinitions; + SortedSetMultimap customItemDefinitions; @NonNull Item javaItem; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index b25fe2eed82..ff586fab4e4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -29,7 +29,6 @@ import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; -import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; @@ -37,6 +36,7 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; +import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; @@ -48,7 +48,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.CustomModelData; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import it.unimi.dsi.fastutil.Pair; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.geysermc.geyser.registry.type.ItemMapping; @@ -69,29 +68,30 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, return null; } - Multimap> allCustomItems = mapping.getCustomItemDefinitions(); + Multimap allCustomItems = mapping.getCustomItemDefinitions(); if (allCustomItems == null) { return null; } Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); - Collection> customItems = allCustomItems.get(itemModel); + Collection customItems = allCustomItems.get(itemModel); if (customItems.isEmpty()) { return null; } - for (Pair customModel : customItems) { - Key expectedModel = ItemRegistryPopulator.identifierToKey(customModel.first().model()); + for (GeyserCustomMappingData customMapping : customItems) { + Key expectedModel = ItemRegistryPopulator.identifierToKey(customMapping.definition().model()); if (expectedModel.equals(itemModel)) { boolean allMatch = true; - for (CustomItemPredicate predicate : customModel.first().predicates()) { + for (CustomItemPredicate predicate : customMapping.definition().predicates()) { if (!predicateMatches(session, predicate, stackSize, components)) { allMatch = false; break; } } if (allMatch) { - return customModel.second(); + System.out.println("using " + customMapping.definition().bedrockIdentifier()); + return customMapping.itemDefinition(); } } } From a4c17daf242d56cd9eaa0119e4c3a85c60bcaf3c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 15:04:31 +0000 Subject: [PATCH 038/118] Fix adding multiple definitions for same item model and sorting of predicates --- .../geysermc/geyser/api/util/Identifier.java | 15 +++++++++++++ .../populator/ItemRegistryPopulator.java | 22 ++++++++----------- .../translator/item/CustomItemTranslator.java | 1 - 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java index a767ff05af6..0b19bc23858 100644 --- a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java +++ b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.api.util; +import java.util.Objects; import java.util.function.Function; public final class Identifier { @@ -65,6 +66,20 @@ public String path() { return path; } + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Identifier other = (Identifier) o; + return Objects.equals(namespace, other.namespace) && Objects.equals(path, other.path); + } + + @Override + public int hashCode() { + return Objects.hash(namespace, path); + } + @Override public String toString() { return namespace + ":" + path; 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 ff2cb5610f9..b5a15ed1994 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 @@ -505,15 +505,7 @@ public static void populate() { // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - System.out.println("putting " + customItem.bedrockIdentifier() + " for model " + identifierToKey(customItem.model())); - for (GeyserCustomMappingData existing : customItemDefinitions.get(identifierToKey(customItem.model()))) { - System.out.println("existing - " + existing); - System.out.println("trying to add - " + customMapping); - System.out.println("trying to add " + (customMapping.equals(existing) ? "DOES" : "does NOT") + " equal existing"); - } - System.out.println("map size: " + customItemDefinitions.size()); customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); - System.out.println("map size after: " + customItemDefinitions.size()); registry.put(customMapping.integerId(), customMapping.itemDefinition()); customIdMappings.put(customMapping.integerId(), customMapping.stringId()); @@ -746,7 +738,7 @@ public static Key identifierToKey(Identifier identifier) { * Compares custom item definitions based on their predicates: * *
    - *
  1. First by checking their priority values.
  2. + *
  3. First by checking their priority values, higher priority values going first.
  4. *
  5. Then by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first.
  6. *
  7. Lastly by the amount of predicates, from most to least.
  8. *
@@ -765,7 +757,11 @@ public int compare(GeyserCustomMappingData firstData, GeyserCustomMappingData se } if (first.priority() != second.priority()) { - return first.priority() - second.priority(); + return second.priority() - first.priority(); + } + + if (first.predicates().isEmpty() || second.predicates().isEmpty()) { + return second.predicates().size() - first.predicates().size(); // No need checking for range predicates if either one has no predicates } for (CustomItemPredicate predicate : first.predicates()) { @@ -775,11 +771,11 @@ public int compare(GeyserCustomMappingData firstData, GeyserCustomMappingData se .map(otherPredicate -> (RangeDispatchPredicate) otherPredicate) .findFirst(); if (other.isPresent()) { - return (int) (rangeDispatch.threshold() - other.orElseThrow().threshold()); + return (int) (other.orElseThrow().threshold() - rangeDispatch.threshold()); } - } + } // TODO not a fan of how this looks } - return first.predicates().size() - second.predicates().size(); + return second.predicates().size() - first.predicates().size(); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index ff586fab4e4..2169d595935 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -90,7 +90,6 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, } } if (allMatch) { - System.out.println("using " + customMapping.definition().bedrockIdentifier()); return customMapping.itemDefinition(); } } From a80e90d64857a6e98303bf870d79ebf6ab428eb0 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 12 Dec 2024 16:21:42 +0000 Subject: [PATCH 039/118] Fix adding multiple definitions for same item model again --- .../geyser/registry/populator/ItemRegistryPopulator.java | 5 +++++ .../geyser/translator/item/CustomItemTranslator.java | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) 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 b5a15ed1994..b73f66b7354 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 @@ -775,6 +775,11 @@ public int compare(GeyserCustomMappingData firstData, GeyserCustomMappingData se } } // TODO not a fan of how this looks } + + if (first.predicates().size() == second.predicates().size()) { + return -1; // If there's no preferred range predicate order and they both have the same amount of predicates, prefer the first + } + return second.predicates().size() - first.predicates().size(); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 2169d595935..09884f00fcb 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -59,7 +59,6 @@ /** * This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator. */ -@Slf4j public final class CustomItemTranslator { @Nullable From e7d7a9d09bede3b0979b43fdafd2fb30c8c2093e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 14 Dec 2024 13:14:45 +0000 Subject: [PATCH 040/118] Improve mapping custom items --- .../translator/item/CustomItemTranslator.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 09884f00fcb..a3bcf578c8d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.translator.item; import com.google.common.collect.Multimap; -import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; @@ -39,7 +38,6 @@ import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.JavaDimension; -import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.registry.RegistryEntryData; import org.geysermc.geyser.util.MinecraftKey; @@ -79,19 +77,16 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, } for (GeyserCustomMappingData customMapping : customItems) { - Key expectedModel = ItemRegistryPopulator.identifierToKey(customMapping.definition().model()); - if (expectedModel.equals(itemModel)) { - boolean allMatch = true; - for (CustomItemPredicate predicate : customMapping.definition().predicates()) { - if (!predicateMatches(session, predicate, stackSize, components)) { - allMatch = false; - break; - } - } - if (allMatch) { - return customMapping.itemDefinition(); + boolean allMatch = true; + for (CustomItemPredicate predicate : customMapping.definition().predicates()) { + if (!predicateMatches(session, predicate, stackSize, components)) { + allMatch = false; + break; } } + if (allMatch) { + return customMapping.itemDefinition(); + } } return null; } From 12f2e1a2327461fb1668382af7762eb1bd75332c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 15:20:23 +0000 Subject: [PATCH 041/118] Improve the reading and error handling of item mappings --- .../components/DataComponentReaders.java | 2 +- .../mappings/versions/MappingsReader.java | 36 +++- .../mappings/versions/MappingsReader_v1.java | 2 +- .../mappings/versions/MappingsReader_v2.java | 197 +++++++----------- 4 files changed, 110 insertions(+), 127 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 7485217edda..8422631940d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -47,7 +47,7 @@ public class DataComponentReaders { public static void readDataComponent(DataComponents components, Key key, @NonNull JsonNode node) throws InvalidCustomMappingsFileException { DataComponentReader reader = READERS.get(key); if (reader == null) { - throw new InvalidCustomMappingsFileException("Unknown data component " + key); + throw new InvalidCustomMappingsFileException("Unknown data component"); } reader.read(components, node); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java index 75827f04a0e..85915ae476c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java @@ -34,6 +34,8 @@ import java.nio.file.Path; import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; public abstract class MappingsReader { public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); @@ -42,7 +44,33 @@ public abstract class MappingsReader { public abstract CustomItemDefinition readItemMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; - protected @Nullable CustomRenderOffsets fromJsonNode(JsonNode node) { + protected static T readOrThrow(JsonNode node, String name, Function converter, String exceptionMessage) throws InvalidCustomMappingsFileException { + JsonNode object = node.get(name); + if (object == null) { + throw new InvalidCustomMappingsFileException(exceptionMessage); + } + return converter.apply(object); + } + + protected static T readOrDefault(JsonNode node, String name, Function converter, T defaultValue) { + JsonNode object = node.get(name); + if (object == null) { + return defaultValue; + } + return converter.apply(object); + } + + protected static void readTextIfPresent(JsonNode node, String name, Consumer consumer) { + readIfPresent(node, name, consumer, JsonNode::asText); + } + + protected static void readIfPresent(JsonNode node, String name, Consumer consumer, Function converter) { + if (node.has(name)) { + consumer.accept(converter.apply(node.get(name))); + } + } + + protected static @Nullable CustomRenderOffsets renderOffsetsFromJsonNode(JsonNode node) { if (node == null || !node.isObject()) { return null; } @@ -53,7 +81,7 @@ public abstract class MappingsReader { ); } - protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonNode node, String hand) { + protected static CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonNode node, String hand) { JsonNode tmpNode = node.get(hand); if (tmpNode == null || !tmpNode.isObject()) { return null; @@ -65,7 +93,7 @@ public abstract class MappingsReader { ); } - protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonNode node, String perspective) { + protected static CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonNode node, String perspective) { JsonNode tmpNode = node.get(perspective); if (tmpNode == null || !tmpNode.isObject()) { return null; @@ -78,7 +106,7 @@ public abstract class MappingsReader { ); } - protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) { + protected static CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) { JsonNode tmpNode = node.get(offsetType); if (tmpNode == null || !tmpNode.isObject()) { return null; 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 b78dc40327f..00313e299d4 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 @@ -206,7 +206,7 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod if (node.has("render_offsets")) { JsonNode tmpNode = node.get("render_offsets"); - customItemData.renderOffsets(fromJsonNode(tmpNode)); + customItemData.renderOffsets(renderOffsetsFromJsonNode(tmpNode)); } if (node.get("tags") instanceof ArrayNode tags) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index e8fc37ae6fe..044ce36f192 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -50,6 +50,8 @@ import java.nio.file.Path; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; @@ -61,45 +63,19 @@ public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + // TODO - do we want to continue allowing type conversions, or do we want to enforce strict typing in JSON mappings? + // E.g., do we want to allow reading "210" as 210 and reading 1 as true, or do we throw exceptions in that case? JsonNode itemModels = mappingsRoot.get("items"); if (itemModels != null && itemModels.isObject()) { itemModels.fields().forEachRemaining(entry -> { if (entry.getValue().isArray()) { entry.getValue().forEach(data -> { - // TODO better error handling - JsonNode type = data.get("type"); - if (type == null || !type.isTextual()) { - GeyserImpl.getInstance().getLogger().error("Error reading type in custom mappings file: " + file.toString()); - } else if (type.asText().equals("group")) { - JsonNode modelNode = data.get("model"); - if (modelNode == null || !modelNode.isTextual()) { - GeyserImpl.getInstance().getLogger().error("Error reading model in custom mappings file: " + file.toString()); - } else { - String model = modelNode.asText(); - JsonNode definitions = data.get("definitions"); - if (definitions == null || !definitions.isArray()) { - GeyserImpl.getInstance().getLogger().error("Error reading item definitions in custom mappings file: " + file.toString()); - } else { - definitions.forEach(definition -> { - try { - CustomItemDefinition customItemDefinition = readItemMappingEntry(model, definition); - consumer.accept(entry.getKey(), customItemDefinition); - } catch (InvalidCustomMappingsFileException e) { - GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); - } - }); - } - } - } else if (type.asText().equals("definition")) { - try { - CustomItemDefinition customItemDefinition = readItemMappingEntry(null, data); - consumer.accept(entry.getKey(), customItemDefinition); - } catch (InvalidCustomMappingsFileException e) { - GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); - } - } else { - GeyserImpl.getInstance().getLogger().error("Unknown type " + type.asText() + " in custom mappings file: " + file.toString()); + try { + readItemDefinitionEntry(data, entry.getKey(), null, consumer); + } catch (InvalidCustomMappingsFileException exception) { + GeyserImpl.getInstance().getLogger().error( + "Error reading definition for item " + entry.getKey() + " in custom mappings file: " + file.toString(), exception); } }); } @@ -112,22 +88,45 @@ public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer definitionConsumer) throws InvalidCustomMappingsFileException { + String type = readOrThrow(data, "type", JsonNode::asText, "Missing type key in definition"); + if (type.equals("group")) { + // Read model of group if it's present, or default to the model of the parent group, if that's present + // If the parent group model is not present (or there is no parent group), and this group also doesn't have a model, then it is expected the definitions supply their model themselves + String groupModel = readOrDefault(data, "model", JsonNode::asText, model); + JsonNode definitions = data.get("definitions"); + if (definitions == null || !definitions.isArray()) { + throw new InvalidCustomMappingsFileException("An item entry group has no definitions key, or it wasn't an array"); + } else { + for (JsonNode definition : definitions) { + readItemDefinitionEntry(definition, itemIdentifier, groupModel, definitionConsumer); + } + } + } else if (type.equals("definition")) { + CustomItemDefinition customItemDefinition = readItemMappingEntry(model, data); + definitionConsumer.accept(itemIdentifier, customItemDefinition); + } else { + throw new InvalidCustomMappingsFileException("Unknown definition type " + type); + } + } + @Override - public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { + public CustomItemDefinition readItemMappingEntry(String itemModel, JsonNode node) throws InvalidCustomMappingsFileException { if (node == null || !node.isObject()) { - throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); + throw new InvalidCustomMappingsFileException("Invalid item definition entry"); } JsonNode bedrockIdentifierNode = node.get("bedrock_identifier"); JsonNode modelNode = node.get("model"); - String model = identifier != null || modelNode == null || !modelNode.isTextual() ? identifier : modelNode.asText(); + String model = itemModel != null || modelNode == null || !modelNode.isTextual() ? itemModel : modelNode.asText(); if (bedrockIdentifierNode == null || !bedrockIdentifierNode.isTextual() || bedrockIdentifierNode.asText().isEmpty()) { - throw new InvalidCustomMappingsFileException("An item entry has no bedrock identifier"); + throw new InvalidCustomMappingsFileException("An item definition has no bedrock identifier"); } if (model == null) { - throw new InvalidCustomMappingsFileException("An item entry has no model"); + throw new InvalidCustomMappingsFileException("An item definition has no model"); } Identifier bedrockIdentifier = new Identifier(bedrockIdentifierNode.asText()); @@ -136,30 +135,31 @@ public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode nod } CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, new Identifier(model)); - if (node.has("display_name")) { - builder.displayName(node.get("display_name").asText()); - } + // We now know the Bedrock identifier, put it in the exception message so that the error can be easily located in the JSON file + try { + readTextIfPresent(node, "display_name", builder::displayName); + readIfPresent(node, "priority", builder::priority, JsonNode::asInt); - readPredicates(builder, node.get("predicate")); - - if (node.has("priority")) { - builder.priority(node.get("priority").asInt()); - } + readPredicates(builder, node.get("predicate")); - builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); + builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); - DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? - JsonNode componentsNode = node.get("components"); - if (componentsNode != null && componentsNode.isObject()) { - componentsNode.fields().forEachRemaining(entry -> { - try { - DataComponentReaders.readDataComponent(components, MinecraftKey.key(entry.getKey()), entry.getValue()); - } catch (InvalidCustomMappingsFileException e) { - GeyserImpl.getInstance().getLogger().error("Error reading component " + entry.getKey() + " for item model " + modelNode.textValue(), e); + JsonNode componentsNode = node.get("components"); + if (componentsNode != null && componentsNode.isObject()) { + DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? + for (Iterator> iterator = componentsNode.fields(); iterator.hasNext();) { + Map.Entry entry = iterator.next(); + try { + DataComponentReaders.readDataComponent(components, MinecraftKey.key(entry.getKey()), entry.getValue()); + } catch (InvalidCustomMappingsFileException exception) { + throw new InvalidCustomMappingsFileException("While reading data component " + entry.getKey() + ": " + exception.getMessage()); + } } - }); + builder.components(components); + } + } catch (InvalidCustomMappingsFileException exception) { + throw new InvalidCustomMappingsFileException("While reading item definition (bedrock identifier=" + bedrockIdentifier + "): " + exception.getMessage()); } - builder.components(components); return builder.build(); } @@ -170,35 +170,13 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node) { return builder; } - if (node.has("icon")) { - builder.icon(node.get("icon").asText()); - } - - if (node.has("creative_category")) { - builder.creativeCategory(CreativeCategory.fromName(node.get("creative_category").asText())); - } - - if (node.has("creative_group")) { - builder.creativeGroup(node.get("creative_group").asText()); - } - - if (node.has("allow_offhand")) { - builder.allowOffhand(node.get("allow_offhand").asBoolean()); - } - - if (node.has("display_handheld")) { - builder.displayHandheld(node.get("display_handheld").asBoolean()); - } - - if (node.has("texture_size")) { - builder.textureSize(node.get("texture_size").asInt()); - } - - if (node.has("render_offsets")) { - JsonNode tmpNode = node.get("render_offsets"); - - builder.renderOffsets(fromJsonNode(tmpNode)); - } + readTextIfPresent(node, "icon", builder::icon); + readIfPresent(node, "creative_category", builder::creativeCategory, category -> CreativeCategory.fromName(category.asText())); + readTextIfPresent(node, "creative_group", builder::creativeGroup); + readIfPresent(node, "allow_offhand", builder::allowOffhand, JsonNode::asBoolean); + readIfPresent(node, "display_handheld", builder::displayHandheld, JsonNode::asBoolean); + readIfPresent(node, "texture_size", builder::textureSize, JsonNode::asInt); + readIfPresent(node, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode); if (node.get("tags") instanceof ArrayNode tags) { Set tagsSet = new ObjectOpenHashSet<>(); @@ -234,38 +212,24 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo throw new InvalidCustomMappingsFileException("Expected predicate to be an object"); } - JsonNode typeNode = node.get("type"); - if (typeNode == null || !typeNode.isTextual()) { - throw new InvalidCustomMappingsFileException("Predicate missing type key"); - } - String type = typeNode.asText(); - - JsonNode propertyNode = node.get("property"); - if (propertyNode == null || !propertyNode.isTextual()) { - throw new InvalidCustomMappingsFileException("Predicate missing property key"); - } - String property = propertyNode.asText(); + String type = readOrThrow(node, "type", JsonNode::asText, "Predicate requires type key"); + String property = readOrThrow(node, "property", JsonNode::asText, "Predicate requires property key"); - // TODO helper methods to lessen code duplication switch (type) { case "condition" -> { try { ConditionPredicate.ConditionProperty conditionProperty = ConditionPredicate.ConditionProperty.valueOf(property.toUpperCase()); JsonNode expected = node.get("expected"); - JsonNode index = node.get("index"); + // Note that index is only used for the CUSTOM_MODEL_DATA property, but we allow specifying it for other properties anyway builder.predicate(new ConditionPredicate(conditionProperty, - expected == null || expected.asBoolean(), index == null || !index.isIntegralNumber() ? 0 : index.asInt())); + expected == null || expected.asBoolean(), readOrDefault(node, "index", JsonNode::asInt, 0))); } catch (IllegalArgumentException exception) { throw new InvalidCustomMappingsFileException("Unknown property " + property); } } case "match" -> { - JsonNode valueNode = node.get("value"); - if (valueNode == null || !valueNode.isTextual()) { - throw new InvalidCustomMappingsFileException("Predicate missing value key"); - } - String value = valueNode.asText(); + String value = readOrThrow(node, "value", JsonNode::asText, "Predicate requires value key"); switch (property) { case "charge_type" -> { @@ -279,37 +243,28 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, MinecraftKey.key(value))); // TODO case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, MinecraftKey.key(value))); // TODO case "custom_model_data" -> { - JsonNode indexNode = node.get("index"); - int index = 0; - if (indexNode != null && indexNode.isIntegralNumber()) { - index = indexNode.asInt(); - } - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataString(value, index))); + builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, + new CustomModelDataString(value, readOrDefault(node, "index", JsonNode::asInt, 0)))); } default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } } case "range_dispatch" -> { - JsonNode threshold = node.get("threshold"); - if (threshold == null || !threshold.isNumber()) { - throw new InvalidCustomMappingsFileException("Predicate missing threshold key"); - } + double threshold = readOrThrow(node, "threshold", JsonNode::asDouble, "Predicate requires threshold key"); JsonNode scaleNode = node.get("scale"); double scale = 1.0; if (scaleNode != null && scaleNode.isNumber()) { scale = scaleNode.asDouble(); } + JsonNode normalizeNode = node.get("normalize"); boolean normalizeIfPossible = normalizeNode != null && normalizeNode.booleanValue(); - JsonNode indexNode = node.get("index"); - int index = 0; - if (indexNode != null && indexNode.isIntegralNumber()) { - index = indexNode.asInt(); - } + + int index = readOrDefault(node, "index", JsonNode::asInt, 0); try { RangeDispatchPredicate.RangeDispatchProperty rangeDispatchProperty = RangeDispatchPredicate.RangeDispatchProperty.valueOf(property.toUpperCase()); - builder.predicate(new RangeDispatchPredicate(rangeDispatchProperty, threshold.asDouble(), scale, normalizeIfPossible, index)); + builder.predicate(new RangeDispatchPredicate(rangeDispatchProperty, threshold, scale, normalizeIfPossible, index)); } catch (IllegalArgumentException exception) { throw new InvalidCustomMappingsFileException("Unknown property " + property); } From fe8336a2b1b6a7c063465868cbdffbd773fc4ee1 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 15:23:27 +0000 Subject: [PATCH 042/118] Some of Adventure was left in the API --- .../v2/predicate/match/MatchPredicateProperty.java | 6 +++--- .../registry/mappings/versions/MappingsReader_v2.java | 6 +++--- .../geyser/translator/item/CustomItemTranslator.java | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java index cd1348c5534..15dbb609990 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java @@ -25,14 +25,14 @@ package org.geysermc.geyser.api.item.custom.v2.predicate.match; -import net.kyori.adventure.key.Key; +import org.geysermc.geyser.api.util.Identifier; // TODO can we do more? public class MatchPredicateProperty { public static final MatchPredicateProperty CHARGE_TYPE = create(); - public static final MatchPredicateProperty TRIM_MATERIAL = create(); - public static final MatchPredicateProperty CONTEXT_DIMENSION = create(); + public static final MatchPredicateProperty TRIM_MATERIAL = create(); + public static final MatchPredicateProperty CONTEXT_DIMENSION = create(); public static final MatchPredicateProperty CUSTOM_MODEL_DATA = create(); private MatchPredicateProperty() {} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 044ce36f192..cea6def5162 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -125,7 +125,7 @@ public CustomItemDefinition readItemMappingEntry(String itemModel, JsonNode node if (bedrockIdentifierNode == null || !bedrockIdentifierNode.isTextual() || bedrockIdentifierNode.asText().isEmpty()) { throw new InvalidCustomMappingsFileException("An item definition has no bedrock identifier"); } - if (model == null) { + if (model == null || model.isEmpty()) { throw new InvalidCustomMappingsFileException("An item definition has no model"); } @@ -240,8 +240,8 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo throw new InvalidCustomMappingsFileException("Unknown charge type " + value); } } - case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, MinecraftKey.key(value))); // TODO - case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, MinecraftKey.key(value))); // TODO + case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, new Identifier(value))); + case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, new Identifier(value))); case "custom_model_data" -> { builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataString(value, readOrDefault(node, "index", JsonNode::asInt, 0)))); diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index a3bcf578c8d..2c4b4aacb96 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -35,9 +35,11 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.JavaDimension; +import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.registry.RegistryEntryData; import org.geysermc.geyser.util.MinecraftKey; @@ -115,17 +117,17 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica } return true; } else if (match.property() == MatchPredicateProperty.TRIM_MATERIAL) { - Key material = (Key) match.data(); + Identifier material = (Identifier) match.data(); ArmorTrim trim = components.get(DataComponentType.TRIM); if (trim == null || trim.material().isCustom()) { return false; } RegistryEntryData registered = session.getRegistryCache().trimMaterials().entryById(trim.material().id()); - return registered != null && registered.key().equals(material); + return ItemRegistryPopulator.identifierToKey(material).equals(registered.key()); } else if (match.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { - Key dimension = (Key) match.data(); + Identifier dimension = (Identifier) match.data(); RegistryEntryData registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType()); - return registered != null && dimension.equals(registered.key()); + return ItemRegistryPopulator.identifierToKey(dimension).equals(registered.key()); } else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { CustomModelDataString expected = (CustomModelDataString) match.data(); return expected.value().equals(getSafeCustomModelData(components, CustomModelData::strings, expected.index())); From f44f368f9614ce7317c8478d75a5b3e4701ae8b3 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 15:24:53 +0000 Subject: [PATCH 043/118] Improve predicate reading error handling --- .../registry/mappings/versions/MappingsReader_v2.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index cea6def5162..f3721488de2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -195,13 +195,9 @@ private void readPredicates(CustomItemDefinition.Builder builder, JsonNode node) if (node.isObject()) { readPredicate(builder, node); } else if (node.isArray()) { - node.forEach(predicate -> { - try { - readPredicate(builder, predicate); - } catch (InvalidCustomMappingsFileException e) { - GeyserImpl.getInstance().getLogger().error("Error in reading predicate", e); // TODO log this better - } - }); + for (JsonNode predicate : node) { + readPredicate(builder, predicate); + } } else { throw new InvalidCustomMappingsFileException("Expected predicate key to be a list of predicates or a predicate"); } From a997820121ba8e4826d2041028610efee787b11c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 15:27:56 +0000 Subject: [PATCH 044/118] Make item definitions in group able to override the group's model --- .../registry/mappings/versions/MappingsReader_v2.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index f3721488de2..f5c521c6bcf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -120,7 +120,7 @@ public CustomItemDefinition readItemMappingEntry(String itemModel, JsonNode node JsonNode bedrockIdentifierNode = node.get("bedrock_identifier"); JsonNode modelNode = node.get("model"); - String model = itemModel != null || modelNode == null || !modelNode.isTextual() ? itemModel : modelNode.asText(); + String model = modelNode == null || !modelNode.isTextual() ? itemModel : modelNode.asText(); if (bedrockIdentifierNode == null || !bedrockIdentifierNode.isTextual() || bedrockIdentifierNode.asText().isEmpty()) { throw new InvalidCustomMappingsFileException("An item definition has no bedrock identifier"); @@ -238,10 +238,8 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo } case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, new Identifier(value))); case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, new Identifier(value))); - case "custom_model_data" -> { - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, - new CustomModelDataString(value, readOrDefault(node, "index", JsonNode::asInt, 0)))); - } + case "custom_model_data" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, + new CustomModelDataString(value, readOrDefault(node, "index", JsonNode::asInt, 0)))); default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } } From 7ed830ad78908f7033a054231391e46cdb590399 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 15:57:23 +0000 Subject: [PATCH 045/118] Improve error handling when registering custom items --- .../CustomItemRegistryPopulator.java | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 1fd9450c80e..b1a0000c289 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -81,14 +81,15 @@ public class CustomItemRegistryPopulator { Consumable.ItemUseAnimation.BRUSH, 12 ); - public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems /* TODO */) { - // TODO - // TODO better error handling? + public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems) { MappingsConfigReader mappingsConfigReader = new MappingsConfigReader(); // Load custom items from mappings files - mappingsConfigReader.loadItemMappingsFromJson((id, item) -> { - if (initialCheck(id, item, customItems, items)) { - customItems.get(id).add(item); + mappingsConfigReader.loadItemMappingsFromJson((identifier, item) -> { + Optional error = validate(identifier, item, customItems, items); + if (error.isEmpty()) { + customItems.get(identifier).add(item); + } else { + GeyserImpl.getInstance().getLogger().error("Not registering custom item definition (bedrock identifier=" + item.bedrockIdentifier() + "): " + error.get()); } }); @@ -102,10 +103,12 @@ public boolean register(@NonNull String identifier, @NonNull CustomItemData cust @Override public boolean register(@NonNull String identifier, @NonNull CustomItemDefinition definition) { - if (initialCheck(identifier, definition, customItems, items)) { + Optional error = validate(identifier, definition, customItems, items); + if (error.isEmpty()) { customItems.get(identifier).add(definition); return true; } + GeyserImpl.getInstance().getLogger().error("Not registering custom item definition (bedrock identifier=" + definition.bedrockIdentifier() + "): " + error.get()); return false; } @@ -132,56 +135,50 @@ public static GeyserCustomMappingData registerCustomItem(String customItemName, return new GeyserCustomMappingData(customItemDefinition, componentItemData, itemDefinition, customItemName, bedrockId); } - private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap registered, Map mappings) { - // TODO check if there's already a same model without predicate and this hasn't a predicate either - if (!mappings.containsKey(identifier)) { - GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier()); - return false; + /** + * @return an empty optional if there are no errors with the registration, and an optional with an error message if there are + */ + private static Optional validate(String vanillaIdentifier, CustomItemDefinition item, Multimap registered, Map mappings) { + if (!mappings.containsKey(vanillaIdentifier)) { + return Optional.of("Unknown Java item " + vanillaIdentifier); } Identifier bedrockIdentifier = item.bedrockIdentifier(); if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft"); - return false; + return Optional.of("Custom item bedrock identifier namespace can't be minecraft"); } else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) { - GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be in the minecraft namespace without a predicate"); - return false; + return Optional.of("Custom item definition model can't be in the minecraft namespace without a predicate"); } for (Map.Entry entry : registered.entries()) { if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) { - GeyserImpl.getInstance().getLogger().error("Duplicate custom item definition for Bedrock ID " + item.bedrockIdentifier()); - return false; + return Optional.of("Conflicts with another custom item definition with the same bedrock identifier"); } - Optional error = checkPredicate(entry, identifier, item); + Optional error = checkPredicate(entry, vanillaIdentifier, item); if (error.isPresent()) { - GeyserImpl.getInstance().getLogger().error("An existing item definition for the Java item " + identifier + " was already registered that conflicts with this one!"); - GeyserImpl.getInstance().getLogger().error("First entry: " + entry.getValue().bedrockIdentifier()); - GeyserImpl.getInstance().getLogger().error("Second entry: " + item.bedrockIdentifier()); - GeyserImpl.getInstance().getLogger().error(error.orElseThrow()); + return Optional.of("Conflicts with custom item definition (bedrock identifier=" + entry.getValue().bedrockIdentifier() + "): " + error.get()); } } - return true; + return Optional.empty(); } /** - * Returns an error message if there was a conflict, or an empty optional otherwise + * @return an error message if there was a conflict, or an empty optional otherwise */ - // TODO maybe simplify this - private static Optional checkPredicate(Map.Entry existing, String identifier, CustomItemDefinition item) { + private static Optional checkPredicate(Map.Entry existing, String vanillaIdentifier, CustomItemDefinition newItem) { // If the definitions are for different Java items or models then it doesn't matter - if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) { + if (!vanillaIdentifier.equals(existing.getKey()) || !newItem.model().equals(existing.getValue().model())) { return Optional.empty(); } // If they both don't have predicates they conflict - if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) { + if (existing.getValue().predicates().isEmpty() && newItem.predicates().isEmpty()) { return Optional.of("Both entries don't have predicates, one must have a predicate"); } // If their predicates are equal then they also conflict - if (existing.getValue().predicates().size() == item.predicates().size()) { + if (existing.getValue().predicates().size() == newItem.predicates().size()) { boolean equal = true; for (CustomItemPredicate predicate : existing.getValue().predicates()) { - if (!item.predicates().contains(predicate)) { + if (!newItem.predicates().contains(predicate)) { equal = false; } } From b5cef996fbf5c4f5dc3f421a2b5a01989c34a2de Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 16:04:46 +0000 Subject: [PATCH 046/118] Clean up custom item registry populator a bit --- .../CustomItemRegistryPopulator.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index b1a0000c289..26bb3b888ff 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -245,7 +245,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeThrowableProperties(componentBuilder); } - computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); // TODO check "hats" the hardcoded ones, once default components are here, check stack size + computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); componentBuilder.putCompound("item_properties", itemProperties.build()); builder.putCompound("components", componentBuilder.build()); @@ -310,11 +310,11 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp } } - // TODO minecraft java tool component - also needs work elsewhere to calculate correct break speed (server authorised block breaking) /** * @return can destroy in creative */ private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) { + // TODO check this, it's probably wrong by now, also check what the minecraft:tool java component can do here, if anything boolean canDestroyInCreative = true; float miningSpeed = 1.0f; @@ -374,8 +374,10 @@ private static boolean computeToolProperties(String toolType, NbtMapBuilder item } private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + // TODO set stack size to 1 when armour is effective as Bedrock doesn't allow armour with a stack size above 1 + // This should also be noted in the docs and maybe we should not allow armour with a stack size above 1 at all to prevent issues int protectionValue = 0; - // TODO protection value, check if it's just visual or not, also enchantable stuff + // TODO protection value, enchantable stuff and armour type? switch (equippable.slot()) { case BOOTS -> { componentBuilder.putString("minecraft:render_offsets", "boots"); @@ -418,8 +420,9 @@ private static void computeBlockItemProperties(String blockItem, NbtMapBuilder c componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); } - // TODO this isn't right private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping) { + // TODO check this, it's probably wrong by now + // setting high use_duration prevents the consume animation from playing itemProperties.putInt("use_duration", Integer.MAX_VALUE); // display item as tool (mainly for crossbow and bow) @@ -481,13 +484,14 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb } private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + // TODO check the animations, it didn't work properly // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() .putString("value", consumable.animation().toString().toLowerCase()) - .build()); // TODO check + .build()); // this component is required to allow the eat animation to play componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); @@ -499,8 +503,9 @@ private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); } - // TODO this also probably isn't right private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { + // TODO check this, it's probably wrong by now + // allows item to be thrown when holding down right click (individual presses are required w/o this component) componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); // this must be set to something for the swing animation to play @@ -509,6 +514,7 @@ private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { } private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) { + // TODO the non null check can probably be removed when no longer using MCPL in API Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null"); componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder() .putString("category", cooldown.cooldownGroup().asString()) @@ -518,6 +524,7 @@ private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBui } private static void computeRenderOffsets(CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) { + // TODO remove this one day when, probably when removing the old format, as render offsets are deprecated CustomRenderOffsets renderOffsets = bedrockOptions.renderOffsets(); if (renderOffsets != null) { componentBuilder.remove("minecraft:render_offsets"); @@ -621,7 +628,6 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { return false; } - // TODO is this right? private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { return javaItem.gatherComponents(definition.components()); } From 66e288f30207a7fde91c85257d640c46115b284e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 16:35:49 +0000 Subject: [PATCH 047/118] Improve documentation of (predicate) new API --- .../custom/v2/CustomItemBedrockOptions.java | 2 +- .../item/custom/v2/CustomItemDefinition.java | 34 +++++++++++++------ .../v2/predicate/ConditionPredicate.java | 20 ++++++++++- .../custom/v2/predicate/MatchPredicate.java | 6 ++++ .../v2/predicate/RangeDispatchPredicate.java | 24 ++++++++++++- .../match/MatchPredicateProperty.java | 13 ++++++- 6 files changed, 85 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index bdf2de38316..2ae6576c9ea 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -34,7 +34,7 @@ import java.util.Set; /** - * This is used to store options for a custom item that can't be described using item components. + * This is used to store options for a custom item defintion that can't be described using item components. */ public interface CustomItemBedrockOptions { diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 0896779cffa..17cbfe7b5ff 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -34,14 +34,28 @@ import java.util.List; /** - * This is used to define a custom item and its properties. + * This is used to define a custom item and its properties for a specific Java item and item model definition combination. + * + *

A custom item definition will be used for all item stacks that match the Java item and item model this item is for. + * Additionally, predicates can be added that allow fine-grained control as to when to use this custom item. These predicates are similar + * to the predicates available in Java item model definitions.

+ * + *

In Geyser, all registered custom item definitions for a Java item model will be checked in a specific order: + * + *

    + *
  1. First by checking their priority values, higher priority values going first.
  2. + *
  3. Then by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first.
  4. + *
  5. Lastly by the amount of predicates, from most to least.
  6. + *
+ * + * This ensures predicates will be checked in a correct order, and that in most cases specifying a priority value isn't necessary, and in the few cases + * where Geyser doesn't properly sort definitions, specifying a priority value will. + *

*/ -// TODO note that definitions will be sorted by predicates public interface CustomItemDefinition { /** - * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. If the {@code minecraft} namespace is given in the builder, the default - * namespace of the implementation is used. For Geyser, the default namespace is the {@code geyser_custom} namespace. + * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. */ @NonNull Identifier bedrockIdentifier(); @@ -51,7 +65,7 @@ public interface CustomItemDefinition { @NonNull String displayName(); /** - * The item model this definition is for. If the model is in the {@code minecraft} namespace, then the definition is required to have a predicate. + * The item model this definition is for. If the model is in the {@code minecraft} namespace, then the definition must have at least one predicate. * *

If multiple item definitions for a model are registered, then only one can have no predicate.

*/ @@ -61,7 +75,10 @@ public interface CustomItemDefinition { * The icon used for this item. * *

If none is set in the item's Bedrock options, then the item's Bedrock identifier is used, - * the namespace separator replaced with {@code .} and the path separators ({@code /}) replaced with {@code _}.

+ * the namespace separator ({@code :}) replaced with {@code .} and the path separators ({@code /}) replaced with {@code _}. For example:

+ * + *

{@code my_datapack:my_custom_item} => {@code my_datapack.my_custom_item}

+ *

{@code my_datapack:cool_items/cool_item_1} => {@code my_datapack.cool_items_cool_item_1}

*/ default @NonNull String icon() { return bedrockOptions().icon() == null ? bedrockIdentifier().toString().replaceAll(":", ".").replaceAll("/", "_") : bedrockOptions().icon(); @@ -69,9 +86,6 @@ public interface CustomItemDefinition { /** * The predicates that have to match for this item to be used. These predicates are similar to the Java item model predicates. - * - *

If all predicates match for multiple definitions, then the first registered item with all matching predicates is used. If no predicates match, then the item definition without any predicates - * is used, if any.

*/ @NonNull List predicates(); @@ -118,7 +132,7 @@ interface Builder { Builder priority(int priority); - // TODO do we want another format for this? + // TODO do we want a component(Type, Value) method instead? Builder components(@NonNull DataComponents components); CustomItemDefinition build(); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java index dc6ef518bd0..a19af524c21 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java @@ -25,6 +25,13 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; +/** + * A predicate that checks for a certain boolean property of the item stack and returns true if it matches the expected value. + * + * @param property the property to check. + * @param expected whether the property should be true or false. Defaults to true. + * @param index only used for the {@code CUSTOM_MODEL_DATA} property, determines which flag of the item's custom model data to check. Defaults to 0. + */ public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate { public ConditionPredicate(ConditionProperty property, boolean expected) { @@ -35,11 +42,22 @@ public ConditionPredicate(ConditionProperty property) { this(property, true); } - // TODO maybe we can extend this public enum ConditionProperty { + /** + * Checks if the item is broken (has 1 durability point left). + */ BROKEN, + /** + * Checks if the item is damaged (has non-full durability). + */ DAMAGED, + /** + * Checks if the item is unbreakable. + */ UNBREAKABLE, + /** + * Returns one of the item's custom model data flags, defaults to false. + */ CUSTOM_MODEL_DATA } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java index eca29d2e4b6..14e005332ff 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java @@ -27,5 +27,11 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; +/** + * A predicate that matches a property of the item stack and returns true if it matches the expected value. + * + * @param property the property to check for. + * @param data the value expected. + */ public record MatchPredicate(MatchPredicateProperty property, T data) implements CustomItemPredicate { } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java index 575190ac78b..08bcaf130e8 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java @@ -25,6 +25,15 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; +/** + * A predicate that checks for a certain numeric property of the item stack and returns true if it is above the specified threshold. + * + * @param property the property to check. + * @param threshold the threshold the property should be above. + * @param scale factor to multiply the property value with before checking it with the threshold. Defaults to 1.0. + * @param normalizeIfPossible if the property value should be normalised to a value between 0.0 and 1.0. Defaults to false. Only works for certain properties. + * @param index only used for the {@code CUSTOM_MODEL_DATA} property, determines which float of the item's custom model data to check. Defaults to 0. + */ public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements CustomItemPredicate { public RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { @@ -39,11 +48,24 @@ public RangeDispatchPredicate(RangeDispatchProperty property, double threshold) this(property, threshold, 1.0); } - // TODO check if we can change items while bedrock is using them, and if bedrock will continue to use them public enum RangeDispatchProperty { + /** + * Checks the item's bundle fullness. Returns the total stack count of all the items in a bundle. + * + *

Usually used with bundles, but works for any item with the {@code minecraft:bundle_contents} component.

+ */ BUNDLE_FULLNESS, + /** + * Checks the item's damage value. Can be normalised. + */ DAMAGE, + /** + * Checks the item's stack count. Can be normalised. + */ COUNT, + /** + * Checks one of the item's custom model data floats, defaults to 0.0. + */ CUSTOM_MODEL_DATA } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java index 15dbb609990..a9dd20c3b84 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java @@ -27,12 +27,23 @@ import org.geysermc.geyser.api.util.Identifier; -// TODO can we do more? public class MatchPredicateProperty { + /** + * Matches for the item's charged projectile. Usually used with crossbows, but checks any item with the {@code minecraft:charged_projectiles} component. + */ public static final MatchPredicateProperty CHARGE_TYPE = create(); + /** + * Matches the item's trim material identifier. Works for any item with the {@code minecraft:trim} component. + */ public static final MatchPredicateProperty TRIM_MATERIAL = create(); + /** + * Matches the dimension identifier the Bedrock session player is currently in. + */ public static final MatchPredicateProperty CONTEXT_DIMENSION = create(); + /** + * Matches a string of the item's custom model data strings. + */ public static final MatchPredicateProperty CUSTOM_MODEL_DATA = create(); private MatchPredicateProperty() {} From 852c5c2187538486e1261492c6e75a27feaeb896 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 17:04:15 +0000 Subject: [PATCH 048/118] Apparently Javadoc can break builds --- .../geyser/api/item/custom/v2/CustomItemDefinition.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 17cbfe7b5ff..a34f25e8274 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -40,7 +40,7 @@ * Additionally, predicates can be added that allow fine-grained control as to when to use this custom item. These predicates are similar * to the predicates available in Java item model definitions.

* - *

In Geyser, all registered custom item definitions for a Java item model will be checked in a specific order: + *

In Geyser, all registered custom item definitions for a Java item model will be checked in a specific order:

* *
    *
  1. First by checking their priority values, higher priority values going first.
  2. @@ -48,9 +48,8 @@ *
  3. Lastly by the amount of predicates, from most to least.
  4. *
* - * This ensures predicates will be checked in a correct order, and that in most cases specifying a priority value isn't necessary, and in the few cases - * where Geyser doesn't properly sort definitions, specifying a priority value will. - *

+ *

This ensures predicates will be checked in a correct order, and that in most cases specifying a priority value isn't necessary, and in the few cases + * where Geyser doesn't properly sort definitions, specifying a priority value will.

*/ public interface CustomItemDefinition { From 18cde3264e946af6b5db277c1248b4aee771897c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 16 Dec 2024 18:31:57 +0000 Subject: [PATCH 049/118] Improve documentation just a bit --- .../api/item/custom/v2/predicate/RangeDispatchPredicate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java index 08bcaf130e8..da0d0d63903 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java @@ -30,8 +30,8 @@ * * @param property the property to check. * @param threshold the threshold the property should be above. - * @param scale factor to multiply the property value with before checking it with the threshold. Defaults to 1.0. - * @param normalizeIfPossible if the property value should be normalised to a value between 0.0 and 1.0. Defaults to false. Only works for certain properties. + * @param scale factor to multiply the property value with before comparing it with the threshold. Defaults to 1.0. + * @param normalizeIfPossible if the property value should be normalised to a value between 0.0 and 1.0 before scaling and comparing. Defaults to false. Only works for certain properties. * @param index only used for the {@code CUSTOM_MODEL_DATA} property, determines which float of the item's custom model data to check. Defaults to 0. */ public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements CustomItemPredicate { From 25109b71b3ae87dd03d29d5d0175c763112d6367 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:09:43 +0000 Subject: [PATCH 050/118] Add predicate caching --- .../geyser/translator/item/CustomItemTranslator.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 2c4b4aacb96..648d28b5c2d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.translator.item; import com.google.common.collect.Multimap; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; @@ -78,10 +80,13 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, return null; } + // Cache predicate values so they're not recalculated every time when there are multiple item definitions using the same predicates + Object2BooleanMap calculatedPredicates = new Object2BooleanOpenHashMap<>(); for (GeyserCustomMappingData customMapping : customItems) { boolean allMatch = true; for (CustomItemPredicate predicate : customMapping.definition().predicates()) { - if (!predicateMatches(session, predicate, stackSize, components)) { + boolean value = calculatedPredicates.computeIfAbsent(predicate, x -> predicateMatches(session, predicate, stackSize, components)); + if (!value) { allMatch = false; break; } From 635c8c45beb45b3fd0ff56f3c64cf86b8f3fe79e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:10:00 +0000 Subject: [PATCH 051/118] Default to definition type when no type key is given --- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index f5c521c6bcf..2a0ab3cbac6 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -90,7 +90,7 @@ public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer definitionConsumer) throws InvalidCustomMappingsFileException { - String type = readOrThrow(data, "type", JsonNode::asText, "Missing type key in definition"); + String type = readOrDefault(data, "type", JsonNode::asText, "definition"); if (type.equals("group")) { // Read model of group if it's present, or default to the model of the parent group, if that's present // If the parent group model is not present (or there is no parent group), and this group also doesn't have a model, then it is expected the definitions supply their model themselves From e84f8a354344518a75d61a0cbab4b217489cdb7e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:10:52 +0000 Subject: [PATCH 052/118] Whoops got to include the lib --- gradle/libs.versions.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b904366441e..9a82cefb226 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,6 +71,7 @@ fastutil-int-byte-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int- fastutil-int-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-boolean-maps", version.ref = "fastutil" } fastutil-object-int-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-int-maps", version.ref = "fastutil" } fastutil-object-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-object-maps", version.ref = "fastutil" } +fastutil-object-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-boolean-maps", version.ref = "fastutil" } adventure-text-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" } # Remove when we remove our Adventure bump adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" } @@ -153,7 +154,7 @@ blossom = { id = "net.kyori.blossom", version.ref = "blossom" } [bundles] jackson = [ "jackson-annotations", "jackson-databind", "jackson-dataformat-yaml" ] -fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps" ] +fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps", "fastutil-object-boolean-maps" ] adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ] log4j = [ "log4j-api", "log4j-core", "log4j-slf4j2-impl" ] jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ] From bcc791544b4f9cf55de75086720bdc667949863e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:17:51 +0000 Subject: [PATCH 053/118] Factor in range dispatch scaling when sorting predicate --- .../geyser/registry/populator/ItemRegistryPopulator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 b73f66b7354..9596a38d8c4 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 @@ -771,7 +771,9 @@ public int compare(GeyserCustomMappingData firstData, GeyserCustomMappingData se .map(otherPredicate -> (RangeDispatchPredicate) otherPredicate) .findFirst(); if (other.isPresent()) { - return (int) (other.orElseThrow().threshold() - rangeDispatch.threshold()); + double otherScaledThreshold = other.get().threshold() / other.get().scale(); + double thisThreshold = rangeDispatch.threshold() / rangeDispatch.scale(); + return (int) (otherScaledThreshold - thisThreshold); } } // TODO not a fan of how this looks } From 4aea17a174ba49471ffb99c3071ba5d7dabe6edf Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:51:26 +0000 Subject: [PATCH 054/118] Component combination validation --- .../InvalidItemComponentsException.java | 33 +++++++++++++++++++ .../CustomItemRegistryPopulator.java | 25 ++++++++++++-- .../populator/ItemRegistryPopulator.java | 29 +++++++++------- 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java diff --git a/core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java new file mode 100644 index 00000000000..c2109dd8cfd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.item.exception; + +public class InvalidItemComponentsException extends Exception { + + public InvalidItemComponentsException(String message) { + super(message); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 26bb3b888ff..3a83f62fd5e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -48,6 +48,7 @@ import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.WearableSlot; +import org.geysermc.geyser.item.exception.InvalidItemComponentsException; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.GeyserMappingItem; @@ -126,7 +127,9 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { } public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, - CustomItemDefinition customItemDefinition, int bedrockId) { + CustomItemDefinition customItemDefinition, int bedrockId) throws InvalidItemComponentsException { + checkComponents(customItemDefinition, javaItem); + ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId); @@ -190,6 +193,24 @@ private static Optional checkPredicate(Map.EntryNote that, this method only checks for illegal combinations of item components. It is expected that the values of the components separately have + * already been validated (for example, it is expected that stack size is in the range [1, 99]).

+ */ + private static void checkComponents(CustomItemDefinition definition, Item javaItem) throws InvalidItemComponentsException { + DataComponents components = patchDataComponents(javaItem, definition); + int stackSize = components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); + int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); + + if (components.get(DataComponentType.EQUIPPABLE) != null && stackSize > 1) { + throw new InvalidItemComponentsException("Bedrock doesn't support equippable items with a stack size above 1"); + } else if (stackSize > 1 && maxDamage > 0) { + throw new InvalidItemComponentsException("Stack size must be 1 when max damage is above 0"); + } + } + public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { // TODO return null; @@ -374,8 +395,6 @@ private static boolean computeToolProperties(String toolType, NbtMapBuilder item } private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - // TODO set stack size to 1 when armour is effective as Bedrock doesn't allow armour with a stack size above 1 - // This should also be noted in the docs and maybe we should not allow armour with a stack size above 1 at all to prevent issues int protectionValue = 0; // TODO protection value, enchantable stuff and armour type? switch (equippable.slot()) { 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 9596a38d8c4..bd6bfb668bf 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 @@ -68,6 +68,7 @@ import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.Rarity; +import org.geysermc.geyser.item.exception.InvalidItemComponentsException; import org.geysermc.geyser.item.type.BlockItem; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.level.block.property.Properties; @@ -483,7 +484,7 @@ public static void populate() { for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().toString(); // TODO non vanilla stuff + String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().toString(); // TODO non vanilla stuff, simplify this maybe cause it's all identifiers now if (!registeredItemNames.add(customItemName)) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); @@ -491,24 +492,30 @@ public static void populate() { continue; } - GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( - customItemName, javaItem, mappingItem, customItem, customProtocolId); + try { + GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( + customItemName, javaItem, mappingItem, customItem, customProtocolId); - if (customItem.bedrockOptions().creativeCategory() != CreativeCategory.NONE) { - creativeItems.add(ItemData.builder() + if (customItem.bedrockOptions().creativeCategory() != CreativeCategory.NONE) { + creativeItems.add(ItemData.builder() .netId(creativeNetId.incrementAndGet()) .definition(customMapping.itemDefinition()) .blockDefinition(null) .count(1) .build()); - } + } - // ComponentItemData - used to register some custom properties - componentItemData.add(customMapping.componentItemData()); - customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); - registry.put(customMapping.integerId(), customMapping.itemDefinition()); + // ComponentItemData - used to register some custom properties + componentItemData.add(customMapping.componentItemData()); + customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); + registry.put(customMapping.integerId(), customMapping.itemDefinition()); - customIdMappings.put(customMapping.integerId(), customMapping.stringId()); + customIdMappings.put(customMapping.integerId(), customMapping.stringId()); + } catch (InvalidItemComponentsException exception) { + if (firstMappingsPass) { + GeyserImpl.getInstance().getLogger().error("Not registering custom item " + customItem.bedrockIdentifier() + "!", exception); + } + } } } else { customItemDefinitions = null; From 16832433a09a55cc278cd40723cc69ebd8eb9585 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:52:04 +0000 Subject: [PATCH 055/118] Fix max stack size validation when reading json mappings --- .../registry/mappings/components/DataComponentReaders.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 8422631940d..783084652d7 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -57,7 +57,7 @@ public static void readDataComponent(DataComponents components, Key key, @NonNul READERS.put(MinecraftKey.key("equippable"), new EquippableReader()); READERS.put(MinecraftKey.key("food"), new FoodReader()); READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponentType.MAX_DAMAGE, 0)); - READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponentType.MAX_STACK_SIZE, 0, 99)); + READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponentType.MAX_STACK_SIZE, 1, 99)); READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); } } From 4629f7b6f658ef99d566fa670dc7d2032cb283f6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 11:52:56 +0000 Subject: [PATCH 056/118] "Implement" v2 block mappings reading --- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 2a0ab3cbac6..d1204748ba4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -85,7 +85,7 @@ public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - // TODO + throw new RuntimeException("Unimplemented; use the v1 format of block mappings"); } private void readItemDefinitionEntry(JsonNode data, String itemIdentifier, String model, @@ -269,6 +269,6 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo @Override public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { - return null; // TODO + throw new InvalidCustomMappingsFileException("Unimplemented; use the v1 format of block mappings"); } } From 2b9f51915894f3a95f4fc805383463d7a7cfb06e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 12:03:00 +0000 Subject: [PATCH 057/118] Cleanup item registry populator a bit --- .../geyser/item/GeyserCustomMappingData.java | 2 +- .../CustomItemRegistryPopulator.java | 19 +++++++++---------- .../populator/ItemRegistryPopulator.java | 13 ++++++------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java index 26a15733eee..9f8d10b8d27 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomMappingData.java @@ -29,5 +29,5 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -public record GeyserCustomMappingData(CustomItemDefinition definition, ComponentItemData componentItemData, ItemDefinition itemDefinition, String stringId, int integerId) { +public record GeyserCustomMappingData(CustomItemDefinition definition, ComponentItemData componentItemData, ItemDefinition itemDefinition, int integerId) { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 3a83f62fd5e..10ef41bb475 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -126,16 +126,16 @@ public boolean register(@NonNull NonVanillaCustomItemData customItemData) { } } - public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, - CustomItemDefinition customItemDefinition, int bedrockId) throws InvalidItemComponentsException { - checkComponents(customItemDefinition, javaItem); + public static GeyserCustomMappingData registerCustomItem(Item javaItem, GeyserMappingItem mapping, CustomItemDefinition customItem, + int bedrockId) throws InvalidItemComponentsException { + checkComponents(customItem, javaItem); - ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); + ItemDefinition itemDefinition = new SimpleItemDefinition(customItem.bedrockIdentifier().toString(), bedrockId, true); - NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId); - ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build()); + NbtMapBuilder builder = createComponentNbt(customItem, javaItem, mapping, bedrockId); + ComponentItemData componentItemData = new ComponentItemData(customItem.bedrockIdentifier().toString(), builder.build()); - return new GeyserCustomMappingData(customItemDefinition, componentItemData, itemDefinition, customItemName, bedrockId); + return new GeyserCustomMappingData(customItem, componentItemData, itemDefinition, bedrockId); } /** @@ -216,10 +216,9 @@ public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItem return null; } - private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping, - String customItemName, int customItemId) { + private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping, int customItemId) { NbtMapBuilder builder = NbtMap.builder() - .putString("name", customItemName) + .putString("name", customItemDefinition.bedrockIdentifier().toString()) .putInt("id", customItemId); NbtMapBuilder itemProperties = NbtMap.builder(); 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 bd6bfb668bf..cedab115052 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 @@ -270,7 +270,7 @@ public static void populate() { javaOnlyItems.addAll(palette.javaOnlyItems().keySet()); Int2ObjectMap customIdMappings = new Int2ObjectOpenHashMap<>(); - Set registeredItemNames = new ObjectOpenHashSet<>(); // This is used to check for duplicate item names + Set registeredCustomItems = new ObjectOpenHashSet<>(); // This is used to check for duplicate item names for (Map.Entry entry : items.entrySet()) { Item javaItem = Registries.JAVA_ITEM_IDENTIFIERS.get(entry.getKey()); @@ -484,17 +484,16 @@ public static void populate() { for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().toString(); // TODO non vanilla stuff, simplify this maybe cause it's all identifiers now - if (!registeredItemNames.add(customItemName)) { + Identifier customItemIdentifier = customItem.bedrockIdentifier(); // TODO don't forget to check if this works for non vanilla too, it probably does + if (!registeredCustomItems.add(customItemIdentifier)) { if (firstMappingsPass) { - GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); + GeyserImpl.getInstance().getLogger().error("Custom item '" + customItemIdentifier + "' already exists and was registered again! Skipping..."); } continue; } try { - GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( - customItemName, javaItem, mappingItem, customItem, customProtocolId); + GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem(javaItem, mappingItem, customItem, customProtocolId); if (customItem.bedrockOptions().creativeCategory() != CreativeCategory.NONE) { creativeItems.add(ItemData.builder() @@ -510,7 +509,7 @@ public static void populate() { customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); registry.put(customMapping.integerId(), customMapping.itemDefinition()); - customIdMappings.put(customMapping.integerId(), customMapping.stringId()); + customIdMappings.put(customMapping.integerId(), customItemIdentifier.toString()); } catch (InvalidItemComponentsException exception) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Not registering custom item " + customItem.bedrockIdentifier() + "!", exception); From ff35c6e75289d9652755cea0a4975e79349bee51 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 12:04:27 +0000 Subject: [PATCH 058/118] Fix mapping reading --- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index d1204748ba4..7039e6892a4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -85,7 +85,10 @@ public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - throw new RuntimeException("Unimplemented; use the v1 format of block mappings"); + JsonNode blocksNode = mappingsRoot.get("blocks"); + if (blocksNode != null) { + throw new RuntimeException("Unimplemented; use the v1 format of block mappings"); + } } private void readItemDefinitionEntry(JsonNode data, String itemIdentifier, String model, From b254594bbf3fb00e2f767188f6a870b6804b1607 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 12:06:28 +0000 Subject: [PATCH 059/118] Move identifierToKey out of ItemRegistryPopulator --- .../geyser/registry/populator/ItemRegistryPopulator.java | 8 ++------ .../geyser/translator/item/CustomItemTranslator.java | 5 ++--- .../main/java/org/geysermc/geyser/util/MinecraftKey.java | 5 +++++ 3 files changed, 9 insertions(+), 9 deletions(-) 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 cedab115052..1a7abc3ec8d 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 @@ -81,6 +81,7 @@ import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.geyser.registry.type.PaletteItem; +import org.geysermc.geyser.util.MinecraftKey; import java.io.InputStream; import java.util.ArrayList; @@ -506,7 +507,7 @@ public static void populate() { // ComponentItemData - used to register some custom properties componentItemData.add(customMapping.componentItemData()); - customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); + customItemDefinitions.put(MinecraftKey.identifierToKey(customItem.model()), customMapping); registry.put(customMapping.integerId(), customMapping.itemDefinition()); customIdMappings.put(customMapping.integerId(), customItemIdentifier.toString()); @@ -735,11 +736,6 @@ private static void registerFurnaceMinecart(int nextFreeBedrockId, List registered = session.getRegistryCache().trimMaterials().entryById(trim.material().id()); - return ItemRegistryPopulator.identifierToKey(material).equals(registered.key()); + return MinecraftKey.identifierToKey(material).equals(registered.key()); } else if (match.property() == MatchPredicateProperty.CONTEXT_DIMENSION) { Identifier dimension = (Identifier) match.data(); RegistryEntryData registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType()); - return ItemRegistryPopulator.identifierToKey(dimension).equals(registered.key()); + return MinecraftKey.identifierToKey(dimension).equals(registered.key()); } else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) { CustomModelDataString expected = (CustomModelDataString) match.data(); return expected.value().equals(getSafeCustomModelData(components, CustomModelData::strings, expected.index())); diff --git a/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java b/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java index 5f1c8e084e7..d2d54f579d0 100644 --- a/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java +++ b/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.util; import net.kyori.adventure.key.Key; +import org.geysermc.geyser.api.util.Identifier; import org.intellij.lang.annotations.Subst; public final class MinecraftKey { @@ -36,4 +37,8 @@ public final class MinecraftKey { public static Key key(@Subst("empty") String s) { return Key.key(s); } + + public static Key identifierToKey(Identifier identifier) { + return Key.key(identifier.namespace(), identifier.path()); + } } From 56c508deaa06a256cc58ff1804961b57ac110a03 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 19 Dec 2024 13:39:39 +0000 Subject: [PATCH 060/118] Throw UnsupportedOperationException when reading block v2 mappings --- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 7039e6892a4..c024616dabf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -87,7 +87,7 @@ public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer consumer) { JsonNode blocksNode = mappingsRoot.get("blocks"); if (blocksNode != null) { - throw new RuntimeException("Unimplemented; use the v1 format of block mappings"); + throw new UnsupportedOperationException("Unimplemented; use the v1 format of block mappings"); } } From 5620f0a9b71be497b7802646aaae5d65ef8d28ba Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 10 Jan 2025 12:44:52 +0000 Subject: [PATCH 061/118] Move predicate implementations out of API module --- .../api/item/custom/CustomItemData.java | 12 ++--- .../v2/predicate/ConditionProperty.java | 45 ++++++++++++++++++ .../v2/predicate/CustomItemPredicate.java | 45 +++++++++++++++++- .../v2/predicate/RangeDispatchProperty.java | 47 +++++++++++++++++++ .../match/MatchPredicateProperty.java | 2 +- .../v2/predicate/ConditionPredicate.java | 34 ++------------ .../custom/v2/predicate/MatchPredicate.java | 5 +- .../v2/predicate/RangeDispatchPredicate.java | 27 ++--------- .../loader/ProviderRegistryLoader.java | 13 +++++ .../mappings/versions/MappingsReader_v2.java | 22 ++++----- .../CustomItemRegistryPopulator.java | 28 ++++++----- .../populator/ItemRegistryPopulator.java | 2 +- .../translator/item/CustomItemTranslator.java | 6 +-- 13 files changed, 201 insertions(+), 87 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java rename {api/src/main/java/org/geysermc/geyser/api => core/src/main/java/org/geysermc/geyser}/item/custom/v2/predicate/ConditionPredicate.java (67%) rename {api/src/main/java/org/geysermc/geyser/api => core/src/main/java/org/geysermc/geyser}/item/custom/v2/predicate/MatchPredicate.java (89%) rename {api/src/main/java/org/geysermc/geyser/api => core/src/main/java/org/geysermc/geyser}/item/custom/v2/predicate/RangeDispatchPredicate.java (76%) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 7370b29bdce..782ca6ceb74 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -30,8 +30,9 @@ import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.api.util.TriState; @@ -146,14 +147,13 @@ default CustomItemDefinition.Builder toDefinition(String javaItem) { CustomItemOptions options = customItemOptions(); if (options.customModelData().isPresent()) { - definition.predicate(new RangeDispatchPredicate(RangeDispatchPredicate.RangeDispatchProperty.CUSTOM_MODEL_DATA, - options.customModelData().getAsInt(), 1.0, false, 0)); + definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchProperty.CUSTOM_MODEL_DATA, options.customModelData().getAsInt())); } if (options.damagePredicate().isPresent()) { - definition.predicate(new RangeDispatchPredicate(RangeDispatchPredicate.RangeDispatchProperty.DAMAGE, options.damagePredicate().getAsInt())); + definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchProperty.DAMAGE, options.damagePredicate().getAsInt())); } if (options.unbreakable() != TriState.NOT_SET) { - definition.predicate(new ConditionPredicate(ConditionPredicate.ConditionProperty.UNBREAKABLE, Objects.requireNonNull(options.unbreakable().toBoolean()))); + definition.predicate(CustomItemPredicate.condition(ConditionProperty.UNBREAKABLE, Objects.requireNonNull(options.unbreakable().toBoolean()))); } return definition; } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java new file mode 100644 index 00000000000..0f7d34d3fc3 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +public enum ConditionProperty { + /** + * Checks if the item is broken (has 1 durability point left). + */ + BROKEN, + /** + * Checks if the item is damaged (has non-full durability). + */ + DAMAGED, + /** + * Checks if the item is unbreakable. + */ + UNBREAKABLE, + /** + * Returns one of the item's custom model data flags, defaults to false. + */ + CUSTOM_MODEL_DATA +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java index abb61efbed0..2aa971f465d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -25,5 +25,48 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; -public sealed interface CustomItemPredicate permits ConditionPredicate, MatchPredicate, RangeDispatchPredicate { // TODO maybe we need to move the predicate classes out of API +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; + +public interface CustomItemPredicate { + + int CONDITION = 0; + int MATCH = 1; + int RANGE_DISPATCH = 2; + + static CustomItemPredicate condition(ConditionProperty property) { + return condition(property, true); + } + + static CustomItemPredicate condition(ConditionProperty property, boolean expected) { + return condition(property, expected, 0); + } + + static CustomItemPredicate condition(ConditionProperty property, boolean expected, int index) { + return GeyserApi.api().provider(CustomItemPredicate.class, CONDITION, property, expected, index); + } + + static CustomItemPredicate match(MatchPredicateProperty property, T data) { + return GeyserApi.api().provider(CustomItemPredicate.class, MATCH, property, data); + } + + static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold) { + return rangeDispatch(property, threshold, 1.0); + } + + static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale) { + return rangeDispatch(property, threshold, scale, false, 0); + } + + static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, boolean normalizeIfPossible) { + return rangeDispatch(property, threshold, 1.0, normalizeIfPossible, 0); + } + + static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { + return rangeDispatch(property, threshold, scale, normalizeIfPossible, 0); + } + + static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) { + return GeyserApi.api().provider(CustomItemPredicate.class, RANGE_DISPATCH, property, threshold, scale, normalizeIfPossible, index); + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java new file mode 100644 index 00000000000..b1d1de5afda --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +public enum RangeDispatchProperty { + /** + * Checks the item's bundle fullness. Returns the total stack count of all the items in a bundle. + * + *

Usually used with bundles, but works for any item with the {@code minecraft:bundle_contents} component.

+ */ + BUNDLE_FULLNESS, + /** + * Checks the item's damage value. Can be normalised. + */ + DAMAGE, + /** + * Checks the item's stack count. Can be normalised. + */ + COUNT, + /** + * Checks one of the item's custom model data floats, defaults to 0.0. + */ + CUSTOM_MODEL_DATA +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java index a9dd20c3b84..e9d8041cc8a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java @@ -27,7 +27,7 @@ import org.geysermc.geyser.api.util.Identifier; -public class MatchPredicateProperty { +public final class MatchPredicateProperty { /** * Matches for the item's charged projectile. Usually used with crossbows, but checks any item with the {@code minecraft:charged_projectiles} component. diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java similarity index 67% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java rename to core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java index a19af524c21..3c82a8b884b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate; +package org.geysermc.geyser.item.custom.v2.predicate; + +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; /** * A predicate that checks for a certain boolean property of the item stack and returns true if it matches the expected value. @@ -33,31 +36,4 @@ * @param index only used for the {@code CUSTOM_MODEL_DATA} property, determines which flag of the item's custom model data to check. Defaults to 0. */ public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate { - - public ConditionPredicate(ConditionProperty property, boolean expected) { - this(property, expected, 0); - } - - public ConditionPredicate(ConditionProperty property) { - this(property, true); - } - - public enum ConditionProperty { - /** - * Checks if the item is broken (has 1 durability point left). - */ - BROKEN, - /** - * Checks if the item is damaged (has non-full durability). - */ - DAMAGED, - /** - * Checks if the item is unbreakable. - */ - UNBREAKABLE, - /** - * Returns one of the item's custom model data flags, defaults to false. - */ - CUSTOM_MODEL_DATA - } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java similarity index 89% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java rename to core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java index 14e005332ff..135d8af3e6a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,8 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate; +package org.geysermc.geyser.item.custom.v2.predicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; /** diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java similarity index 76% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java rename to core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java index da0d0d63903..a68bb5df0ba 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024-2025 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.predicate; +package org.geysermc.geyser.item.custom.v2.predicate; + +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; /** * A predicate that checks for a certain numeric property of the item stack and returns true if it is above the specified threshold. @@ -48,24 +51,4 @@ public RangeDispatchPredicate(RangeDispatchProperty property, double threshold) this(property, threshold, 1.0); } - public enum RangeDispatchProperty { - /** - * Checks the item's bundle fullness. Returns the total stack count of all the items in a bundle. - * - *

Usually used with bundles, but works for any item with the {@code minecraft:bundle_contents} component.

- */ - BUNDLE_FULLNESS, - /** - * Checks the item's damage value. Can be normalised. - */ - DAMAGE, - /** - * Checks the item's stack count. Can be normalised. - */ - COUNT, - /** - * Checks one of the item's custom model data floats, defaults to 0.0. - */ - CUSTOM_MODEL_DATA - } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 6c4550ef564..81089542abc 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -41,6 +41,10 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.impl.camera.GeyserCameraFade; @@ -52,6 +56,9 @@ import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; import org.geysermc.geyser.item.custom.GeyserCustomItemBedrockOptions; import org.geysermc.geyser.item.custom.GeyserCustomItemDefinition; +import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.item.custom.v2.predicate.MatchPredicate; +import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; import org.geysermc.geyser.level.block.GeyserCustomBlockData; import org.geysermc.geyser.level.block.GeyserGeometryComponent; @@ -92,6 +99,12 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov // items v2 providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Identifier) args[0], (Identifier) args[1])); providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); + providers.put(CustomItemPredicate.class, args -> switch ((int) args[0]) { + case CustomItemPredicate.CONDITION -> new ConditionPredicate((ConditionProperty) args[1], (boolean) args[2], (int) args[3]); + case CustomItemPredicate.MATCH -> new MatchPredicate<>((MatchPredicateProperty) args[1], args[2]); + case CustomItemPredicate.RANGE_DISPATCH -> new RangeDispatchPredicate((RangeDispatchProperty) args[1], (double) args[2], (double) args[3], (boolean) args[4], (int) args[5]); + default -> throw new IllegalArgumentException("Unknown predicate"); + }); // cameras providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index c024616dabf..9d35c5b7078 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -34,11 +34,11 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; -import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; @@ -217,11 +217,11 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo switch (type) { case "condition" -> { try { - ConditionPredicate.ConditionProperty conditionProperty = ConditionPredicate.ConditionProperty.valueOf(property.toUpperCase()); + ConditionProperty conditionProperty = ConditionProperty.valueOf(property.toUpperCase()); JsonNode expected = node.get("expected"); // Note that index is only used for the CUSTOM_MODEL_DATA property, but we allow specifying it for other properties anyway - builder.predicate(new ConditionPredicate(conditionProperty, + builder.predicate(CustomItemPredicate.condition(conditionProperty, expected == null || expected.asBoolean(), readOrDefault(node, "index", JsonNode::asInt, 0))); } catch (IllegalArgumentException exception) { throw new InvalidCustomMappingsFileException("Unknown property " + property); @@ -234,14 +234,14 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo case "charge_type" -> { try { ChargeType chargeType = ChargeType.valueOf(value.toUpperCase()); - builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CHARGE_TYPE, chargeType)); + builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CHARGE_TYPE, chargeType)); } catch (IllegalArgumentException exception) { throw new InvalidCustomMappingsFileException("Unknown charge type " + value); } } - case "trim_material" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.TRIM_MATERIAL, new Identifier(value))); - case "context_dimension" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CONTEXT_DIMENSION, new Identifier(value))); - case "custom_model_data" -> builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, + case "trim_material" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.TRIM_MATERIAL, new Identifier(value))); + case "context_dimension" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CONTEXT_DIMENSION, new Identifier(value))); + case "custom_model_data" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataString(value, readOrDefault(node, "index", JsonNode::asInt, 0)))); default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); } @@ -260,8 +260,8 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo int index = readOrDefault(node, "index", JsonNode::asInt, 0); try { - RangeDispatchPredicate.RangeDispatchProperty rangeDispatchProperty = RangeDispatchPredicate.RangeDispatchProperty.valueOf(property.toUpperCase()); - builder.predicate(new RangeDispatchPredicate(rangeDispatchProperty, threshold, scale, normalizeIfPossible, index)); + RangeDispatchProperty rangeDispatchProperty = RangeDispatchProperty.valueOf(property.toUpperCase()); + builder.predicate(CustomItemPredicate.rangeDispatch(rangeDispatchProperty, threshold, scale, normalizeIfPossible, index)); } catch (IllegalArgumentException exception) { throw new InvalidCustomMappingsFileException("Unknown property " + property); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 10ef41bb475..bff5bbb423d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -41,7 +41,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; @@ -71,15 +72,15 @@ public class CustomItemRegistryPopulator { // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. // TODO these don't seem to be applying correctly private static final Map BEDROCK_ANIMATIONS = Map.of( - Consumable.ItemUseAnimation.NONE, 0, - Consumable.ItemUseAnimation.EAT, 1, - Consumable.ItemUseAnimation.DRINK, 2, - Consumable.ItemUseAnimation.BLOCK, 3, - Consumable.ItemUseAnimation.BOW, 4, - Consumable.ItemUseAnimation.SPEAR, 6, - Consumable.ItemUseAnimation.CROSSBOW, 9, - Consumable.ItemUseAnimation.SPYGLASS, 10, - Consumable.ItemUseAnimation.BRUSH, 12 + Consumable.ItemUseAnimation.NONE, 0, // Does nothing in 1st person, eating in 3rd person + Consumable.ItemUseAnimation.EAT, 1, // Appears to look correctly + Consumable.ItemUseAnimation.DRINK, 2, // Appears to look correctly + Consumable.ItemUseAnimation.BLOCK, 3, // Does nothing in 1st person, eating in 3rd person + Consumable.ItemUseAnimation.BOW, 4, // Does nothing in 1st person, eating in 3rd person + Consumable.ItemUseAnimation.SPEAR, 6, // Does nothing, but looks like spear in 3rd person. Still has eating animation in 3rd person though, looks weird + Consumable.ItemUseAnimation.CROSSBOW, 9, // Does nothing in 1st person, eating in 3rd person + Consumable.ItemUseAnimation.SPYGLASS, 10, // Does nothing, but looks like spyglass in 3rd person. Same problems as spear. + Consumable.ItemUseAnimation.BRUSH, 12 // Brush in 1st and 3rd person. Same problems as spear. Looks weird when not displayed handheld. ); public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems) { @@ -513,6 +514,11 @@ private static void computeConsumableProperties(Consumable consumable, boolean c // this component is required to allow the eat animation to play componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); + + componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() + .putFloat("movement_modifier", 0.2F) + .putFloat("use_duration", consumable.consumeSeconds()) + .build()); } private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { @@ -639,7 +645,7 @@ private static NbtMap xyzToScaleList(float x, float y, float z) { private static boolean isUnbreakableItem(CustomItemDefinition definition) { for (CustomItemPredicate predicate : definition.predicates()) { - if (predicate instanceof ConditionPredicate condition && condition.property() == ConditionPredicate.ConditionProperty.UNBREAKABLE && condition.expected()) { + if (predicate instanceof ConditionPredicate condition && condition.property() == ConditionProperty.UNBREAKABLE && condition.expected()) { return true; } } 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 1a7abc3ec8d..d33e2e9fa07 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 @@ -61,7 +61,7 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.inventory.item.StoredItemMappings; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index ca660d69238..7306bf58954 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -31,10 +31,10 @@ import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; -import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate; +import org.geysermc.geyser.item.custom.v2.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.Identifier; From 0986203bdaf9e4b2a40a5811ddf56d8e20d499b9 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 10 Jan 2025 16:11:29 +0000 Subject: [PATCH 062/118] Send nutrition and saturation to the client --- .../CustomItemRegistryPopulator.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index bff5bbb423d..b8104e7cdc3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -246,7 +246,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD Consumable consumable = components.get(DataComponentType.CONSUMABLE); if (consumable != null) { FoodProperties foodProperties = components.get(DataComponentType.FOOD); - computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder); + computeConsumableProperties(consumable, foodProperties, itemProperties, componentBuilder); } if (vanillaMapping.isEntityPlacer()) { @@ -502,21 +502,28 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb } } - private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + private static void computeConsumableProperties(Consumable consumable, @Nullable FoodProperties foodProperties, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { // TODO check the animations, it didn't work properly // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); - itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); + componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() .putString("value", consumable.animation().toString().toLowerCase()) .build()); - // this component is required to allow the eat animation to play - componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build()); + int nutrition = foodProperties == null ? 0 : foodProperties.getNutrition(); + float saturationModifier = foodProperties == null ? 0.0F : foodProperties.getSaturationModifier(); + boolean canAlwaysEat = foodProperties == null || foodProperties.isCanAlwaysEat(); + componentBuilder.putCompound("minecraft:food", NbtMap.builder() + .putBoolean("can_always_eat", canAlwaysEat) + .putInt("nutrition", nutrition) + .putFloat("saturation_modifier", saturationModifier) + .putCompound("using_converts_to", NbtMap.EMPTY) + .build()); componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() - .putFloat("movement_modifier", 0.2F) + .putFloat("movement_modifier", 0.2F) // TODO is this the right value .putFloat("use_duration", consumable.consumeSeconds()) .build()); } From e8bd2a1e1ced23c2f23d26b4fcda3cd1cbb0d077 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 10 Jan 2025 16:13:29 +0000 Subject: [PATCH 063/118] Refactor mappings reading with proper type checking and conversions and better error messages, read nutrition and saturation values from mappings --- .../api/item/custom/CustomItemData.java | 4 +- .../InvalidCustomMappingsFileException.java | 4 + .../components/DataComponentReader.java | 6 +- .../components/DataComponentReaders.java | 6 +- .../components/readers/ConsumableReader.java | 18 +- .../components/readers/EquippableReader.java | 17 +- .../components/readers/FoodReader.java | 13 +- .../readers/IntComponentReader.java | 12 +- .../components/readers/UseCooldownReader.java | 23 +-- .../registry/mappings/util/MappingsUtil.java | 70 +++++++ .../registry/mappings/util/NodeReader.java | 149 +++++++++++++++ .../mappings/versions/MappingsReader.java | 31 +--- .../mappings/versions/MappingsReader_v1.java | 5 +- .../mappings/versions/MappingsReader_v2.java | 171 ++++++++---------- .../CustomItemRegistryPopulator.java | 2 +- 15 files changed, 348 insertions(+), 183 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 782ca6ceb74..4c7d7d36e9d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -130,9 +130,9 @@ static CustomItemData.Builder builder() { return GeyserApi.api().provider(CustomItemData.Builder.class); } - default CustomItemDefinition.Builder toDefinition(String javaItem) { + default CustomItemDefinition.Builder toDefinition(Identifier javaItem) { // TODO non vanilla - CustomItemDefinition.Builder definition = CustomItemDefinition.builder(new Identifier(javaItem), new Identifier(javaItem)) + CustomItemDefinition.Builder definition = CustomItemDefinition.builder(javaItem, javaItem) .displayName(displayName()) .bedrockOptions(CustomItemBedrockOptions.builder() .icon(icon()) diff --git a/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java index 99ca463d761..b2452fe0d2e 100644 --- a/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java +++ b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidCustomMappingsFileException.java @@ -35,4 +35,8 @@ public class InvalidCustomMappingsFileException extends Exception { public InvalidCustomMappingsFileException(String message) { super(message); } + + public InvalidCustomMappingsFileException(String task, String error, String... context) { + this("While " + task + " in " + String.join(" in ", context) + ": " + error); + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index 9f00c6bfaff..e4e6dd6ee8d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -38,10 +38,10 @@ protected DataComponentReader(DataComponentType type) { this.type = type; } - protected abstract V readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException; + protected abstract V readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException; - void read(DataComponents components, JsonNode node) throws InvalidCustomMappingsFileException { - components.put(type, readDataComponent(node)); + void read(DataComponents components, JsonNode node, String... context) throws InvalidCustomMappingsFileException { + components.put(type, readDataComponent(node, context)); } protected static void requireObject(JsonNode node) throws InvalidCustomMappingsFileException { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 783084652d7..457bd29cedf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -44,12 +44,12 @@ public class DataComponentReaders { private static final Map> READERS = new HashMap<>(); - public static void readDataComponent(DataComponents components, Key key, @NonNull JsonNode node) throws InvalidCustomMappingsFileException { + public static void readDataComponent(DataComponents components, Key key, @NonNull JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { DataComponentReader reader = READERS.get(key); if (reader == null) { - throw new InvalidCustomMappingsFileException("Unknown data component"); + throw new InvalidCustomMappingsFileException("reading data components", "unknown data component " + key, baseContext); } - reader.read(components, node); + reader.read(components, node, "component " + key, baseContext); } static { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java index 2b2ce144075..3d4be7578a2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -29,6 +29,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; @@ -42,17 +44,11 @@ public ConsumableReader() { } @Override - protected Consumable readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { - requireObject(node); - float consumeSeconds = 1.6F; - if (node.has("consume_seconds")) { - consumeSeconds = (float) node.get("consume_seconds").asDouble(); - } - - Consumable.ItemUseAnimation animation = Consumable.ItemUseAnimation.EAT; - if (node.has("animation")) { - animation = Consumable.ItemUseAnimation.valueOf(node.get("animation").asText().toUpperCase()); // TODO maybe not rely on the enum - } + protected Consumable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + MappingsUtil.requireObject(node, "reading component", context); + + float consumeSeconds = MappingsUtil.readOrDefault(node, "consume_seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), 1.6F, context); + Consumable.ItemUseAnimation animation = MappingsUtil.readOrDefault(node, "animation", NodeReader.ITEM_USE_ANIMATION, Consumable.ItemUseAnimation.EAT, context); return new Consumable(consumeSeconds, animation, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); // TODO are sound and particles supported on bedrock? } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java index b317955f657..193cb725525 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -29,12 +29,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; import java.util.Map; +import java.util.Objects; public class EquippableReader extends DataComponentReader { private static final Map SLOTS = Map.of( @@ -49,17 +52,11 @@ public EquippableReader() { } @Override - protected Equippable readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { - requireObject(node); + protected Equippable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + MappingsUtil.requireObject(node, "reading component", context); - JsonNode slotNode = node.get("slot"); - if (slotNode == null) { - throw new InvalidCustomMappingsFileException("Expected slot to be present"); - } - EquipmentSlot slot = SLOTS.get(slotNode.asText()); - if (slot == null) { - throw new InvalidCustomMappingsFileException("Expected slot to be head, chest, legs or feet"); - } + EquipmentSlot slot = MappingsUtil.readOrThrow(node, "slot", + NodeReader.NON_EMPTY_STRING.andThen(SLOTS::get).validate(Objects::nonNull, "expected slot to be head, chest, legs or feet"), context); return new Equippable(slot, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, null, null, null, false, false, false); // Other properties are unused diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java index 011c0210ed2..742fe99284c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java @@ -29,6 +29,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; @@ -39,10 +41,13 @@ public FoodReader() { } @Override - protected FoodProperties readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { - requireObject(node); + protected FoodProperties readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + MappingsUtil.requireObject(node, "reading component", context); - JsonNode canAlwaysEat = node.get("can_always_eat"); - return new FoodProperties(0, 0, canAlwaysEat != null && canAlwaysEat.asBoolean()); + int nutrition = MappingsUtil.readOrDefault(node, "nutrition", NodeReader.NON_NEGATIVE_INT, 0, context); + float saturationModifier = MappingsUtil.readOrDefault(node, "saturation_modifier", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); + boolean canAlwaysEat = MappingsUtil.readOrDefault(node, "can_always_eat", NodeReader.BOOLEAN, false, context); + + return new FoodProperties(nutrition, saturationModifier, canAlwaysEat); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java index 1d54e3314c3..ea6f61321c2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.NodeReader; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; public class IntComponentReader extends DataComponentReader { @@ -46,14 +47,7 @@ public IntComponentReader(DataComponentType type, int minimum) { } @Override - protected Integer readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { - if (!node.isIntegralNumber()) { - throw new InvalidCustomMappingsFileException("Expected an integer number"); - } - int value = node.asInt(); - if (value < minimum || value > maximum) { - throw new InvalidCustomMappingsFileException("Expected integer to be in the range of [" + minimum + ", " + maximum + "]"); - } - return value; + protected Integer readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + return NodeReader.boundedInt(minimum, maximum).read(node, "reading component", context); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java index 583bddfc039..f8a14893298 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java @@ -26,10 +26,13 @@ package org.geysermc.geyser.registry.mappings.components.readers; import com.fasterxml.jackson.databind.JsonNode; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; +import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; @@ -40,20 +43,10 @@ public UseCooldownReader() { } @Override - protected UseCooldown readDataComponent(@NonNull JsonNode node) throws InvalidCustomMappingsFileException { - requireObject(node); + protected UseCooldown readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + float seconds = MappingsUtil.readOrThrow(node, "seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), context); + Identifier cooldownGroup = MappingsUtil.readOrThrow(node, "cooldown_group", NodeReader.IDENTIFIER, context); - JsonNode seconds = node.get("seconds"); - JsonNode cooldown_group = node.get("cooldown_group"); - - if (seconds == null || !seconds.isNumber()) { - throw new InvalidCustomMappingsFileException("Expected seconds to be a number"); - } - - if (cooldown_group == null || !cooldown_group.isTextual()) { - throw new InvalidCustomMappingsFileException("Expected cooldown group to be a resource location"); - } - - return new UseCooldown((float) seconds.asDouble(), Key.key(cooldown_group.asText())); + return new UseCooldown(seconds, MinecraftKey.identifierToKey(cooldownGroup)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java new file mode 100644 index 00000000000..4f22ae7cf24 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.util; + +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; + +import java.util.function.Consumer; + +public class MappingsUtil { + + public static T readOrThrow(JsonNode node, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { + JsonNode object = node.get(name); + if (object == null) { + throw new InvalidCustomMappingsFileException(formatTask(name), "key " + name + " is required but was not present", context); + } + return converter.read(object, formatTask(name), context); + } + + public static T readOrDefault(JsonNode node, String name, NodeReader converter, T defaultValue, String... context) throws InvalidCustomMappingsFileException { + JsonNode object = node.get(name); + if (object == null) { + return defaultValue; + } + return converter.read(object, formatTask(name), context); + } + + public static void readTextIfPresent(JsonNode node, String name, Consumer consumer, String... context) throws InvalidCustomMappingsFileException { + readIfPresent(node, name, consumer, NodeReader.STRING, context); + } + + public static void readIfPresent(JsonNode node, String name, Consumer consumer, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { + if (node.has(name)) { + consumer.accept(converter.read(node.get(name), formatTask(name), context)); + } + } + + public static void requireObject(JsonNode node, String task, String... context) throws InvalidCustomMappingsFileException { + if (node == null || !node.isObject()) { + throw new InvalidCustomMappingsFileException(task, "expected an object", context); + } + } + + private static String formatTask(String name) { + return "reading key \"" + name + "\""; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java new file mode 100644 index 00000000000..7d48365d9c8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.util; + +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; +import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; + +import java.util.Objects; +import java.util.function.Predicate; + +@FunctionalInterface +public interface NodeReader { + + NodeReader INT = node -> { + if (!node.isTextual() && !node.isIntegralNumber()) { + throw new InvalidCustomMappingsFileException("expected node to be an integer"); + } + return node.isTextual() ? Integer.parseInt(node.textValue()) : node.intValue(); // Not using asInt because that catches the exception parseInt throws, which we don't want + }; + + NodeReader NON_NEGATIVE_INT = INT.validate(i -> i >= 0, "integer must be non-negative"); + + NodeReader POSITIVE_INT = INT.validate(i -> i > 0, "integer must be positive"); + + NodeReader DOUBLE = node -> { + if (!node.isTextual() && !node.isNumber()) { + throw new InvalidCustomMappingsFileException("expected node to be a number"); + } + return node.isTextual() ? Double.parseDouble(node.textValue()) : node.doubleValue(); // Not using asDouble because that catches the exception parseDouble throws, which we don't want + }; + + NodeReader NON_NEGATIVE_DOUBLE = DOUBLE.validate(d -> d >= 0, "number must be non-negative"); + + NodeReader POSITIVE_DOUBLE = DOUBLE.validate(d -> d > 0, "number must be positive"); + + NodeReader BOOLEAN = node -> { + if (node.isTextual()) { + String s = node.textValue(); + if (s.equals("true")) { + return true; + } else if (s.equals("false")) { + return false; + } + } else if (node.isIntegralNumber()) { + int i = node.intValue(); + if (i == 1) { + return true; + } else if (i == 0) { + return false; + } + } else if (node.isBoolean()) { + return node.booleanValue(); + } + throw new InvalidCustomMappingsFileException("expected node to be a boolean"); + }; + + NodeReader STRING = node -> { + if (!node.isTextual() && !node.isNumber() && !node.isBoolean()) { + throw new InvalidCustomMappingsFileException("expected node to be a string"); + } + return node.asText(); + }; + + NodeReader NON_EMPTY_STRING = STRING.validate(s -> !s.isEmpty(), "string must not be empty"); + + NodeReader IDENTIFIER = NON_EMPTY_STRING.andThen(Identifier::new); + + NodeReader CREATIVE_CATEGORY = NON_EMPTY_STRING.andThen(CreativeCategory::fromName).validate(Objects::nonNull, "unknown creative category"); + + NodeReader CONDITION_PROPERTY = ofEnum(ConditionProperty.class); + + NodeReader CHARGE_TYPE = ofEnum(ChargeType.class); + + NodeReader RANGE_DISPATCH_PROPERTY = ofEnum(RangeDispatchProperty.class); + + NodeReader ITEM_USE_ANIMATION = ofEnum(Consumable.ItemUseAnimation.class); + + static > NodeReader ofEnum(Class clazz) { + return NON_EMPTY_STRING.andThen(String::toUpperCase).andThen(s -> { + try { + return Enum.valueOf(clazz, s); + } catch (IllegalArgumentException exception) { + throw new InvalidCustomMappingsFileException("unknown element"); + } + }); + } + + static NodeReader boundedInt(int min, int max) { + return INT.validate(i -> i >= min && i <= max, "integer must be in range [" + min + ", " + max + "]"); + } + + T read(JsonNode node) throws InvalidCustomMappingsFileException; + + default T read(JsonNode node, String task, String... context) throws InvalidCustomMappingsFileException { + try { + return read(node); + } catch (Exception exception) { + throw new InvalidCustomMappingsFileException(task, exception.getMessage() + " (node was " + node.toString() + ")", context); + } + } + + default NodeReader andThen(After after) { + return node -> after.apply(read(node)); + } + + default NodeReader validate(Predicate validator, String error) { + return andThen(v -> { + if (!validator.test(v)) { + throw new InvalidCustomMappingsFileException(error); + } + return v; + }); + } + + @FunctionalInterface + interface After { + + T apply(V value) throws InvalidCustomMappingsFileException; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java index 85915ae476c..fe1432eb73b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java @@ -29,47 +29,20 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; import java.nio.file.Path; import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; public abstract class MappingsReader { public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); - public abstract CustomItemDefinition readItemMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; + public abstract CustomItemDefinition readItemMappingEntry(Identifier identifier, JsonNode node) throws InvalidCustomMappingsFileException; public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; - protected static T readOrThrow(JsonNode node, String name, Function converter, String exceptionMessage) throws InvalidCustomMappingsFileException { - JsonNode object = node.get(name); - if (object == null) { - throw new InvalidCustomMappingsFileException(exceptionMessage); - } - return converter.apply(object); - } - - protected static T readOrDefault(JsonNode node, String name, Function converter, T defaultValue) { - JsonNode object = node.get(name); - if (object == null) { - return defaultValue; - } - return converter.apply(object); - } - - protected static void readTextIfPresent(JsonNode node, String name, Consumer consumer) { - readIfPresent(node, name, consumer, JsonNode::asText); - } - - protected static void readIfPresent(JsonNode node, String name, Consumer consumer, Function converter) { - if (node.has(name)) { - consumer.accept(converter.apply(node.get(name))); - } - } - protected static @Nullable CustomRenderOffsets renderOffsetsFromJsonNode(JsonNode node) { if (node == null || !node.isObject()) { return null; 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 00313e299d4..3f1615510e8 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 @@ -41,6 +41,7 @@ import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; import org.geysermc.geyser.level.block.GeyserCustomBlockData; @@ -95,7 +96,7 @@ public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer { try { - CustomItemDefinition customItemData = this.readItemMappingEntry(entry.getKey(), data); + CustomItemDefinition customItemData = this.readItemMappingEntry(new Identifier(entry.getKey()), data); consumer.accept(entry.getKey(), customItemData); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); @@ -160,7 +161,7 @@ private CustomItemOptions readItemCustomItemOptions(JsonNode node) { } @Override - public CustomItemDefinition readItemMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { + public CustomItemDefinition readItemMappingEntry(Identifier identifier, JsonNode node) throws InvalidCustomMappingsFileException { if (node == null || !node.isObject()) { throw new InvalidCustomMappingsFileException("Invalid item mappings entry"); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 9d35c5b7078..ed2dccd30bc 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -45,6 +45,8 @@ import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -63,12 +65,10 @@ public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - // TODO - do we want to continue allowing type conversions, or do we want to enforce strict typing in JSON mappings? - // E.g., do we want to allow reading "210" as 210 and reading 1 as true, or do we throw exceptions in that case? - JsonNode itemModels = mappingsRoot.get("items"); + JsonNode items = mappingsRoot.get("items"); - if (itemModels != null && itemModels.isObject()) { - itemModels.fields().forEachRemaining(entry -> { + if (items != null && items.isObject()) { + items.fields().forEachRemaining(entry -> { if (entry.getValue().isArray()) { entry.getValue().forEach(data -> { try { @@ -78,6 +78,8 @@ public void readItemMappingsV2(Path file, JsonNode mappingsRoot, BiConsumer definitionConsumer) throws InvalidCustomMappingsFileException { - String type = readOrDefault(data, "type", JsonNode::asText, "definition"); + String context = "item definition(s) for Java item " + itemIdentifier; + + String type = MappingsUtil.readOrDefault(data, "type", NodeReader.NON_EMPTY_STRING, "definition", context); if (type.equals("group")) { // Read model of group if it's present, or default to the model of the parent group, if that's present // If the parent group model is not present (or there is no parent group), and this group also doesn't have a model, then it is expected the definitions supply their model themselves - String groupModel = readOrDefault(data, "model", JsonNode::asText, model); + Identifier groupModel = MappingsUtil.readOrDefault(data, "model", NodeReader.IDENTIFIER, model, context); JsonNode definitions = data.get("definitions"); if (definitions == null || !definitions.isArray()) { - throw new InvalidCustomMappingsFileException("An item entry group has no definitions key, or it wasn't an array"); + throw new InvalidCustomMappingsFileException("reading item definitions in group", "group has no definitions key, or it wasn't an array", context); } else { for (JsonNode definition : definitions) { + // Recursively read all the entries in the group - they can be more groups or definitions readItemDefinitionEntry(definition, itemIdentifier, groupModel, definitionConsumer); } } @@ -110,163 +115,141 @@ private void readItemDefinitionEntry(JsonNode data, String itemIdentifier, Strin CustomItemDefinition customItemDefinition = readItemMappingEntry(model, data); definitionConsumer.accept(itemIdentifier, customItemDefinition); } else { - throw new InvalidCustomMappingsFileException("Unknown definition type " + type); + throw new InvalidCustomMappingsFileException("reading item definition", "unknown definition type " + type, context); } } @Override - public CustomItemDefinition readItemMappingEntry(String itemModel, JsonNode node) throws InvalidCustomMappingsFileException { + public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonNode node) throws InvalidCustomMappingsFileException { if (node == null || !node.isObject()) { throw new InvalidCustomMappingsFileException("Invalid item definition entry"); } - JsonNode bedrockIdentifierNode = node.get("bedrock_identifier"); + Identifier bedrockIdentifier = MappingsUtil.readOrThrow(node, "bedrock_identifier", NodeReader.IDENTIFIER, "item definition"); + // We now know the Bedrock identifier, make a base context so that the error can be easily located in the JSON file + String context = "item definition (bedrock identifier=" + bedrockIdentifier + ")"; - JsonNode modelNode = node.get("model"); - String model = modelNode == null || !modelNode.isTextual() ? itemModel : modelNode.asText(); + Identifier model = MappingsUtil.readOrDefault(node, "model", NodeReader.IDENTIFIER, parentModel, context); - if (bedrockIdentifierNode == null || !bedrockIdentifierNode.isTextual() || bedrockIdentifierNode.asText().isEmpty()) { - throw new InvalidCustomMappingsFileException("An item definition has no bedrock identifier"); - } - if (model == null || model.isEmpty()) { - throw new InvalidCustomMappingsFileException("An item definition has no model"); + if (model == null) { + throw new InvalidCustomMappingsFileException("reading item model", "no model present", context); } - Identifier bedrockIdentifier = new Identifier(bedrockIdentifierNode.asText()); if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - bedrockIdentifier = new Identifier(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.path()); + bedrockIdentifier = new Identifier(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.path()); // Use geyser_custom namespace when no namespace or the minecraft namespace was given } - CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, new Identifier(model)); + CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, model); - // We now know the Bedrock identifier, put it in the exception message so that the error can be easily located in the JSON file - try { - readTextIfPresent(node, "display_name", builder::displayName); - readIfPresent(node, "priority", builder::priority, JsonNode::asInt); + MappingsUtil.readTextIfPresent(node, "display_name", builder::displayName, context); + MappingsUtil.readIfPresent(node, "priority", builder::priority, NodeReader.INT, context); - readPredicates(builder, node.get("predicate")); + readPredicates(builder, node.get("predicate"), context); - builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"))); + builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"), context)); - JsonNode componentsNode = node.get("components"); - if (componentsNode != null && componentsNode.isObject()) { + JsonNode componentsNode = node.get("components"); + if (componentsNode != null) { + if (componentsNode.isObject()) { DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? for (Iterator> iterator = componentsNode.fields(); iterator.hasNext();) { Map.Entry entry = iterator.next(); - try { - DataComponentReaders.readDataComponent(components, MinecraftKey.key(entry.getKey()), entry.getValue()); - } catch (InvalidCustomMappingsFileException exception) { - throw new InvalidCustomMappingsFileException("While reading data component " + entry.getKey() + ": " + exception.getMessage()); - } + DataComponentReaders.readDataComponent(components, MinecraftKey.key(entry.getKey()), entry.getValue(), context); } builder.components(components); + } else { + throw new InvalidCustomMappingsFileException("reading components", "expected components key to be an object", context); } - } catch (InvalidCustomMappingsFileException exception) { - throw new InvalidCustomMappingsFileException("While reading item definition (bedrock identifier=" + bedrockIdentifier + "): " + exception.getMessage()); } return builder.build(); } - private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node) { + private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { CustomItemBedrockOptions.Builder builder = CustomItemBedrockOptions.builder(); if (node == null || !node.isObject()) { return builder; } - readTextIfPresent(node, "icon", builder::icon); - readIfPresent(node, "creative_category", builder::creativeCategory, category -> CreativeCategory.fromName(category.asText())); - readTextIfPresent(node, "creative_group", builder::creativeGroup); - readIfPresent(node, "allow_offhand", builder::allowOffhand, JsonNode::asBoolean); - readIfPresent(node, "display_handheld", builder::displayHandheld, JsonNode::asBoolean); - readIfPresent(node, "texture_size", builder::textureSize, JsonNode::asInt); - readIfPresent(node, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode); + String[] context = {"bedrock options", baseContext}; + MappingsUtil.readTextIfPresent(node, "icon", builder::icon, context); + MappingsUtil.readIfPresent(node, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); + MappingsUtil.readTextIfPresent(node, "creative_group", builder::creativeGroup, context); + MappingsUtil.readIfPresent(node, "allow_offhand", builder::allowOffhand, NodeReader.BOOLEAN, context); + MappingsUtil.readIfPresent(node, "display_handheld", builder::displayHandheld, NodeReader.BOOLEAN, context); + MappingsUtil.readIfPresent(node, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); + MappingsUtil.readIfPresent(node, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode, context); if (node.get("tags") instanceof ArrayNode tags) { Set tagsSet = new ObjectOpenHashSet<>(); - tags.forEach(tag -> tagsSet.add(tag.asText())); + for (JsonNode tag : tags) { + tagsSet.add(NodeReader.NON_EMPTY_STRING.read(tag, "reading tag", context)); + } builder.tags(tagsSet); } return builder; } - private void readPredicates(CustomItemDefinition.Builder builder, JsonNode node) throws InvalidCustomMappingsFileException { + private void readPredicates(CustomItemDefinition.Builder builder, JsonNode node, String context) throws InvalidCustomMappingsFileException { if (node == null) { return; } if (node.isObject()) { - readPredicate(builder, node); + readPredicate(builder, node, context); } else if (node.isArray()) { for (JsonNode predicate : node) { - readPredicate(builder, predicate); + readPredicate(builder, predicate, context); } } else { - throw new InvalidCustomMappingsFileException("Expected predicate key to be a list of predicates or a predicate"); + throw new InvalidCustomMappingsFileException("reading predicates", "expected predicate key to be a list of predicates or a predicate", context); } } - private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNode node) throws InvalidCustomMappingsFileException { + private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { if (!node.isObject()) { - throw new InvalidCustomMappingsFileException("Expected predicate to be an object"); + throw new InvalidCustomMappingsFileException("reading predicate", "expected predicate to be an object", baseContext); } - String type = readOrThrow(node, "type", JsonNode::asText, "Predicate requires type key"); - String property = readOrThrow(node, "property", JsonNode::asText, "Predicate requires property key"); + String type = MappingsUtil.readOrThrow(node, "type", NodeReader.NON_EMPTY_STRING, "predicate", baseContext); + String[] context = {type + " predicate", baseContext}; switch (type) { case "condition" -> { - try { - ConditionProperty conditionProperty = ConditionProperty.valueOf(property.toUpperCase()); - JsonNode expected = node.get("expected"); - - // Note that index is only used for the CUSTOM_MODEL_DATA property, but we allow specifying it for other properties anyway - builder.predicate(CustomItemPredicate.condition(conditionProperty, - expected == null || expected.asBoolean(), readOrDefault(node, "index", JsonNode::asInt, 0))); - } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown property " + property); - } + ConditionProperty conditionProperty = MappingsUtil.readOrThrow(node, "property", NodeReader.CONDITION_PROPERTY, context); + boolean expected = MappingsUtil.readOrDefault(node, "expected", NodeReader.BOOLEAN, true, context); + int index = MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context); + + // Note that index is only used for the CUSTOM_MODEL_DATA property, but we allow specifying it for other properties anyway + builder.predicate(CustomItemPredicate.condition(conditionProperty, expected, index)); } case "match" -> { - String value = readOrThrow(node, "value", JsonNode::asText, "Predicate requires value key"); + String property = MappingsUtil.readOrThrow(node, "property", NodeReader.NON_EMPTY_STRING, context); switch (property) { - case "charge_type" -> { - try { - ChargeType chargeType = ChargeType.valueOf(value.toUpperCase()); - builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CHARGE_TYPE, chargeType)); - } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown charge type " + value); - } - } - case "trim_material" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.TRIM_MATERIAL, new Identifier(value))); - case "context_dimension" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CONTEXT_DIMENSION, new Identifier(value))); + case "charge_type" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CHARGE_TYPE, + MappingsUtil.readOrThrow(node, "value", NodeReader.CHARGE_TYPE, context))); + case "trim_material" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.TRIM_MATERIAL, + MappingsUtil.readOrThrow(node, "value", NodeReader.IDENTIFIER, context))); + case "context_dimension" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CONTEXT_DIMENSION, + MappingsUtil.readOrThrow(node, "value", NodeReader.IDENTIFIER, context))); case "custom_model_data" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CUSTOM_MODEL_DATA, - new CustomModelDataString(value, readOrDefault(node, "index", JsonNode::asInt, 0)))); - default -> throw new InvalidCustomMappingsFileException("Unknown property " + property); + new CustomModelDataString(MappingsUtil.readOrThrow(node, "value", NodeReader.STRING, context), + MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context)))); + default -> throw new InvalidCustomMappingsFileException("reading match predicate", "unknown property " + property, context); } } case "range_dispatch" -> { - double threshold = readOrThrow(node, "threshold", JsonNode::asDouble, "Predicate requires threshold key"); - JsonNode scaleNode = node.get("scale"); - double scale = 1.0; - if (scaleNode != null && scaleNode.isNumber()) { - scale = scaleNode.asDouble(); - } + RangeDispatchProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.RANGE_DISPATCH_PROPERTY, context); - JsonNode normalizeNode = node.get("normalize"); - boolean normalizeIfPossible = normalizeNode != null && normalizeNode.booleanValue(); + double threshold = MappingsUtil.readOrThrow(node, "threshold", NodeReader.DOUBLE, context); + double scale = MappingsUtil.readOrDefault(node, "scale", NodeReader.DOUBLE, 1.0, context); + boolean normalizeIfPossible = MappingsUtil.readOrDefault(node, "normalize", NodeReader.BOOLEAN, false, context); + int index = MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context); - int index = readOrDefault(node, "index", JsonNode::asInt, 0); - - try { - RangeDispatchProperty rangeDispatchProperty = RangeDispatchProperty.valueOf(property.toUpperCase()); - builder.predicate(CustomItemPredicate.rangeDispatch(rangeDispatchProperty, threshold, scale, normalizeIfPossible, index)); - } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("Unknown property " + property); - } + builder.predicate(CustomItemPredicate.rangeDispatch(property, threshold, scale, normalizeIfPossible, index)); } - default -> throw new InvalidCustomMappingsFileException("Unknown predicate type " + type); + default -> throw new InvalidCustomMappingsFileException("reading predicate", "unknown predicate type " + type, context); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index b8104e7cdc3..3c54ef0903c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -100,7 +100,7 @@ public static void populate(Map items, Multimap Date: Fri, 10 Jan 2025 16:21:03 +0000 Subject: [PATCH 064/118] Small cleanup --- .../registry/mappings/components/DataComponentReader.java | 6 ------ .../geyser/registry/mappings/util/MappingsUtil.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index e4e6dd6ee8d..3f8699605c3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -43,10 +43,4 @@ protected DataComponentReader(DataComponentType type) { void read(DataComponents components, JsonNode node, String... context) throws InvalidCustomMappingsFileException { components.put(type, readDataComponent(node, context)); } - - protected static void requireObject(JsonNode node) throws InvalidCustomMappingsFileException { - if (!node.isObject()) { - throw new InvalidCustomMappingsFileException("Expected an object"); - } - } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java index 4f22ae7cf24..d403371f156 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -35,7 +35,7 @@ public class MappingsUtil { public static T readOrThrow(JsonNode node, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { JsonNode object = node.get(name); if (object == null) { - throw new InvalidCustomMappingsFileException(formatTask(name), "key " + name + " is required but was not present", context); + throw new InvalidCustomMappingsFileException(formatTask(name), "key is required but was not present", context); } return converter.read(object, formatTask(name), context); } From aa13113cee5fea51eaef7125f7e4ff3afa203401 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 10 Jan 2025 16:33:44 +0000 Subject: [PATCH 065/118] Clean up predicates in the API --- .../v2/predicate/ConditionItemPredicate.java | 38 ++++++++++++++ .../v2/predicate/CustomItemPredicate.java | 50 +++++++++++++------ .../v2/predicate/MatchItemPredicate.java | 38 ++++++++++++++ .../predicate/RangeDispatchItemPredicate.java | 42 ++++++++++++++++ .../v2/predicate/ConditionPredicate.java | 11 +--- .../custom/v2/predicate/MatchPredicate.java | 10 +--- .../v2/predicate/RangeDispatchPredicate.java | 26 +--------- .../loader/ProviderRegistryLoader.java | 12 ++--- 8 files changed, 164 insertions(+), 63 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchItemPredicate.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java new file mode 100644 index 00000000000..a266ec78be3 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +/** + * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#condition(ConditionProperty, boolean, int) + */ +public interface ConditionItemPredicate extends CustomItemPredicate { + + ConditionProperty property(); + + boolean expected(); + + int index(); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java index 2aa971f465d..2bd08643241 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -30,43 +30,61 @@ public interface CustomItemPredicate { - int CONDITION = 0; - int MATCH = 1; - int RANGE_DISPATCH = 2; - - static CustomItemPredicate condition(ConditionProperty property) { + static ConditionItemPredicate condition(ConditionProperty property) { return condition(property, true); } - static CustomItemPredicate condition(ConditionProperty property, boolean expected) { + static ConditionItemPredicate condition(ConditionProperty property, boolean expected) { return condition(property, expected, 0); } - static CustomItemPredicate condition(ConditionProperty property, boolean expected, int index) { - return GeyserApi.api().provider(CustomItemPredicate.class, CONDITION, property, expected, index); + /** + * A predicate that checks for a certain boolean property of the item stack and returns true if it matches the expected value. + * + * @param property the property to check. + * @param expected whether the property should be true or false. Defaults to true. + * @param index only used for the {@link ConditionProperty#CUSTOM_MODEL_DATA} property, determines which flag of the item's custom model data to check. Defaults to 0. + */ + static ConditionItemPredicate condition(ConditionProperty property, boolean expected, int index) { + return GeyserApi.api().provider(ConditionItemPredicate.class, property, expected, index); } - static CustomItemPredicate match(MatchPredicateProperty property, T data) { - return GeyserApi.api().provider(CustomItemPredicate.class, MATCH, property, data); + /** + * A predicate that matches a property of the item stack and returns true if it matches the expected value. + * + * @param property the property to check for. + * @param data the value expected. + */ + static MatchItemPredicate match(MatchPredicateProperty property, T data) { + return GeyserApi.api().provider(MatchItemPredicate.class, property, data); } - static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold) { return rangeDispatch(property, threshold, 1.0); } - static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale) { return rangeDispatch(property, threshold, scale, false, 0); } - static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, boolean normalizeIfPossible) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, boolean normalizeIfPossible) { return rangeDispatch(property, threshold, 1.0, normalizeIfPossible, 0); } - static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { return rangeDispatch(property, threshold, scale, normalizeIfPossible, 0); } - static CustomItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) { - return GeyserApi.api().provider(CustomItemPredicate.class, RANGE_DISPATCH, property, threshold, scale, normalizeIfPossible, index); + /** + * A predicate that checks for a certain numeric property of the item stack and returns true if it is above the specified threshold. + * + * @param property the property to check. + * @param threshold the threshold the property should be above. + * @param scale factor to multiply the property value with before comparing it with the threshold. Defaults to 1.0. + * @param normalizeIfPossible if the property value should be normalised to a value between 0.0 and 1.0 before scaling and comparing. Defaults to false. Only works for certain properties. + * @param index only used for the {@link RangeDispatchProperty#CUSTOM_MODEL_DATA} property, determines which float of the item's custom model data to check. Defaults to 0. + */ + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) { + return GeyserApi.api().provider(RangeDispatchItemPredicate.class, property, threshold, scale, normalizeIfPossible, index); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchItemPredicate.java new file mode 100644 index 00000000000..6b099cd24a6 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchItemPredicate.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; + +/** + * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#match(MatchPredicateProperty, Object) + */ +public interface MatchItemPredicate extends CustomItemPredicate { + + MatchPredicateProperty property(); + + T data(); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java new file mode 100644 index 00000000000..d0269c16915 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +/** + * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#rangeDispatch(RangeDispatchProperty, double, double, boolean, int) + */ +public interface RangeDispatchItemPredicate extends CustomItemPredicate { + + RangeDispatchProperty property(); + + double threshold(); + + double scale(); + + boolean normalizeIfPossible(); + + int index(); +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java index 3c82a8b884b..67b64eb3ac2 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java @@ -25,15 +25,8 @@ package org.geysermc.geyser.item.custom.v2.predicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -/** - * A predicate that checks for a certain boolean property of the item stack and returns true if it matches the expected value. - * - * @param property the property to check. - * @param expected whether the property should be true or false. Defaults to true. - * @param index only used for the {@code CUSTOM_MODEL_DATA} property, determines which flag of the item's custom model data to check. Defaults to 0. - */ -public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate { +public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements ConditionItemPredicate { } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java index 135d8af3e6a..edc9a9f1535 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java @@ -25,14 +25,8 @@ package org.geysermc.geyser.item.custom.v2.predicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; -/** - * A predicate that matches a property of the item stack and returns true if it matches the expected value. - * - * @param property the property to check for. - * @param data the value expected. - */ -public record MatchPredicate(MatchPredicateProperty property, T data) implements CustomItemPredicate { +public record MatchPredicate(MatchPredicateProperty property, T data) implements MatchItemPredicate { } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java index a68bb5df0ba..49796182767 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java @@ -25,30 +25,8 @@ package org.geysermc.geyser.item.custom.v2.predicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; -/** - * A predicate that checks for a certain numeric property of the item stack and returns true if it is above the specified threshold. - * - * @param property the property to check. - * @param threshold the threshold the property should be above. - * @param scale factor to multiply the property value with before comparing it with the threshold. Defaults to 1.0. - * @param normalizeIfPossible if the property value should be normalised to a value between 0.0 and 1.0 before scaling and comparing. Defaults to false. Only works for certain properties. - * @param index only used for the {@code CUSTOM_MODEL_DATA} property, determines which float of the item's custom model data to check. Defaults to 0. - */ -public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements CustomItemPredicate { - - public RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { - this(property, threshold, scale, normalizeIfPossible, 0); - } - - public RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale) { - this(property, threshold, scale, false); - } - - public RangeDispatchPredicate(RangeDispatchProperty property, double threshold) { - this(property, threshold, 1.0); - } - +public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements RangeDispatchItemPredicate { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 81089542abc..5c0969de27d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -41,8 +41,11 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.pack.PathPackCodec; @@ -99,12 +102,9 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov // items v2 providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Identifier) args[0], (Identifier) args[1])); providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); - providers.put(CustomItemPredicate.class, args -> switch ((int) args[0]) { - case CustomItemPredicate.CONDITION -> new ConditionPredicate((ConditionProperty) args[1], (boolean) args[2], (int) args[3]); - case CustomItemPredicate.MATCH -> new MatchPredicate<>((MatchPredicateProperty) args[1], args[2]); - case CustomItemPredicate.RANGE_DISPATCH -> new RangeDispatchPredicate((RangeDispatchProperty) args[1], (double) args[2], (double) args[3], (boolean) args[4], (int) args[5]); - default -> throw new IllegalArgumentException("Unknown predicate"); - }); + providers.put(ConditionItemPredicate.class, args -> new ConditionPredicate((ConditionProperty) args[0], (boolean) args[1], (int) args[2])); + providers.put(MatchItemPredicate.class, args -> new MatchPredicate<>((MatchPredicateProperty) args[0], args[1])); + providers.put(RangeDispatchItemPredicate.class, args -> new RangeDispatchPredicate((RangeDispatchProperty) args[0], (double) args[1], (double) args[2], (boolean) args[3], (int) args[4])); // cameras providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); From 7af9518d6668e38c88119c7bf29d402e3195f1f7 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 10 Jan 2025 16:45:15 +0000 Subject: [PATCH 066/118] Implement predicate strategies --- .../item/custom/v2/CustomItemDefinition.java | 14 +++++-- .../v2/predicate/PredicateStrategy.java | 37 +++++++++++++++++++ .../custom/GeyserCustomItemDefinition.java | 26 +++++++++---- .../translator/item/CustomItemTranslator.java | 5 +++ 4 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/PredicateStrategy.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index a34f25e8274..170d8276386 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -28,6 +28,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -84,10 +85,15 @@ public interface CustomItemDefinition { } /** - * The predicates that have to match for this item to be used. These predicates are similar to the Java item model predicates. + * The predicates that have to match for this item definition to be used. These predicates are similar to the Java item model predicates. */ @NonNull List predicates(); + /** + * The predicate strategy to be used. Determines if one of, or all of the predicates have to pass for this item definition to be used. Defaults to {@link PredicateStrategy#AND}. + */ + PredicateStrategy predicateStrategy(); + /** * The priority of this definition. For all definitions for a single Java item model, definitions with a higher priority will be matched first. Defaults to 0. */ @@ -125,11 +131,13 @@ interface Builder { Builder displayName(String displayName); - Builder predicate(@NonNull CustomItemPredicate predicate); + Builder priority(int priority); Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); - Builder priority(int priority); + Builder predicate(@NonNull CustomItemPredicate predicate); + + Builder predicateStrategy(@NonNull PredicateStrategy strategy); // TODO do we want a component(Type, Value) method instead? Builder components(@NonNull DataComponents components); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/PredicateStrategy.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/PredicateStrategy.java new file mode 100644 index 00000000000..6168112bb56 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/PredicateStrategy.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate; + +public enum PredicateStrategy { + /** + * Require all predicates to pass for the item to be used. + */ + AND, + /** + * Require only one of the predicates to pass for the item to be used. + */ + OR +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index b6c2128b23c..08671658c54 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -29,6 +29,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -37,15 +38,18 @@ import java.util.List; public record GeyserCustomItemDefinition(@NonNull Identifier bedrockIdentifier, String displayName, @NonNull Identifier model, @NonNull List predicates, + PredicateStrategy predicateStrategy, int priority, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { private final Identifier bedrockIdentifier; private final Identifier model; private final List predicates = new ArrayList<>(); - private int priority = 0; + private String displayName; + private int priority = 0; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); + private PredicateStrategy predicateStrategy = PredicateStrategy.AND; private DataComponents components = new DataComponents(new HashMap<>()); public Builder(Identifier bedrockIdentifier, Identifier model) { @@ -60,12 +64,6 @@ public CustomItemDefinition.Builder displayName(String displayName) { return this; } - @Override - public CustomItemDefinition.Builder predicate(@NonNull CustomItemPredicate predicate) { - predicates.add(predicate); - return this; - } - @Override public CustomItemDefinition.Builder priority(int priority) { this.priority = priority; @@ -78,6 +76,18 @@ public CustomItemDefinition.Builder bedrockOptions(CustomItemBedrockOptions.@Non return this; } + @Override + public CustomItemDefinition.Builder predicate(@NonNull CustomItemPredicate predicate) { + predicates.add(predicate); + return this; + } + + @Override + public CustomItemDefinition.Builder predicateStrategy(@NonNull PredicateStrategy strategy) { + predicateStrategy = strategy; + return this; + } + @Override public CustomItemDefinition.Builder components(@NonNull DataComponents components) { this.components = components; @@ -86,7 +96,7 @@ public CustomItemDefinition.Builder components(@NonNull DataComponents component @Override public CustomItemDefinition build() { - return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), priority, bedrockOptions, components); + return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), predicateStrategy, priority, bedrockOptions, components); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 7306bf58954..9382beed4c5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -31,6 +31,7 @@ import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; @@ -82,12 +83,16 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, // Cache predicate values so they're not recalculated every time when there are multiple item definitions using the same predicates Object2BooleanMap calculatedPredicates = new Object2BooleanOpenHashMap<>(); for (GeyserCustomMappingData customMapping : customItems) { + boolean needsOnlyOneMatch = customMapping.definition().predicateStrategy() == PredicateStrategy.OR; boolean allMatch = true; + for (CustomItemPredicate predicate : customMapping.definition().predicates()) { boolean value = calculatedPredicates.computeIfAbsent(predicate, x -> predicateMatches(session, predicate, stackSize, components)); if (!value) { allMatch = false; break; + } else if (needsOnlyOneMatch) { + break; } } if (allMatch) { From f4c9c3d41b7e6469780e46890f8bbcc7e0b769e6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 10 Jan 2025 16:48:17 +0000 Subject: [PATCH 067/118] Read predicate strategies from mappings --- .../org/geysermc/geyser/registry/mappings/util/NodeReader.java | 3 +++ .../geyser/registry/mappings/versions/MappingsReader_v2.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 7d48365d9c8..7f5aa3da895 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.util.CreativeCategory; @@ -96,6 +97,8 @@ public interface NodeReader { NodeReader CREATIVE_CATEGORY = NON_EMPTY_STRING.andThen(CreativeCategory::fromName).validate(Objects::nonNull, "unknown creative category"); + NodeReader PREDICATE_STRATEGY = ofEnum(PredicateStrategy.class); + NodeReader CONDITION_PROPERTY = ofEnum(ConditionProperty.class); NodeReader CHARGE_TYPE = ofEnum(ChargeType.class); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index ed2dccd30bc..d0765990d8b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; @@ -144,6 +145,7 @@ public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonNod MappingsUtil.readIfPresent(node, "priority", builder::priority, NodeReader.INT, context); readPredicates(builder, node.get("predicate"), context); + MappingsUtil.readIfPresent(node, "predicate_strategy", builder::predicateStrategy, NodeReader.PREDICATE_STRATEGY, context); builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"), context)); From 46e6fd33be2edd0a8ffb35fb416194d329334ea5 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 10:31:23 +0000 Subject: [PATCH 068/118] Remove MCPL from API module, but now stuff is broken. Also still needs component verification --- api/build.gradle.kts | 6 --- .../item/custom/v2/CustomItemDefinition.java | 8 ++-- .../item/custom/v2/component/Consumable.java | 42 +++++++++++++++++++ .../custom/v2/component/DataComponentMap.java | 31 ++++++++++++++ .../v2/component/DataComponentType.java | 41 ++++++++++++++++++ .../item/custom/v2/component/Equippable.java | 36 ++++++++++++++++ .../custom/v2/component/FoodProperties.java | 29 +++++++++++++ .../item/custom/v2/component/UseCooldown.java | 31 ++++++++++++++ 8 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 80ad07e69c6..eac02ebeb4c 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -8,12 +8,6 @@ plugins { dependencies { api(libs.base.api) api(libs.math) - - // TODO remove MCPL from API - api(libs.mcprotocollib) { - exclude("io.netty", "netty-all") - exclude("net.raphimc", "MinecraftAuth") - } } version = property("version")!! diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 170d8276386..6263b57f132 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -27,10 +27,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.util.Identifier; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.List; @@ -121,7 +122,7 @@ public interface CustomItemDefinition { *

Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, * and do not have to be specified here.

*/ - @NonNull DataComponents components(); + @NonNull DataComponentMap components(); static Builder builder(Identifier identifier, Identifier itemModel) { return GeyserApi.api().provider(Builder.class, identifier, itemModel); @@ -139,8 +140,7 @@ interface Builder { Builder predicateStrategy(@NonNull PredicateStrategy strategy); - // TODO do we want a component(Type, Value) method instead? - Builder components(@NonNull DataComponents components); + Builder components(@NonNull DataComponentType component, @NonNull T value); CustomItemDefinition build(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java new file mode 100644 index 00000000000..f2a5a232359 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +public record Consumable(float consumeSeconds, Animation animation) { + + public enum Animation { + NONE, + EAT, + DRINK, + BLOCK, + BOW, + SPEAR, + CROSSBOW, + SPYGLASS, + TOOT_HORN, + BRUSH + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java new file mode 100644 index 00000000000..560f5c380eb --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +public interface DataComponentMap { + + T get(DataComponentType type); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java new file mode 100644 index 00000000000..cf41f157211 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +public final class DataComponentType { + public static final DataComponentType CONSUMABLE = create(); + public static final DataComponentType EQUIPPABLE = create(); + public static final DataComponentType FOOD = create(); + public static final DataComponentType MAX_DAMAGE = create(); + public static final DataComponentType MAX_STACK_SIZE = create(); + public static final DataComponentType USE_COOLDOWN = create(); + + private DataComponentType() {} + + private static DataComponentType create() { + return new DataComponentType<>(); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java new file mode 100644 index 00000000000..7646209dc14 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +public record Equippable(EquipmentSlot slot) { + + public enum EquipmentSlot { + HEAD, + CHEST, + LEGS, + FEET + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java new file mode 100644 index 00000000000..049704c3b99 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +public record FoodProperties(int nutrition, float saturation, boolean canAlwaysEat) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java new file mode 100644 index 00000000000..803d652be02 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +import org.geysermc.geyser.api.util.Identifier; + +public record UseCooldown(float seconds, Identifier cooldownGroup) { +} From 03402d4220e1d94cdb282f0eaae421eb52ed1dc9 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 11:22:59 +0000 Subject: [PATCH 069/118] Update implementations and mappings reader, but populator is broken --- .../item/custom/v2/CustomItemDefinition.java | 5 ++-- .../custom/GeyserCustomItemDefinition.java | 26 +++++++++++++----- .../components/DataComponentReader.java | 8 +++--- .../components/DataComponentReaders.java | 12 ++++----- .../components/readers/ConsumableReader.java | 11 +++----- .../components/readers/EquippableReader.java | 23 +++------------- ...dReader.java => FoodPropertiesReader.java} | 10 +++---- .../readers/IntComponentReader.java | 2 +- .../components/readers/UseCooldownReader.java | 7 +++-- .../registry/mappings/util/NodeReader.java | 7 +++-- .../mappings/versions/MappingsReader_v2.java | 7 +---- .../CustomItemRegistryPopulator.java | 27 ++++++++++--------- gradle/libs.versions.toml | 3 ++- 13 files changed, 72 insertions(+), 76 deletions(-) rename core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/{FoodReader.java => FoodPropertiesReader.java} (86%) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 6263b57f132..68a58fcd1ac 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -122,7 +122,8 @@ public interface CustomItemDefinition { *

Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, * and do not have to be specified here.

*/ - @NonNull DataComponentMap components(); + @NonNull + DataComponentMap components(); static Builder builder(Identifier identifier, Identifier itemModel) { return GeyserApi.api().provider(Builder.class, identifier, itemModel); @@ -140,7 +141,7 @@ interface Builder { Builder predicateStrategy(@NonNull PredicateStrategy strategy); - Builder components(@NonNull DataComponentType component, @NonNull T value); + Builder component(@NonNull DataComponentType component, @NonNull T value); CustomItemDefinition build(); } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 08671658c54..5ba6abd652e 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -25,32 +25,35 @@ package org.geysermc.geyser.item.custom; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.util.Identifier; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.function.BiConsumer; public record GeyserCustomItemDefinition(@NonNull Identifier bedrockIdentifier, String displayName, @NonNull Identifier model, @NonNull List predicates, PredicateStrategy predicateStrategy, - int priority, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponents components) implements CustomItemDefinition { + int priority, @NonNull CustomItemBedrockOptions bedrockOptions, @NonNull DataComponentMap components) implements CustomItemDefinition { public static class Builder implements CustomItemDefinition.Builder { private final Identifier bedrockIdentifier; private final Identifier model; private final List predicates = new ArrayList<>(); + private final Reference2ObjectMap, Object> components = new Reference2ObjectOpenHashMap<>(); private String displayName; private int priority = 0; private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build(); private PredicateStrategy predicateStrategy = PredicateStrategy.AND; - private DataComponents components = new DataComponents(new HashMap<>()); public Builder(Identifier bedrockIdentifier, Identifier model) { this.bedrockIdentifier = bedrockIdentifier; @@ -89,14 +92,23 @@ public CustomItemDefinition.Builder predicateStrategy(@NonNull PredicateStrategy } @Override - public CustomItemDefinition.Builder components(@NonNull DataComponents components) { - this.components = components; + public CustomItemDefinition.Builder component(@NonNull DataComponentType component, @NonNull T value) { + components.put(component, value); return this; } @Override public CustomItemDefinition build() { - return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), predicateStrategy, priority, bedrockOptions, components); + return new GeyserCustomItemDefinition(bedrockIdentifier, displayName, model, List.copyOf(predicates), predicateStrategy, priority, bedrockOptions, + new GeyserCustomItemDefinition.ComponentMap(components)); + } + } + + private record ComponentMap(Reference2ObjectMap, Object> components) implements DataComponentMap { + + @Override + public T get(DataComponentType type) { + return (T) components.get(type); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index 3f8699605c3..0c3496740e8 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -27,9 +27,9 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; public abstract class DataComponentReader { private final DataComponentType type; @@ -40,7 +40,7 @@ protected DataComponentReader(DataComponentType type) { protected abstract V readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException; - void read(DataComponents components, JsonNode node, String... context) throws InvalidCustomMappingsFileException { - components.put(type, readDataComponent(node, context)); + void read(CustomItemDefinition.Builder builder, JsonNode node, String... context) throws InvalidCustomMappingsFileException { + builder.component(type, readDataComponent(node, context)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 457bd29cedf..5b84b34e9f4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -28,15 +28,15 @@ import com.fasterxml.jackson.databind.JsonNode; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.readers.ConsumableReader; import org.geysermc.geyser.registry.mappings.components.readers.EquippableReader; -import org.geysermc.geyser.registry.mappings.components.readers.FoodReader; +import org.geysermc.geyser.registry.mappings.components.readers.FoodPropertiesReader; import org.geysermc.geyser.registry.mappings.components.readers.IntComponentReader; import org.geysermc.geyser.registry.mappings.components.readers.UseCooldownReader; import org.geysermc.geyser.util.MinecraftKey; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.util.HashMap; import java.util.Map; @@ -44,18 +44,18 @@ public class DataComponentReaders { private static final Map> READERS = new HashMap<>(); - public static void readDataComponent(DataComponents components, Key key, @NonNull JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { + public static void readDataComponent(CustomItemDefinition.Builder builder, Key key, @NonNull JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { DataComponentReader reader = READERS.get(key); if (reader == null) { throw new InvalidCustomMappingsFileException("reading data components", "unknown data component " + key, baseContext); } - reader.read(components, node, "component " + key, baseContext); + reader.read(builder, node, "component " + key, baseContext); } static { READERS.put(MinecraftKey.key("consumable"), new ConsumableReader()); READERS.put(MinecraftKey.key("equippable"), new EquippableReader()); - READERS.put(MinecraftKey.key("food"), new FoodReader()); + READERS.put(MinecraftKey.key("food"), new FoodPropertiesReader()); READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponentType.MAX_DAMAGE, 0)); READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponentType.MAX_STACK_SIZE, 1, 99)); READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java index 3d4be7578a2..ea4797d0f78 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -27,15 +27,12 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.Consumable; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; import org.geysermc.geyser.registry.mappings.util.NodeReader; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; - -import java.util.List; public class ConsumableReader extends DataComponentReader { @@ -48,8 +45,8 @@ protected Consumable readDataComponent(@NonNull JsonNode node, String... context MappingsUtil.requireObject(node, "reading component", context); float consumeSeconds = MappingsUtil.readOrDefault(node, "consume_seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), 1.6F, context); - Consumable.ItemUseAnimation animation = MappingsUtil.readOrDefault(node, "animation", NodeReader.ITEM_USE_ANIMATION, Consumable.ItemUseAnimation.EAT, context); + Consumable.Animation animation = MappingsUtil.readOrDefault(node, "animation", NodeReader.CONSUMABLE_ANIMATION, Consumable.Animation.EAT, context); - return new Consumable(consumeSeconds, animation, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of()); // TODO are sound and particles supported on bedrock? + return new Consumable(consumeSeconds, animation); // TODO are sound and particles supported on bedrock? } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java index 193cb725525..8f1044da688 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -27,25 +27,14 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.Equippable; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; import org.geysermc.geyser.registry.mappings.util.NodeReader; -import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; -import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; - -import java.util.Map; -import java.util.Objects; public class EquippableReader extends DataComponentReader { - private static final Map SLOTS = Map.of( - "head", EquipmentSlot.HELMET, - "chest", EquipmentSlot.CHESTPLATE, - "legs", EquipmentSlot.LEGGINGS, - "feet", EquipmentSlot.BOOTS - ); public EquippableReader() { super(DataComponentType.EQUIPPABLE); @@ -54,11 +43,7 @@ public EquippableReader() { @Override protected Equippable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { MappingsUtil.requireObject(node, "reading component", context); - - EquipmentSlot slot = MappingsUtil.readOrThrow(node, "slot", - NodeReader.NON_EMPTY_STRING.andThen(SLOTS::get).validate(Objects::nonNull, "expected slot to be head, chest, legs or feet"), context); - - return new Equippable(slot, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, - null, null, null, false, false, false); // Other properties are unused + Equippable.EquipmentSlot slot = MappingsUtil.readOrThrow(node, "slot", NodeReader.EQUIPMENT_SLOT, context); + return new Equippable(slot); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java similarity index 86% rename from core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java rename to core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java index 742fe99284c..022da911944 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java @@ -27,16 +27,16 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.FoodProperties; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; import org.geysermc.geyser.registry.mappings.util.NodeReader; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; -public class FoodReader extends DataComponentReader { +public class FoodPropertiesReader extends DataComponentReader { - public FoodReader() { + public FoodPropertiesReader() { super(DataComponentType.FOOD); } @@ -45,7 +45,7 @@ protected FoodProperties readDataComponent(@NonNull JsonNode node, String... con MappingsUtil.requireObject(node, "reading component", context); int nutrition = MappingsUtil.readOrDefault(node, "nutrition", NodeReader.NON_NEGATIVE_INT, 0, context); - float saturationModifier = MappingsUtil.readOrDefault(node, "saturation_modifier", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); + float saturationModifier = MappingsUtil.readOrDefault(node, "saturation", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); boolean canAlwaysEat = MappingsUtil.readOrDefault(node, "can_always_eat", NodeReader.BOOLEAN, false, context); return new FoodProperties(nutrition, saturationModifier, canAlwaysEat); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java index ea6f61321c2..11a920d7f26 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java @@ -27,10 +27,10 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.NodeReader; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; public class IntComponentReader extends DataComponentReader { private final int minimum; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java index f8a14893298..1f50d888ef8 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java @@ -27,14 +27,13 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.UseCooldown; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; import org.geysermc.geyser.registry.mappings.util.NodeReader; -import org.geysermc.geyser.util.MinecraftKey; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; public class UseCooldownReader extends DataComponentReader { @@ -47,6 +46,6 @@ protected UseCooldown readDataComponent(@NonNull JsonNode node, String... contex float seconds = MappingsUtil.readOrThrow(node, "seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), context); Identifier cooldownGroup = MappingsUtil.readOrThrow(node, "cooldown_group", NodeReader.IDENTIFIER, context); - return new UseCooldown(seconds, MinecraftKey.identifierToKey(cooldownGroup)); + return new UseCooldown(seconds, cooldownGroup); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 7f5aa3da895..4c7e6869e92 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.registry.mappings.util; import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.api.item.custom.v2.component.Consumable; +import org.geysermc.geyser.api.item.custom.v2.component.Equippable; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; @@ -33,7 +35,6 @@ import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; import java.util.Objects; import java.util.function.Predicate; @@ -105,7 +106,9 @@ public interface NodeReader { NodeReader RANGE_DISPATCH_PROPERTY = ofEnum(RangeDispatchProperty.class); - NodeReader ITEM_USE_ANIMATION = ofEnum(Consumable.ItemUseAnimation.class); + NodeReader CONSUMABLE_ANIMATION = ofEnum(Consumable.Animation.class); + + NodeReader EQUIPMENT_SLOT = ofEnum(Equippable.EquipmentSlot.class); static > NodeReader ofEnum(Class clazz) { return NON_EMPTY_STRING.andThen(String::toUpperCase).andThen(s -> { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index d0765990d8b..84c2aa47cd4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -36,12 +36,9 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; -import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; -import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; @@ -152,12 +149,10 @@ public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonNod JsonNode componentsNode = node.get("components"); if (componentsNode != null) { if (componentsNode.isObject()) { - DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ? for (Iterator> iterator = componentsNode.fields(); iterator.hasNext();) { Map.Entry entry = iterator.next(); - DataComponentReaders.readDataComponent(components, MinecraftKey.key(entry.getKey()), entry.getValue(), context); + DataComponentReaders.readDataComponent(builder, MinecraftKey.key(entry.getKey()), entry.getValue(), context); } - builder.components(components); } else { throw new InvalidCustomMappingsFileException("reading components", "expected components key to be an object", context); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 3c54ef0903c..bdcc4a7bce0 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -41,6 +41,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; @@ -55,13 +57,13 @@ import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -202,10 +204,10 @@ private static Optional checkPredicate(Map.Entry 1) { + if (components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.EQUIPPABLE) != null && stackSize > 1) { throw new InvalidItemComponentsException("Bedrock doesn't support equippable items with a stack size above 1"); } else if (stackSize > 1 && maxDamage > 0) { throw new InvalidItemComponentsException("Stack size must be 1 when max damage is above 0"); @@ -234,7 +236,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - Equippable equippable = components.get(DataComponentType.EQUIPPABLE); + Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.EQUIPPABLE); if (equippable != null) { computeArmorProperties(equippable, itemProperties, componentBuilder); } @@ -243,9 +245,9 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder); } - Consumable consumable = components.get(DataComponentType.CONSUMABLE); + Consumable consumable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.CONSUMABLE); if (consumable != null) { - FoodProperties foodProperties = components.get(DataComponentType.FOOD); + FoodProperties foodProperties = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.FOOD); computeConsumableProperties(consumable, foodProperties, itemProperties, componentBuilder); } @@ -253,7 +255,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeEntityPlacerProperties(componentBuilder); } - UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN); + UseCooldown useCooldown = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.USE_COOLDOWN); if (useCooldown != null) { computeUseCooldownProperties(useCooldown, componentBuilder); } @@ -313,10 +315,10 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); itemProperties.putBoolean("hand_equipped", options.displayHandheld()); - int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); - Equippable equippable = components.get(DataComponentType.EQUIPPABLE); + int maxDamage = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.MAX_DAMAGE, 0); + Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.EQUIPPABLE); // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped - int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's itemProperties.putInt("max_stack_size", stackSize); if (maxDamage > 0 && !isUnbreakableItem(definition)) { @@ -660,7 +662,8 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { } private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { - return javaItem.gatherComponents(definition.components()); + //return javaItem.gatherComponents(definition.components()); + return javaItem.gatherComponents(new DataComponents(new HashMap<>())); // TODO FIXME } @SuppressWarnings("unchecked") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e1c94b8341d..90f31450a34 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,6 +72,7 @@ fastutil-int-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-i fastutil-object-int-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-int-maps", version.ref = "fastutil" } fastutil-object-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-object-maps", version.ref = "fastutil" } fastutil-object-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-boolean-maps", version.ref = "fastutil" } +fastutil-reference-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-reference-object-maps", version.ref = "fastutil" } adventure-text-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" } # Remove when we remove our Adventure bump adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" } @@ -154,7 +155,7 @@ blossom = { id = "net.kyori.blossom", version.ref = "blossom" } [bundles] jackson = [ "jackson-annotations", "jackson-databind", "jackson-dataformat-yaml" ] -fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps", "fastutil-object-boolean-maps" ] +fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps", "fastutil-object-boolean-maps", "fastutil-reference-object-maps" ] adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ] log4j = [ "log4j-api", "log4j-core", "log4j-slf4j2-impl" ] jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ] From aabd5370799299f49d7f7906ca7e51922e75da86 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 11:39:48 +0000 Subject: [PATCH 070/118] My IDE did something it shouldn't have --- .../geyser/api/item/custom/v2/CustomItemDefinition.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 68a58fcd1ac..05f660e97dc 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -122,8 +122,7 @@ public interface CustomItemDefinition { *

Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, * and do not have to be specified here.

*/ - @NonNull - DataComponentMap components(); + @NonNull DataComponentMap components(); static Builder builder(Identifier identifier, Identifier itemModel) { return GeyserApi.api().provider(Builder.class, identifier, itemModel); From d06ad0ba36bfa0b025282c2aedb46a7847bcc2d0 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 11:45:55 +0000 Subject: [PATCH 071/118] Improve enum reader error message --- .../mappings/components/readers/FoodPropertiesReader.java | 4 ++-- .../geysermc/geyser/registry/mappings/util/NodeReader.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java index 022da911944..4d7a3bcbc20 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java @@ -45,9 +45,9 @@ protected FoodProperties readDataComponent(@NonNull JsonNode node, String... con MappingsUtil.requireObject(node, "reading component", context); int nutrition = MappingsUtil.readOrDefault(node, "nutrition", NodeReader.NON_NEGATIVE_INT, 0, context); - float saturationModifier = MappingsUtil.readOrDefault(node, "saturation", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); + float saturation = MappingsUtil.readOrDefault(node, "saturation", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); boolean canAlwaysEat = MappingsUtil.readOrDefault(node, "can_always_eat", NodeReader.BOOLEAN, false, context); - return new FoodProperties(nutrition, saturationModifier, canAlwaysEat); + return new FoodProperties(nutrition, saturation, canAlwaysEat); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 4c7e6869e92..88d08432209 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import java.util.Arrays; import java.util.Objects; import java.util.function.Predicate; @@ -115,7 +116,8 @@ static > NodeReader ofEnum(Class clazz) { try { return Enum.valueOf(clazz, s); } catch (IllegalArgumentException exception) { - throw new InvalidCustomMappingsFileException("unknown element"); + throw new InvalidCustomMappingsFileException("unknown element, must be one of [" + + String.join(", ", Arrays.stream(clazz.getEnumConstants()).map(E::toString).toArray(String[]::new)).toLowerCase() + "]"); } }); } From 1dce683eb09524fcded9c67f525b19681a30e543 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 19:15:32 +0000 Subject: [PATCH 072/118] Move predicate classes up --- .../item/custom/{v2 => }/predicate/ConditionPredicate.java | 2 +- .../item/custom/{v2 => }/predicate/MatchPredicate.java | 2 +- .../custom/{v2 => }/predicate/RangeDispatchPredicate.java | 2 +- .../geyser/registry/loader/ProviderRegistryLoader.java | 7 +++---- .../registry/populator/CustomItemRegistryPopulator.java | 4 +--- .../geyser/registry/populator/ItemRegistryPopulator.java | 2 +- .../geyser/translator/item/CustomItemTranslator.java | 6 +++--- 7 files changed, 11 insertions(+), 14 deletions(-) rename core/src/main/java/org/geysermc/geyser/item/custom/{v2 => }/predicate/ConditionPredicate.java (96%) rename core/src/main/java/org/geysermc/geyser/item/custom/{v2 => }/predicate/MatchPredicate.java (96%) rename core/src/main/java/org/geysermc/geyser/item/custom/{v2 => }/predicate/RangeDispatchPredicate.java (96%) diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java similarity index 96% rename from core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java rename to core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java index 67b64eb3ac2..b169fb483e2 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/ConditionPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.item.custom.v2.predicate; +package org.geysermc.geyser.item.custom.predicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/MatchPredicate.java similarity index 96% rename from core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java rename to core/src/main/java/org/geysermc/geyser/item/custom/predicate/MatchPredicate.java index edc9a9f1535..01095f3949b 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/MatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/MatchPredicate.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.item.custom.v2.predicate; +package org.geysermc.geyser.item.custom.predicate; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java similarity index 96% rename from core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java rename to core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java index 49796182767..081afbf79f8 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/v2/predicate/RangeDispatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.item.custom.v2.predicate; +package org.geysermc.geyser.item.custom.predicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 5c0969de27d..36ba9ac8cca 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -43,7 +43,6 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; -import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; @@ -59,9 +58,9 @@ import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; import org.geysermc.geyser.item.custom.GeyserCustomItemBedrockOptions; import org.geysermc.geyser.item.custom.GeyserCustomItemDefinition; -import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; -import org.geysermc.geyser.item.custom.v2.predicate.MatchPredicate; -import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; +import org.geysermc.geyser.item.custom.predicate.MatchPredicate; +import org.geysermc.geyser.item.custom.predicate.RangeDispatchPredicate; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; import org.geysermc.geyser.level.block.GeyserCustomBlockData; import org.geysermc.geyser.level.block.GeyserGeometryComponent; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index bdcc4a7bce0..2a4224f2cca 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -41,10 +41,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; -import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; +import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; 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 d33e2e9fa07..5ee756c3a93 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 @@ -61,7 +61,7 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.item.custom.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.inventory.item.StoredItemMappings; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 9382beed4c5..be6ce15c046 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -32,10 +32,10 @@ import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; -import org.geysermc.geyser.item.custom.v2.predicate.ConditionPredicate; -import org.geysermc.geyser.item.custom.v2.predicate.RangeDispatchPredicate; +import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; +import org.geysermc.geyser.item.custom.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; -import org.geysermc.geyser.item.custom.v2.predicate.MatchPredicate; +import org.geysermc.geyser.item.custom.predicate.MatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.Identifier; From 7e973ea0d4d20ce43aa2ead75f5edf8d0efb0eb8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 19:41:40 +0000 Subject: [PATCH 073/118] Fix component patching, MCPL removal is done now --- .../item/custom/v2/CustomItemDefinition.java | 4 +- ...aComponentType.java => DataComponent.java} | 20 ++-- .../custom/v2/component/DataComponentMap.java | 6 +- .../item/custom/ComponentConverters.java | 111 ++++++++++++++++++ .../custom/GeyserCustomItemDefinition.java | 17 ++- .../components/DataComponentReader.java | 6 +- .../components/DataComponentReaders.java | 6 +- .../components/readers/ConsumableReader.java | 4 +- .../components/readers/EquippableReader.java | 4 +- .../readers/FoodPropertiesReader.java | 4 +- .../readers/IntComponentReader.java | 6 +- .../components/readers/UseCooldownReader.java | 4 +- .../CustomItemRegistryPopulator.java | 6 +- 13 files changed, 160 insertions(+), 38 deletions(-) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/{DataComponentType.java => DataComponent.java} (67%) create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 05f660e97dc..319d9b93014 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -28,7 +28,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.util.Identifier; @@ -140,7 +140,7 @@ interface Builder { Builder predicateStrategy(@NonNull PredicateStrategy strategy); - Builder component(@NonNull DataComponentType component, @NonNull T value); + Builder component(@NonNull DataComponent component, @NonNull T value); CustomItemDefinition build(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java similarity index 67% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index cf41f157211..9cab7e73a7b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentType.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java @@ -25,17 +25,17 @@ package org.geysermc.geyser.api.item.custom.v2.component; -public final class DataComponentType { - public static final DataComponentType CONSUMABLE = create(); - public static final DataComponentType EQUIPPABLE = create(); - public static final DataComponentType FOOD = create(); - public static final DataComponentType MAX_DAMAGE = create(); - public static final DataComponentType MAX_STACK_SIZE = create(); - public static final DataComponentType USE_COOLDOWN = create(); +public final class DataComponent { + public static final DataComponent CONSUMABLE = create(); + public static final DataComponent EQUIPPABLE = create(); + public static final DataComponent FOOD = create(); + public static final DataComponent MAX_DAMAGE = create(); + public static final DataComponent MAX_STACK_SIZE = create(); + public static final DataComponent USE_COOLDOWN = create(); - private DataComponentType() {} + private DataComponent() {} - private static DataComponentType create() { - return new DataComponentType<>(); + private static DataComponent create() { + return new DataComponent<>(); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java index 560f5c380eb..4e1ee9f4b6e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java @@ -25,7 +25,11 @@ package org.geysermc.geyser.api.item.custom.v2.component; +import java.util.Set; + public interface DataComponentMap { - T get(DataComponentType type); + T get(DataComponent type); + + Set> keySet(); } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java new file mode 100644 index 00000000000..e0d98e22656 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.item.custom; + +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; +import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// Why is this coded so weirdly, you ask? +// Why, it's because of two reasons! +// First of all, Java generics are a bit limited :( +// Second, the API module has its own set of component classes, because MCPL can't be used in there. +// However, those component classes have the same names as the MCPL ones, which causes some issues when they both have to be used in the same file. +// One can't be imported, and as such its full qualifier (e.g. org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable) would have to be used. +// That would be a mess to code in, and as such this code here was carefully designed to only require one set of component classes by name (the MCPL ones). +public class ComponentConverters { + private static final Map, ComponentConverter> converters = new HashMap<>(); + + static { + registerConverter(DataComponent.CONSUMABLE, (itemMap, value) -> { + Consumable.ItemUseAnimation convertedAnimation = switch (value.animation()) { + case NONE -> Consumable.ItemUseAnimation.NONE; + case EAT -> Consumable.ItemUseAnimation.EAT; + case DRINK -> Consumable.ItemUseAnimation.DRINK; + case BLOCK -> Consumable.ItemUseAnimation.BLOCK; + case BOW -> Consumable.ItemUseAnimation.BOW; + case SPEAR -> Consumable.ItemUseAnimation.SPEAR; + case CROSSBOW -> Consumable.ItemUseAnimation.CROSSBOW; + case SPYGLASS -> Consumable.ItemUseAnimation.SPYGLASS; + case TOOT_HORN -> Consumable.ItemUseAnimation.TOOT_HORN; + case BRUSH -> Consumable.ItemUseAnimation.BRUSH; + }; + itemMap.put(DataComponentType.CONSUMABLE, new Consumable(value.consumeSeconds(), convertedAnimation, BuiltinSound.ENTITY_GENERIC_EAT, + true, List.of())); + }); + + registerConverter(DataComponent.EQUIPPABLE, (itemMap, value) -> { + EquipmentSlot convertedSlot = switch (value.slot()) { + case HEAD -> EquipmentSlot.HELMET; + case CHEST -> EquipmentSlot.CHESTPLATE; + case LEGS -> EquipmentSlot.LEGGINGS; + case FEET -> EquipmentSlot.BOOTS; + }; + itemMap.put(DataComponentType.EQUIPPABLE, new Equippable(convertedSlot, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, + null, null, null, false, false, false)); + }); + + registerConverter(DataComponent.FOOD, (itemMap, value) -> itemMap.put(DataComponentType.FOOD, + new FoodProperties(value.nutrition(), value.saturation(), value.canAlwaysEat()))); + + registerConverter(DataComponent.MAX_DAMAGE, (itemMap, value) -> itemMap.put(DataComponentType.MAX_DAMAGE, value)); + registerConverter(DataComponent.MAX_STACK_SIZE, (itemMap, value) -> itemMap.put(DataComponentType.MAX_STACK_SIZE, value)); + + registerConverter(DataComponent.USE_COOLDOWN, (itemMap, value) -> itemMap.put(DataComponentType.USE_COOLDOWN, + new UseCooldown(value.seconds(), MinecraftKey.identifierToKey(value.cooldownGroup())))); + } + + private static void registerConverter(DataComponent component, ComponentConverter converter) { + converters.put(component, converter); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void convertAndPutComponents(DataComponents itemMap, DataComponentMap customDefinitionMap) { + for (DataComponent component : customDefinitionMap.keySet()) { + ComponentConverter converter = converters.get(component); + Object value = customDefinitionMap.get(component); + converter.convertAndPut(itemMap, value); + } + } + + @FunctionalInterface + public interface ComponentConverter { + + void convertAndPut(DataComponents itemMap, T value); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 5ba6abd652e..38ffd7963d9 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -31,14 +31,14 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.util.Identifier; import java.util.ArrayList; import java.util.List; -import java.util.function.BiConsumer; +import java.util.Set; public record GeyserCustomItemDefinition(@NonNull Identifier bedrockIdentifier, String displayName, @NonNull Identifier model, @NonNull List predicates, PredicateStrategy predicateStrategy, @@ -48,7 +48,7 @@ public static class Builder implements CustomItemDefinition.Builder { private final Identifier bedrockIdentifier; private final Identifier model; private final List predicates = new ArrayList<>(); - private final Reference2ObjectMap, Object> components = new Reference2ObjectOpenHashMap<>(); + private final Reference2ObjectMap, Object> components = new Reference2ObjectOpenHashMap<>(); private String displayName; private int priority = 0; @@ -92,7 +92,7 @@ public CustomItemDefinition.Builder predicateStrategy(@NonNull PredicateStrategy } @Override - public CustomItemDefinition.Builder component(@NonNull DataComponentType component, @NonNull T value) { + public CustomItemDefinition.Builder component(@NonNull DataComponent component, @NonNull T value) { components.put(component, value); return this; } @@ -104,11 +104,16 @@ public CustomItemDefinition build() { } } - private record ComponentMap(Reference2ObjectMap, Object> components) implements DataComponentMap { + private record ComponentMap(Reference2ObjectMap, Object> components) implements DataComponentMap { @Override - public T get(DataComponentType type) { + public T get(DataComponent type) { return (T) components.get(type); } + + @Override + public Set> keySet() { + return components.keySet(); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index 0c3496740e8..ae689c15057 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -28,13 +28,13 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; public abstract class DataComponentReader { - private final DataComponentType type; + private final DataComponent type; - protected DataComponentReader(DataComponentType type) { + protected DataComponentReader(DataComponent type) { this.type = type; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 5b84b34e9f4..34842a71deb 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -29,7 +29,7 @@ import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.readers.ConsumableReader; import org.geysermc.geyser.registry.mappings.components.readers.EquippableReader; @@ -56,8 +56,8 @@ public static void readDataComponent(CustomItemDefinition.Builder builder, Key k READERS.put(MinecraftKey.key("consumable"), new ConsumableReader()); READERS.put(MinecraftKey.key("equippable"), new EquippableReader()); READERS.put(MinecraftKey.key("food"), new FoodPropertiesReader()); - READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponentType.MAX_DAMAGE, 0)); - READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponentType.MAX_STACK_SIZE, 1, 99)); + READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponent.MAX_DAMAGE, 0)); + READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponent.MAX_STACK_SIZE, 1, 99)); READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java index ea4797d0f78..809b96f2b43 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -28,7 +28,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.Consumable; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -37,7 +37,7 @@ public class ConsumableReader extends DataComponentReader { public ConsumableReader() { - super(DataComponentType.CONSUMABLE); + super(DataComponent.CONSUMABLE); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java index 8f1044da688..32f104fc22d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.Equippable; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; @@ -37,7 +37,7 @@ public class EquippableReader extends DataComponentReader { public EquippableReader() { - super(DataComponentType.EQUIPPABLE); + super(DataComponent.EQUIPPABLE); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java index 4d7a3bcbc20..85ee67f1a9d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.FoodProperties; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; @@ -37,7 +37,7 @@ public class FoodPropertiesReader extends DataComponentReader { public FoodPropertiesReader() { - super(DataComponentType.FOOD); + super(DataComponent.FOOD); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java index 11a920d7f26..64d5ea2710c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.NodeReader; @@ -36,13 +36,13 @@ public class IntComponentReader extends DataComponentReader { private final int minimum; private final int maximum; - public IntComponentReader(DataComponentType type, int minimum, int maximum) { + public IntComponentReader(DataComponent type, int minimum, int maximum) { super(type); this.minimum = minimum; this.maximum = maximum; } - public IntComponentReader(DataComponentType type, int minimum) { + public IntComponentReader(DataComponent type, int minimum) { this(type, minimum, Integer.MAX_VALUE); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java index 1f50d888ef8..63b06c9c203 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentType; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.UseCooldown; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; @@ -38,7 +38,7 @@ public class UseCooldownReader extends DataComponentReader { public UseCooldownReader() { - super(DataComponentType.USE_COOLDOWN); + super(DataComponent.USE_COOLDOWN); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 2a4224f2cca..433a529cdfd 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -42,6 +42,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; +import org.geysermc.geyser.item.custom.ComponentConverters; import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; @@ -660,8 +661,9 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { } private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { - //return javaItem.gatherComponents(definition.components()); - return javaItem.gatherComponents(new DataComponents(new HashMap<>())); // TODO FIXME + DataComponents convertedComponents = new DataComponents(new HashMap<>()); + ComponentConverters.convertAndPutComponents(convertedComponents, definition.components()); + return javaItem.gatherComponents(convertedComponents); } @SuppressWarnings("unchecked") From 9f21221d3ee8df477959ae34f1c107654d1f4041 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 11 Jan 2025 19:51:57 +0000 Subject: [PATCH 074/118] Component validation in the API --- .../item/custom/v2/component/Consumable.java | 10 +++- .../custom/v2/component/DataComponent.java | 47 +++++++++++++++---- .../custom/v2/component/FoodProperties.java | 10 +++- .../item/custom/v2/component/UseCooldown.java | 9 +++- .../geysermc/geyser/api/util/Identifier.java | 2 +- .../custom/GeyserCustomItemDefinition.java | 3 ++ 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java index f2a5a232359..b2bfb0b3edf 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java @@ -25,7 +25,15 @@ package org.geysermc.geyser.api.item.custom.v2.component; -public record Consumable(float consumeSeconds, Animation animation) { +import org.checkerframework.checker.index.qual.Positive; + +public record Consumable(@Positive float consumeSeconds, Animation animation) { + + public Consumable { + if (consumeSeconds <= 0.0F) { + throw new IllegalStateException("Consume seconds must be above 0"); + } + } public enum Animation { NONE, diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index 9cab7e73a7b..0c58262bb8a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java @@ -25,17 +25,46 @@ package org.geysermc.geyser.api.item.custom.v2.component; +import org.geysermc.geyser.api.util.Identifier; + +import java.util.function.Predicate; + public final class DataComponent { - public static final DataComponent CONSUMABLE = create(); - public static final DataComponent EQUIPPABLE = create(); - public static final DataComponent FOOD = create(); - public static final DataComponent MAX_DAMAGE = create(); - public static final DataComponent MAX_STACK_SIZE = create(); - public static final DataComponent USE_COOLDOWN = create(); + public static final DataComponent CONSUMABLE = create("consumable"); + public static final DataComponent EQUIPPABLE = create("equippable"); + public static final DataComponent FOOD = create("food"); + /** + * Must be at or above 0. + */ + public static final DataComponent MAX_DAMAGE = create("max_damage", i -> i >= 0); + /** + * Must be between 1 and 99. + */ + public static final DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda + public static final DataComponent USE_COOLDOWN = create("use_cooldown"); + + private final Identifier identifier; + private final Predicate validator; + + private DataComponent(Identifier identifier, Predicate validator) { + this.identifier = identifier; + this.validator = validator; + } - private DataComponent() {} + private static DataComponent create(String name) { + return new DataComponent<>(new Identifier(Identifier.DEFAULT_NAMESPACE, name), t -> true); + } + + private static DataComponent create(String name, Predicate validator) { + return new DataComponent<>(new Identifier(Identifier.DEFAULT_NAMESPACE, name), validator); + } + + public boolean validate(T value) { + return validator.test(value); + } - private static DataComponent create() { - return new DataComponent<>(); + @Override + public String toString() { + return "data component " + identifier.toString(); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java index 049704c3b99..05d4398c92e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java @@ -25,5 +25,13 @@ package org.geysermc.geyser.api.item.custom.v2.component; -public record FoodProperties(int nutrition, float saturation, boolean canAlwaysEat) { +import org.checkerframework.checker.index.qual.NonNegative; + +public record FoodProperties(@NonNegative int nutrition, @NonNegative float saturation, boolean canAlwaysEat) { + + public FoodProperties { + if (nutrition < 0 || saturation < 0.0F) { + throw new IllegalStateException("Nutrition and saturation must be at or above 0"); + } + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java index 803d652be02..d2ba6ca3c44 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java @@ -25,7 +25,14 @@ package org.geysermc.geyser.api.item.custom.v2.component; +import org.checkerframework.checker.index.qual.Positive; import org.geysermc.geyser.api.util.Identifier; -public record UseCooldown(float seconds, Identifier cooldownGroup) { +public record UseCooldown(@Positive float seconds, Identifier cooldownGroup) { + + public UseCooldown { + if (seconds <= 0.0F) { + throw new IllegalStateException("Cooldown seconds must be above 0"); + } + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java index 0b19bc23858..d74c9ed63c9 100644 --- a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java +++ b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java @@ -29,7 +29,7 @@ import java.util.function.Function; public final class Identifier { - private static final String DEFAULT_NAMESPACE = "minecraft"; + public static final String DEFAULT_NAMESPACE = "minecraft"; private final String namespace; private final String path; diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java index 38ffd7963d9..dc160a6ec0d 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java @@ -93,6 +93,9 @@ public CustomItemDefinition.Builder predicateStrategy(@NonNull PredicateStrategy @Override public CustomItemDefinition.Builder component(@NonNull DataComponent component, @NonNull T value) { + if (!component.validate(value)) { + throw new IllegalArgumentException("Value " + value + " is invalid for " + component); + } components.put(component, value); return this; } From ffb94ff06af67906cdfee26a88b6cf8c9ad18627 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 14:52:40 +0000 Subject: [PATCH 075/118] Proper support for the enchantable value property --- .../item/custom/v2/component/Consumable.java | 2 +- .../custom/v2/component/DataComponent.java | 7 ++ .../custom/v2/component/FoodProperties.java | 2 +- .../item/custom/v2/component/UseCooldown.java | 2 +- .../components/DataComponentReaders.java | 2 + .../components/readers/EnchantableReader.java | 76 +++++++++++++++++++ .../CustomItemRegistryPopulator.java | 17 ++++- 7 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java index b2bfb0b3edf..373e7cf37c1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java @@ -31,7 +31,7 @@ public record Consumable(@Positive float consumeSeconds, Animation animation) { public Consumable { if (consumeSeconds <= 0.0F) { - throw new IllegalStateException("Consume seconds must be above 0"); + throw new IllegalArgumentException("Consume seconds must be above 0"); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index 0c58262bb8a..1ee5d508d56 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java @@ -42,6 +42,13 @@ public final class DataComponent { */ public static final DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda public static final DataComponent USE_COOLDOWN = create("use_cooldown"); + /** + * Must be at or above 0. + * + *

Note that, on Bedrock, this will be mapped to the {@code minecraft:enchantable} component with {@code slot=all}. This should, but does not guarantee, + * allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.

+ */ + public static final DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0); private final Identifier identifier; private final Predicate validator; diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java index 05d4398c92e..aa1229fa230 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java @@ -31,7 +31,7 @@ public record FoodProperties(@NonNegative int nutrition, @NonNegative float satu public FoodProperties { if (nutrition < 0 || saturation < 0.0F) { - throw new IllegalStateException("Nutrition and saturation must be at or above 0"); + throw new IllegalArgumentException("Nutrition and saturation must be at or above 0"); } } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java index d2ba6ca3c44..05e01a412f3 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java @@ -32,7 +32,7 @@ public record UseCooldown(@Positive float seconds, Identifier cooldownGroup) { public UseCooldown { if (seconds <= 0.0F) { - throw new IllegalStateException("Cooldown seconds must be above 0"); + throw new IllegalArgumentException("Cooldown seconds must be above 0"); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 34842a71deb..6d6af576d07 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -32,6 +32,7 @@ import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.readers.ConsumableReader; +import org.geysermc.geyser.registry.mappings.components.readers.EnchantableReader; import org.geysermc.geyser.registry.mappings.components.readers.EquippableReader; import org.geysermc.geyser.registry.mappings.components.readers.FoodPropertiesReader; import org.geysermc.geyser.registry.mappings.components.readers.IntComponentReader; @@ -59,5 +60,6 @@ public static void readDataComponent(CustomItemDefinition.Builder builder, Key k READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponent.MAX_DAMAGE, 0)); READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponent.MAX_STACK_SIZE, 1, 99)); READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); + READERS.put(MinecraftKey.key("enchantable"), new EnchantableReader()); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java new file mode 100644 index 00000000000..952dd651ef3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; + +/** + * For some reason, Minecraft Java uses + * + *
+ *     {@code
+ *     {
+ *       "minecraft:enchantable: {
+ *         "value": 4
+ *       }
+ *     }
+ *     }
+ * 
+ * + * instead of + * + *
+ *     {@code
+ *     {
+ *         "minecraft:enchantable": 4
+ *     }
+ *     }
+ * 
+ * + * This reader allows both styles. + */ +public class EnchantableReader extends DataComponentReader { + + public EnchantableReader() { + super(DataComponent.ENCHANTABLE); + } + + @Override + protected Integer readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + try { + return NodeReader.NON_NEGATIVE_INT.read(node, "reading component", context); + } catch (InvalidCustomMappingsFileException exception) { + MappingsUtil.requireObject(node, "reading component", context); + return MappingsUtil.readOrThrow(node, "value", NodeReader.NON_NEGATIVE_INT); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 433a529cdfd..b61b36cb8d3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -56,6 +56,7 @@ import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; @@ -235,11 +236,16 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); - Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.EQUIPPABLE); + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); if (equippable != null) { computeArmorProperties(equippable, itemProperties, componentBuilder); } + Integer enchantmentValue = components.get(DataComponentType.ENCHANTABLE); + if (enchantmentValue != null) { + computeEnchantableProperties(enchantmentValue, itemProperties, componentBuilder); + } + if (vanillaMapping.getFirstBlockRuntimeId() != null) { computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder); } @@ -431,6 +437,15 @@ private static void computeArmorProperties(Equippable equippable, /*String armor } } + private static void computeEnchantableProperties(int enchantmentValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + itemProperties.putString("enchantable_slot", "all"); + itemProperties.putInt("enchantable_value", enchantmentValue); + componentBuilder.putCompound("minecraft:enchantable", NbtMap.builder() + .putString("slot", "all") + .putByte("value", (byte) enchantmentValue) + .build()); + } + private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) { // carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here // however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot From 44a1bba7cfaa82cf360c47c889ec9184324db5a2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 14:54:13 +0000 Subject: [PATCH 076/118] Reformat some stuff my IDE shouldn't have done --- .../CustomItemRegistryPopulator.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index b61b36cb8d3..935778312ec 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -42,14 +42,14 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; -import org.geysermc.geyser.item.custom.ComponentConverters; -import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.WearableSlot; +import org.geysermc.geyser.item.custom.ComponentConverters; +import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.item.exception.InvalidItemComponentsException; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; @@ -204,10 +204,10 @@ private static Optional checkPredicate(Map.Entry 1) { + if (components.get(DataComponentType.EQUIPPABLE) != null && stackSize > 1) { throw new InvalidItemComponentsException("Bedrock doesn't support equippable items with a stack size above 1"); } else if (stackSize > 1 && maxDamage > 0) { throw new InvalidItemComponentsException("Stack size must be 1 when max damage is above 0"); @@ -250,9 +250,9 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder); } - Consumable consumable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.CONSUMABLE); + Consumable consumable = components.get(DataComponentType.CONSUMABLE); if (consumable != null) { - FoodProperties foodProperties = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.FOOD); + FoodProperties foodProperties = components.get(DataComponentType.FOOD); computeConsumableProperties(consumable, foodProperties, itemProperties, componentBuilder); } @@ -260,7 +260,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeEntityPlacerProperties(componentBuilder); } - UseCooldown useCooldown = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.USE_COOLDOWN); + UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN); if (useCooldown != null) { computeUseCooldownProperties(useCooldown, componentBuilder); } @@ -269,8 +269,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD switch (vanillaMapping.getBedrockIdentifier()) { case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, vanillaMapping.getBedrockIdentifier()); - case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> - computeThrowableProperties(componentBuilder); + case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> computeThrowableProperties(componentBuilder); } computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); @@ -320,10 +319,10 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); itemProperties.putBoolean("hand_equipped", options.displayHandheld()); - int maxDamage = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.MAX_DAMAGE, 0); - Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.EQUIPPABLE); + int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped - int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's itemProperties.putInt("max_stack_size", stackSize); if (maxDamage > 0 && !isUnbreakableItem(definition)) { From 33b49eb842c982b6d4ae296916b3e1c4ffa23dcf Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 15:07:10 +0000 Subject: [PATCH 077/118] Properly implement protection value --- .../custom/v2/CustomItemBedrockOptions.java | 12 +++++ .../GeyserCustomItemBedrockOptions.java | 13 ++++- .../mappings/versions/MappingsReader_v2.java | 5 +- .../CustomItemRegistryPopulator.java | 53 ++++++++----------- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index 2ae6576c9ea..6b81a3c4637 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -61,6 +61,16 @@ public interface CustomItemBedrockOptions { */ boolean displayHandheld(); + /** + * Since Bedrock doesn't properly support setting item armour values over attributes, this value + * determines how many armour points should be shown when this item is worn. This is purely visual. + * + *

Only has an effect when the item is equippable, and defaults to 0.

+ * + * @return the item's protection value. Purely visual and for Bedrock only. + */ + int protectionValue(); + /** * The item's creative category. Defaults to {@code NONE}. * @@ -117,6 +127,8 @@ interface Builder { Builder displayHandheld(boolean displayHandheld); + Builder protectionValue(int protectionValue); + Builder creativeCategory(CreativeCategory creativeCategory); Builder creativeGroup(@Nullable String creativeGroup); diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java index 9777fe3c7fb..726cfa00eb6 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java @@ -35,13 +35,15 @@ import java.util.Objects; import java.util.Set; -public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, @NonNull CreativeCategory creativeCategory, @Nullable String creativeGroup, +public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, int protectionValue, + @NonNull CreativeCategory creativeCategory, @Nullable String creativeGroup, int textureSize, @Nullable CustomRenderOffsets renderOffsets, @NonNull Set tags) implements CustomItemBedrockOptions { public static class Builder implements CustomItemBedrockOptions.Builder { private String icon = null; private boolean allowOffhand = true; private boolean displayHandheld = false; + private int protectionValue = 0; private CreativeCategory creativeCategory = CreativeCategory.NONE; private String creativeGroup = null; private int textureSize = 16; @@ -66,6 +68,12 @@ public Builder displayHandheld(boolean displayHandheld) { return this; } + @Override + public Builder protectionValue(int protectionValue) { + this.protectionValue = protectionValue; + return this; + } + @Override public Builder creativeCategory(CreativeCategory creativeCategory) { this.creativeCategory = creativeCategory; @@ -100,7 +108,8 @@ public Builder tags(@Nullable Set tags) { @Override public CustomItemBedrockOptions build() { - return new GeyserCustomItemBedrockOptions(icon, allowOffhand, displayHandheld, creativeCategory, creativeGroup, textureSize, renderOffsets, tags); + return new GeyserCustomItemBedrockOptions(icon, allowOffhand, displayHandheld, protectionValue, + creativeCategory, creativeGroup, textureSize, renderOffsets, tags); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 84c2aa47cd4..aa35b7698c1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -169,10 +169,11 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node, Strin String[] context = {"bedrock options", baseContext}; MappingsUtil.readTextIfPresent(node, "icon", builder::icon, context); - MappingsUtil.readIfPresent(node, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); - MappingsUtil.readTextIfPresent(node, "creative_group", builder::creativeGroup, context); MappingsUtil.readIfPresent(node, "allow_offhand", builder::allowOffhand, NodeReader.BOOLEAN, context); MappingsUtil.readIfPresent(node, "display_handheld", builder::displayHandheld, NodeReader.BOOLEAN, context); + MappingsUtil.readIfPresent(node, "protection_value", builder::protectionValue, NodeReader.NON_NEGATIVE_INT, context); + MappingsUtil.readIfPresent(node, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); + MappingsUtil.readTextIfPresent(node, "creative_group", builder::creativeGroup, context); MappingsUtil.readIfPresent(node, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); MappingsUtil.readIfPresent(node, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode, context); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 935778312ec..282caa6ab52 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -238,7 +238,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD Equippable equippable = components.get(DataComponentType.EQUIPPABLE); if (equippable != null) { - computeArmorProperties(equippable, itemProperties, componentBuilder); + computeArmorProperties(equippable, customItemDefinition.bedrockOptions().protectionValue(), componentBuilder); } Integer enchantmentValue = components.get(DataComponentType.ENCHANTABLE); @@ -322,7 +322,7 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); Equippable equippable = components.get(DataComponentType.EQUIPPABLE); // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped - int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla ones itemProperties.putInt("max_stack_size", stackSize); if (maxDamage > 0 && !isUnbreakableItem(definition)) { @@ -388,10 +388,6 @@ private static boolean computeToolProperties(String toolType, NbtMapBuilder item itemProperties.putBoolean("hand_equipped", true); itemProperties.putFloat("mining_speed", miningSpeed); - // This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil - itemProperties.putInt("enchantable_value", 1); - itemProperties.putString("enchantable_slot", toolType); - // Adds a "attack damage" indicator. Purely visual! if (attackDamage > 0) { itemProperties.putInt("damage", attackDamage); @@ -400,38 +396,31 @@ private static boolean computeToolProperties(String toolType, NbtMapBuilder item return canDestroyInCreative; } - private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - int protectionValue = 0; - // TODO protection value, enchantable stuff and armour type? + private static void computeArmorProperties(Equippable equippable, int protectionValue, NbtMapBuilder componentBuilder) { switch (equippable.slot()) { - case BOOTS -> { - componentBuilder.putString("minecraft:render_offsets", "boots"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt()); - - //itemProperties.putString("enchantable_slot", "armor_feet"); - //itemProperties.putInt("enchantable_value", 15); TODO + case HELMET -> { + componentBuilder.putCompound("minecraft:wearable", NbtMap.builder() + .putString("slot", "slot.armor.head") + .putInt("protection", protectionValue) + .build()); } case CHESTPLATE -> { - componentBuilder.putString("minecraft:render_offsets", "chestplates"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt()); - - //itemProperties.putString("enchantable_slot", "armor_torso"); - //itemProperties.putInt("enchantable_value", 15); TODO + componentBuilder.putCompound("minecraft:wearable", NbtMap.builder() + .putString("slot", "slot.armor.chest") + .putInt("protection", protectionValue) + .build()); } case LEGGINGS -> { - componentBuilder.putString("minecraft:render_offsets", "leggings"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt()); - - //itemProperties.putString("enchantable_slot", "armor_legs"); - //itemProperties.putInt("enchantable_value", 15); TODO + componentBuilder.putCompound("minecraft:wearable", NbtMap.builder() + .putString("slot", "slot.armor.legs") + .putInt("protection", protectionValue) + .build()); } - case HELMET -> { - componentBuilder.putString("minecraft:render_offsets", "helmets"); - componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt()); - //componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build()); - - //itemProperties.putString("enchantable_slot", "armor_head"); - //itemProperties.putInt("enchantable_value", 15); + case BOOTS -> { + componentBuilder.putCompound("minecraft:wearable", NbtMap.builder() + .putString("slot", "slot.armor.feet") + .putInt("protection", protectionValue) + .build()); } } } From 0e5dba1abe762ef2ef30d37b738969ac84ce5cb7 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 15:12:24 +0000 Subject: [PATCH 078/118] Documentation improvements, add converter for enchantable component --- .../api/item/custom/v2/CustomItemDefinition.java | 15 +++++++++------ .../geyser/item/custom/ComponentConverters.java | 4 ++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 319d9b93014..4dad68afcc7 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -111,16 +111,19 @@ public interface CustomItemDefinition { *

Currently, the following components are supported:

* *
    - *
  • {@code minecraft:consumable}
  • - *
  • {@code minecraft:equippable}
  • - *
  • {@code minecraft:food}
  • - *
  • {@code minecraft:max_damage}
  • - *
  • {@code minecraft:max_stack_size}
  • - *
  • {@code minecraft:use_cooldown}
  • + *
  • {@code minecraft:consumable} ({@link DataComponent#CONSUMABLE})
  • + *
  • {@code minecraft:equippable} ({@link DataComponent#EQUIPPABLE})
  • + *
  • {@code minecraft:food} ({@link DataComponent#FOOD})
  • + *
  • {@code minecraft:max_damage} ({@link DataComponent#MAX_DAMAGE})
  • + *
  • {@code minecraft:max_stack_size} ({@link DataComponent#MAX_STACK_SIZE})
  • + *
  • {@code minecraft:use_cooldown} ({@link DataComponent#USE_COOLDOWN})
  • + *
  • {@code minecraft:enchantable} ({@link DataComponent#ENCHANTABLE})
  • *
* *

Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, * and do not have to be specified here.

+ * + * @see DataComponent */ @NonNull DataComponentMap components(); diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java index e0d98e22656..d4e4594d52a 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java @@ -48,6 +48,8 @@ // However, those component classes have the same names as the MCPL ones, which causes some issues when they both have to be used in the same file. // One can't be imported, and as such its full qualifier (e.g. org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable) would have to be used. // That would be a mess to code in, and as such this code here was carefully designed to only require one set of component classes by name (the MCPL ones). +// +// It is VERY IMPORTANT to note that for every component in the API, a converter to MCPL must be put here (better solutions are welcome). public class ComponentConverters { private static final Map, ComponentConverter> converters = new HashMap<>(); @@ -88,6 +90,8 @@ public class ComponentConverters { registerConverter(DataComponent.USE_COOLDOWN, (itemMap, value) -> itemMap.put(DataComponentType.USE_COOLDOWN, new UseCooldown(value.seconds(), MinecraftKey.identifierToKey(value.cooldownGroup())))); + + registerConverter(DataComponent.ENCHANTABLE, (itemMap, value) -> itemMap.put(DataComponentType.ENCHANTABLE, value)); } private static void registerConverter(DataComponent component, ComponentConverter converter) { From 0308388650f17f98f92d34d37cacf0d8eae64514 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 15:13:46 +0000 Subject: [PATCH 079/118] Remove unused stuff --- .../geyser/item/components/WearableSlot.java | 47 ------------------- .../mappings/versions/MappingsReader_v2.java | 2 - .../CustomItemRegistryPopulator.java | 1 - 3 files changed, 50 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java diff --git a/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java b/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java deleted file mode 100644 index 7ce488a512f..00000000000 --- a/core/src/main/java/org/geysermc/geyser/item/components/WearableSlot.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.item.components; - -import org.cloudburstmc.nbt.NbtMap; - -import java.util.Locale; - -public enum WearableSlot { - HEAD, - CHEST, - LEGS, - FEET; - - private final NbtMap slotNbt; - - WearableSlot() { - this.slotNbt = NbtMap.builder().putString("slot", "slot.armor." + this.name().toLowerCase(Locale.ROOT)).build(); - } - - public NbtMap getSlotNbt() { - return slotNbt; - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index aa35b7698c1..2d280c9a8ab 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -46,10 +46,8 @@ import org.geysermc.geyser.registry.mappings.util.MappingsUtil; import org.geysermc.geyser.registry.mappings.util.NodeReader; import org.geysermc.geyser.util.MinecraftKey; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import java.nio.file.Path; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 282caa6ab52..8bae1c0cfcc 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -47,7 +47,6 @@ import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; -import org.geysermc.geyser.item.components.WearableSlot; import org.geysermc.geyser.item.custom.ComponentConverters; import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.item.exception.InvalidItemComponentsException; From d522f614dbc8f12c9387f87ad2c851e90b9aa7b0 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 15:45:00 +0000 Subject: [PATCH 080/118] Remove rarely used method --- .../geyser/registry/mappings/util/MappingsUtil.java | 4 ---- .../registry/mappings/versions/MappingsReader_v2.java | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java index d403371f156..de28bfdf463 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -48,10 +48,6 @@ public static T readOrDefault(JsonNode node, String name, NodeReader conv return converter.read(object, formatTask(name), context); } - public static void readTextIfPresent(JsonNode node, String name, Consumer consumer, String... context) throws InvalidCustomMappingsFileException { - readIfPresent(node, name, consumer, NodeReader.STRING, context); - } - public static void readIfPresent(JsonNode node, String name, Consumer consumer, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { if (node.has(name)) { consumer.accept(converter.read(node.get(name), formatTask(name), context)); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 2d280c9a8ab..9e946836d18 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -136,7 +136,7 @@ public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonNod } CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, model); - MappingsUtil.readTextIfPresent(node, "display_name", builder::displayName, context); + MappingsUtil.readIfPresent(node, "display_name", builder::displayName, NodeReader.NON_EMPTY_STRING, context); MappingsUtil.readIfPresent(node, "priority", builder::priority, NodeReader.INT, context); readPredicates(builder, node.get("predicate"), context); @@ -166,12 +166,12 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node, Strin } String[] context = {"bedrock options", baseContext}; - MappingsUtil.readTextIfPresent(node, "icon", builder::icon, context); + MappingsUtil.readIfPresent(node, "icon", builder::icon, NodeReader.NON_EMPTY_STRING, context); MappingsUtil.readIfPresent(node, "allow_offhand", builder::allowOffhand, NodeReader.BOOLEAN, context); MappingsUtil.readIfPresent(node, "display_handheld", builder::displayHandheld, NodeReader.BOOLEAN, context); MappingsUtil.readIfPresent(node, "protection_value", builder::protectionValue, NodeReader.NON_NEGATIVE_INT, context); MappingsUtil.readIfPresent(node, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); - MappingsUtil.readTextIfPresent(node, "creative_group", builder::creativeGroup, context); + MappingsUtil.readIfPresent(node, "creative_group", builder::creativeGroup, NodeReader.NON_EMPTY_STRING, context); MappingsUtil.readIfPresent(node, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); MappingsUtil.readIfPresent(node, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode, context); From ebbd1c546cc81665acf0a3b82f3c227eb9d64360 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Jan 2025 15:49:59 +0000 Subject: [PATCH 081/118] Small documentation stuff --- .../org/geysermc/geyser/registry/mappings/util/NodeReader.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 88d08432209..5fecd5d5a12 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -126,6 +126,9 @@ static NodeReader boundedInt(int min, int max) { return INT.validate(i -> i >= min && i <= max, "integer must be in range [" + min + ", " + max + "]"); } + /** + * {@link NodeReader#read(JsonNode, String, String...)} is preferably used as that properly formats the error when one is thrown. + */ T read(JsonNode node) throws InvalidCustomMappingsFileException; default T read(JsonNode node, String task, String... context) throws InvalidCustomMappingsFileException { From 1afb733ad3c0d9e098d9fff9abb4e523dc6f653c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 15:35:20 +0000 Subject: [PATCH 082/118] Work on tool component stuff --- .../CustomItemRegistryPopulator.java | 59 ++++--------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 8bae1c0cfcc..0b28d3304ab 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -230,8 +230,9 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder); boolean canDestroyInCreative = true; + computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder); if (vanillaMapping.getToolType() != null) { - canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage()); + canDestroyInCreative = } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); @@ -337,62 +338,28 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp } /** - * @return can destroy in creative + * Adds properties to make the Bedrock client unable to destroy any block with this custom item. + * This works because the molang '1' for tags will be true for all blocks and the speed will be 0. + * We want this since we calculate break speed server side in BedrockActionTranslator */ - private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) { - // TODO check this, it's probably wrong by now, also check what the minecraft:tool java component can do here, if anything - boolean canDestroyInCreative = true; - float miningSpeed = 1.0f; - - // This means client side the tool can never destroy a block - // This works because the molang '1' for tags will be true for all blocks and the speed will be 0 - // We want this since we calculate break speed server side in BedrockActionTranslator + private static void computeToolProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { List speed = new ArrayList<>(List.of( NbtMap.builder() .putCompound("block", NbtMap.builder() + .putString("name", "") + .putCompound("states", NbtMap.EMPTY) .putString("tags", "1") .build()) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) .putInt("speed", 0) .build() )); - componentBuilder.putCompound("minecraft:digger", - NbtMap.builder() - .putList("destroy_speeds", NbtType.COMPOUND, speed) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putBoolean("use_efficiency", true) - .build() - ); - - if (toolType.equals("sword")) { - miningSpeed = 1.5f; - canDestroyInCreative = false; - } - - itemProperties.putBoolean("hand_equipped", true); - itemProperties.putFloat("mining_speed", miningSpeed); - - // Adds a "attack damage" indicator. Purely visual! - if (attackDamage > 0) { - itemProperties.putInt("damage", attackDamage); - } + componentBuilder.putCompound("minecraft:digger", NbtMap.builder() + .putList("destroy_speeds", NbtType.COMPOUND, speed) + .putBoolean("use_efficiency", false) + .build()); - return canDestroyInCreative; + itemProperties.putFloat("mining_speed", 1.0F); } private static void computeArmorProperties(Equippable equippable, int protectionValue, NbtMapBuilder componentBuilder) { From 75e371c75bcd649d1b411d3db53a0c8a674ff792 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 15:37:43 +0000 Subject: [PATCH 083/118] Add tool component to API --- .../custom/v2/component/DataComponent.java | 6 ++++ .../custom/v2/component/ToolProperties.java | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index 1ee5d508d56..7557828ec91 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java @@ -49,6 +49,12 @@ public final class DataComponent { * allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.

*/ public static final DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0); + /** + * At the moment only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option, which will be a feature in Java 1.21.5, but is already in use in Geyser. + * + *

Like other components, when not set this will fall back to the default value.

+ */ + public static final DataComponent TOOL = create("tool"); private final Identifier identifier; private final Predicate validator; diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java new file mode 100644 index 00000000000..eb04e09daab --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +public record ToolProperties(boolean canDestroyBlocksInCreative) { +} From 67b615ed14dc26f6e5244d92194069154d037aad Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 15:48:50 +0000 Subject: [PATCH 084/118] Implement tool component in custom item populator --- .../item/custom/ComponentConverters.java | 15 +++++++- .../CustomItemRegistryPopulator.java | 34 +++++++++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java index d4e4594d52a..560c5a7ca1a 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java @@ -27,6 +27,8 @@ import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; +import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; @@ -98,13 +100,24 @@ private static void registerConverter(DataComponent component, ComponentC converters.put(component, converter); } + /** + * Temporary workaround: while 1.21.5 has not released yet, this method returns the {@link ToolProperties#canDestroyBlocksInCreative()} value. + * + *

When 1.21.5 does release, this value will be mapped into the MCPL tool component, and this method will return nothing again.

+ */ @SuppressWarnings({"unchecked", "rawtypes"}) - public static void convertAndPutComponents(DataComponents itemMap, DataComponentMap customDefinitionMap) { + public static TriState convertAndPutComponents(DataComponents itemMap, DataComponentMap customDefinitionMap) { + TriState canDestroyInCreative = TriState.NOT_SET; for (DataComponent component : customDefinitionMap.keySet()) { + if (component == DataComponent.TOOL) { + canDestroyInCreative = TriState.fromBoolean(((ToolProperties) customDefinitionMap.get(component)).canDestroyBlocksInCreative()); + continue; + } ComponentConverter converter = converters.get(component); Object value = customDefinitionMap.get(component); converter.convertAndPut(itemMap, value); } + return canDestroyInCreative; } @FunctionalInterface diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 0b28d3304ab..90b12e15547 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.registry.populator; import com.google.common.collect.Multimap; +import it.unimi.dsi.fastutil.Pair; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -41,10 +42,12 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.custom.ComponentConverters; @@ -202,7 +205,7 @@ private static Optional checkPredicate(Map.Entry */ private static void checkComponents(CustomItemDefinition definition, Item javaItem) throws InvalidItemComponentsException { - DataComponents components = patchDataComponents(javaItem, definition); + DataComponents components = patchDataComponents(javaItem, definition).first(); int stackSize = components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); @@ -226,15 +229,14 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder componentBuilder = NbtMap.builder(); - DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition); + Pair patchedComponentInfo = patchDataComponents(vanillaJavaItem, customItemDefinition); + DataComponents components = patchedComponentInfo.first(); + boolean canDestroyInCreative = patchedComponentInfo.second() == TriState.NOT_SET ? !"sword".equals(vanillaMapping.getToolType()) : patchedComponentInfo.second() == TriState.TRUE; + setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder); - boolean canDestroyInCreative = true; - computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder); - if (vanillaMapping.getToolType() != null) { - canDestroyInCreative = - } - itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + computeToolProperties(itemProperties, componentBuilder); + computeCreativeDestroyProperties(canDestroyInCreative, itemProperties, componentBuilder); Equippable equippable = components.get(DataComponentType.EQUIPPABLE); if (equippable != null) { @@ -362,6 +364,13 @@ private static void computeToolProperties(NbtMapBuilder itemProperties, NbtMapBu itemProperties.putFloat("mining_speed", 1.0F); } + private static void computeCreativeDestroyProperties(boolean canDestroyInCreative, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { + itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + componentBuilder.putCompound("minecraft:can_destroy_in_creative", NbtMap.builder() + .putBoolean("value", canDestroyInCreative) + .build()); + } + private static void computeArmorProperties(Equippable equippable, int protectionValue, NbtMapBuilder componentBuilder) { switch (equippable.slot()) { case HELMET -> { @@ -629,10 +638,13 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { return false; } - private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { + /** + * Temporary workaround to return can destroy in creative boolean in the pair, see {@link ComponentConverters#convertAndPutComponents(DataComponents, DataComponentMap)}. + */ + private static Pair patchDataComponents(Item javaItem, CustomItemDefinition definition) { DataComponents convertedComponents = new DataComponents(new HashMap<>()); - ComponentConverters.convertAndPutComponents(convertedComponents, definition.components()); - return javaItem.gatherComponents(convertedComponents); + TriState canDestroyInCreative = ComponentConverters.convertAndPutComponents(convertedComponents, definition.components()); + return Pair.of(javaItem.gatherComponents(convertedComponents), canDestroyInCreative); } @SuppressWarnings("unchecked") From 261b6caa7b1b26a58aff0a565d70d1ea5859e0c6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 15:51:44 +0000 Subject: [PATCH 085/118] Add tool component mappings reader --- .../components/DataComponentReaders.java | 2 + .../readers/ToolPropertiesReader.java | 49 +++++++++++++++++++ .../registry/mappings/util/MappingsUtil.java | 1 + 3 files changed, 52 insertions(+) create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 6d6af576d07..880a5fa440f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.registry.mappings.components.readers.EquippableReader; import org.geysermc.geyser.registry.mappings.components.readers.FoodPropertiesReader; import org.geysermc.geyser.registry.mappings.components.readers.IntComponentReader; +import org.geysermc.geyser.registry.mappings.components.readers.ToolPropertiesReader; import org.geysermc.geyser.registry.mappings.components.readers.UseCooldownReader; import org.geysermc.geyser.util.MinecraftKey; @@ -61,5 +62,6 @@ public static void readDataComponent(CustomItemDefinition.Builder builder, Key k READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponent.MAX_STACK_SIZE, 1, 99)); READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); READERS.put(MinecraftKey.key("enchantable"), new EnchantableReader()); + READERS.put(MinecraftKey.key("tool"), new ToolPropertiesReader()); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java new file mode 100644 index 00000000000..9ce80241892 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; +import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; + +public class ToolPropertiesReader extends DataComponentReader { + + public ToolPropertiesReader() { + super(DataComponent.TOOL); + } + + @Override + protected ToolProperties readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + MappingsUtil.requireObject(node, "reading component", context); + + return new ToolProperties(MappingsUtil.readOrDefault(node, "can_destroy_blocks_in_creative", NodeReader.BOOLEAN, true, context)); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java index de28bfdf463..27ac49b528e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -54,6 +54,7 @@ public static void readIfPresent(JsonNode node, String name, Consumer con } } + // TODO don't use this method but throw an error in the above reading methods public static void requireObject(JsonNode node, String task, String... context) throws InvalidCustomMappingsFileException { if (node == null || !node.isObject()) { throw new InvalidCustomMappingsFileException(task, "expected an object", context); From c63386aab8e044dad77019459187fc4c95618059 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 16:09:26 +0000 Subject: [PATCH 086/118] Update documentation --- .../geyser/api/item/custom/v2/CustomItemDefinition.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 4dad68afcc7..27b04f8e8cd 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -108,7 +108,7 @@ public interface CustomItemDefinition { /** * The item's data components. It is expected that the item always has these components on the server. If the components mismatch, bugs will occur. * - *

Currently, the following components are supported:

+ *

Currently, the following components are (somewhat) supported:

* *
    *
  • {@code minecraft:consumable} ({@link DataComponent#CONSUMABLE})
  • @@ -118,6 +118,7 @@ public interface CustomItemDefinition { *
  • {@code minecraft:max_stack_size} ({@link DataComponent#MAX_STACK_SIZE})
  • *
  • {@code minecraft:use_cooldown} ({@link DataComponent#USE_COOLDOWN})
  • *
  • {@code minecraft:enchantable} ({@link DataComponent#ENCHANTABLE})
  • + *
  • {@code minecraft:tool} ({@link DataComponent#TOOL})
  • *
* *

Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, From 2558620c1948cca6cde8117c4587fd8ebdbb2c38 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 16:22:05 +0000 Subject: [PATCH 087/118] Add repairable mappings reader --- .../custom/v2/component/DataComponent.java | 1 + .../item/custom/v2/component/Repairable.java | 31 ++++++++++ .../components/DataComponentReaders.java | 2 + .../components/readers/RepairableReader.java | 58 +++++++++++++++++++ .../registry/mappings/util/MappingsUtil.java | 18 ++++++ 5 files changed, 110 insertions(+) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java create mode 100644 core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index 7557828ec91..8ad00b83758 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java @@ -55,6 +55,7 @@ public final class DataComponent { *

Like other components, when not set this will fall back to the default value.

*/ public static final DataComponent TOOL = create("tool"); + public static final DataComponent REPAIRABLE = create("repairable"); private final Identifier identifier; private final Predicate validator; diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java new file mode 100644 index 00000000000..9a7c01d0696 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.component; + +import org.geysermc.geyser.api.util.Identifier; + +public record Repairable(Identifier... items) { +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 880a5fa440f..90517e107ef 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.registry.mappings.components.readers.EquippableReader; import org.geysermc.geyser.registry.mappings.components.readers.FoodPropertiesReader; import org.geysermc.geyser.registry.mappings.components.readers.IntComponentReader; +import org.geysermc.geyser.registry.mappings.components.readers.RepairableReader; import org.geysermc.geyser.registry.mappings.components.readers.ToolPropertiesReader; import org.geysermc.geyser.registry.mappings.components.readers.UseCooldownReader; import org.geysermc.geyser.util.MinecraftKey; @@ -63,5 +64,6 @@ public static void readDataComponent(CustomItemDefinition.Builder builder, Key k READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); READERS.put(MinecraftKey.key("enchantable"), new EnchantableReader()); READERS.put(MinecraftKey.key("tool"), new ToolPropertiesReader()); + READERS.put(MinecraftKey.key("repairable"), new RepairableReader()); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java new file mode 100644 index 00000000000..77bf9612b3a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.mappings.components.readers; + +import com.fasterxml.jackson.databind.JsonNode; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; +import org.geysermc.geyser.api.item.custom.v2.component.Repairable; +import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import org.geysermc.geyser.registry.mappings.components.DataComponentReader; +import org.geysermc.geyser.registry.mappings.util.MappingsUtil; +import org.geysermc.geyser.registry.mappings.util.NodeReader; + +import java.util.List; + +public class RepairableReader extends DataComponentReader { + + public RepairableReader() { + super(DataComponent.REPAIRABLE); + } + + @Override + protected Repairable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + MappingsUtil.requireObject(node, "reading component", context); + + try { + Identifier item = MappingsUtil.readOrThrow(node, "items", NodeReader.IDENTIFIER, context); + return new Repairable(item); + } catch (InvalidCustomMappingsFileException exception) { + List items = MappingsUtil.readArrayOrThrow(node, "items", NodeReader.IDENTIFIER, context); + return new Repairable(items.toArray(Identifier[]::new)); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java index 27ac49b528e..4a60a181e6f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -28,6 +28,8 @@ import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; public class MappingsUtil { @@ -48,6 +50,22 @@ public static T readOrDefault(JsonNode node, String name, NodeReader conv return converter.read(object, formatTask(name), context); } + public static List readArrayOrThrow(JsonNode node, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { + JsonNode array = node.get(name); + if (array == null) { + throw new InvalidCustomMappingsFileException(formatTask(name), "key is required but was not present", context); + } else if (!array.isArray()) { + throw new InvalidCustomMappingsFileException(formatTask(name), "key must be an array", context); + } + + List objects = new ArrayList<>(); + for (int i = 0; i < node.size(); i++) { + JsonNode object = node.get(i); + objects.add(converter.read(object, "reading object " + i + " in key \"" + name + "\"", context)); + } + return objects; + } + public static void readIfPresent(JsonNode node, String name, Consumer consumer, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { if (node.has(name)) { consumer.accept(converter.read(node.get(name), formatTask(name), context)); From 373eeacad86b8c21de5dde69de74b60b9e774371 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 17:00:25 +0000 Subject: [PATCH 088/118] Implement repairable component in custom item registry populator --- .../item/custom/ComponentConverters.java | 41 ++++++----- .../CustomItemRegistryPopulator.java | 72 +++++++++++++++---- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java index 560c5a7ca1a..ad83e8839c8 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java @@ -25,10 +25,10 @@ package org.geysermc.geyser.item.custom; +import org.cloudburstmc.nbt.NbtMapBuilder; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; -import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; -import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.api.item.custom.v2.component.Repairable; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; @@ -51,7 +51,23 @@ // One can't be imported, and as such its full qualifier (e.g. org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable) would have to be used. // That would be a mess to code in, and as such this code here was carefully designed to only require one set of component classes by name (the MCPL ones). // -// It is VERY IMPORTANT to note that for every component in the API, a converter to MCPL must be put here (better solutions are welcome). +// It is VERY IMPORTANT to note that for every component in the API, a converter to MCPL must be put here (there are some exceptions as noted in the Javadoc, better solutions are welcome). +/** + * This class is used to convert components from the API module to MCPL ones. + * + *

+ * Most components convert over nicely, and it is very much preferred to have every API component have a converter in here. However, this is not always possible. At the moment, there are 2 exceptions: + * + *

    + *
  • The {@link DataComponent#TOOL} component doesn't convert over to its MCPL counterpart as the only reason it's in the API as of right now is the {@code canDestroyInCreative} property. This is a 1.21.5 property, + * and once Geyser for 1.21.5 releases, this component should have a converter in here.
  • + *
  • The MCPL counterpart of the {@link DataComponent#REPAIRABLE} component is just an ID holder set, which can't be used in the custom item registry populator. + * Also see {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator#computeRepairableProperties(Repairable, NbtMapBuilder)}.
  • + *
+ * + * For both of these cases proper accommodations have been made in the {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator}. + *

+ */ public class ComponentConverters { private static final Map, ComponentConverter> converters = new HashMap<>(); @@ -100,24 +116,15 @@ private static void registerConverter(DataComponent component, ComponentC converters.put(component, converter); } - /** - * Temporary workaround: while 1.21.5 has not released yet, this method returns the {@link ToolProperties#canDestroyBlocksInCreative()} value. - * - *

When 1.21.5 does release, this value will be mapped into the MCPL tool component, and this method will return nothing again.

- */ @SuppressWarnings({"unchecked", "rawtypes"}) - public static TriState convertAndPutComponents(DataComponents itemMap, DataComponentMap customDefinitionMap) { - TriState canDestroyInCreative = TriState.NOT_SET; + public static void convertAndPutComponents(DataComponents itemMap, DataComponentMap customDefinitionMap) { for (DataComponent component : customDefinitionMap.keySet()) { - if (component == DataComponent.TOOL) { - canDestroyInCreative = TriState.fromBoolean(((ToolProperties) customDefinitionMap.get(component)).canDestroyBlocksInCreative()); - continue; - } ComponentConverter converter = converters.get(component); - Object value = customDefinitionMap.get(component); - converter.convertAndPut(itemMap, value); + if (converter != null) { + Object value = customDefinitionMap.get(component); + converter.convertAndPut(itemMap, value); + } } - return canDestroyInCreative; } @FunctionalInterface diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 90b12e15547..04c2c0656cd 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.registry.populator; import com.google.common.collect.Multimap; -import it.unimi.dsi.fastutil.Pair; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -42,18 +41,20 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; +import org.geysermc.geyser.api.item.custom.v2.component.Repairable; +import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; -import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.custom.ComponentConverters; import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.item.exception.InvalidItemComponentsException; import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; @@ -65,6 +66,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -199,13 +201,13 @@ private static Optional checkPredicate(Map.EntryNote that, this method only checks for illegal combinations of item components. It is expected that the values of the components separately have - * already been validated (for example, it is expected that stack size is in the range [1, 99]).

+ *

Note that, component validation is preferred to occur early in the API module. This method should primarily check for illegal combinations of item components. + * It is expected that the values of the components separately have already been validated when possible (for example, it is expected that stack size is in the range [1, 99]).

*/ private static void checkComponents(CustomItemDefinition definition, Item javaItem) throws InvalidItemComponentsException { - DataComponents components = patchDataComponents(javaItem, definition).first(); + DataComponents components = patchDataComponents(javaItem, definition); int stackSize = components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); @@ -214,6 +216,15 @@ private static void checkComponents(CustomItemDefinition definition, Item javaIt } else if (stackSize > 1 && maxDamage > 0) { throw new InvalidItemComponentsException("Stack size must be 1 when max damage is above 0"); } + + Repairable repairable = definition.components().get(DataComponent.REPAIRABLE); + if (repairable != null) { + for (Identifier item : repairable.items()) { + if (Registries.JAVA_ITEM_IDENTIFIERS.get(item.toString()) == null) { + throw new InvalidItemComponentsException("Unknown repair item " + item + " in minecraft:repairable component"); + } + } + } } public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { @@ -229,15 +240,23 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder componentBuilder = NbtMap.builder(); - Pair patchedComponentInfo = patchDataComponents(vanillaJavaItem, customItemDefinition); - DataComponents components = patchedComponentInfo.first(); - boolean canDestroyInCreative = patchedComponentInfo.second() == TriState.NOT_SET ? !"sword".equals(vanillaMapping.getToolType()) : patchedComponentInfo.second() == TriState.TRUE; - + DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition); setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder); computeToolProperties(itemProperties, componentBuilder); + + // Temporary workaround: when 1.21.5 releases, this value will be mapped to an MCPL tool component, and this code will look nicer + // since we can get the value from the vanilla item component instead of using the vanilla mapping. + ToolProperties toolProperties = customItemDefinition.components().get(DataComponent.TOOL); + boolean canDestroyInCreative = toolProperties == null ? !"sword".equals(vanillaMapping.getToolType()) : toolProperties.canDestroyBlocksInCreative(); computeCreativeDestroyProperties(canDestroyInCreative, itemProperties, componentBuilder); + // Using API component here because MCPL one is just an ID holder set + Repairable repairable = customItemDefinition.components().get(DataComponent.REPAIRABLE); + if (repairable != null) { + computeRepairableProperties(repairable, componentBuilder); + } + Equippable equippable = components.get(DataComponentType.EQUIPPABLE); if (equippable != null) { computeArmorProperties(equippable, customItemDefinition.bedrockOptions().protectionValue(), componentBuilder); @@ -371,6 +390,25 @@ private static void computeCreativeDestroyProperties(boolean canDestroyInCreativ .build()); } + /** + * Repairable component should already have been validated for valid Java items in {@link CustomItemRegistryPopulator#checkComponents(CustomItemDefinition, Item)}. + * + *

This method passes the Java identifiers straight to bedrock - which isn't perfect.

+ */ + private static void computeRepairableProperties(Repairable repairable, NbtMapBuilder componentBuilder) { + List items = Arrays.stream(repairable.items()) + .map(identifier -> NbtMap.builder() + .putString("name", identifier.toString()) + .build()).toList(); + + componentBuilder.putCompound("minecraft:repairable", NbtMap.builder() + .putList("repair_items", NbtType.COMPOUND, NbtMap.builder() + .putList("items", NbtType.COMPOUND, items) + .putFloat("repair_amount", 0.0F) + .build()) + .build()); + } + private static void computeArmorProperties(Equippable equippable, int protectionValue, NbtMapBuilder componentBuilder) { switch (equippable.slot()) { case HELMET -> { @@ -639,12 +677,16 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { } /** - * Temporary workaround to return can destroy in creative boolean in the pair, see {@link ComponentConverters#convertAndPutComponents(DataComponents, DataComponentMap)}. + * Converts the API components to MCPL ones using the converters in {@link ComponentConverters}, and applies these on top of the default item components. + * + *

Note that note every API component has a converter in {@link ComponentConverters}. See the documentation there.

+ * + * @see ComponentConverters */ - private static Pair patchDataComponents(Item javaItem, CustomItemDefinition definition) { + private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) { DataComponents convertedComponents = new DataComponents(new HashMap<>()); - TriState canDestroyInCreative = ComponentConverters.convertAndPutComponents(convertedComponents, definition.components()); - return Pair.of(javaItem.gatherComponents(convertedComponents), canDestroyInCreative); + ComponentConverters.convertAndPutComponents(convertedComponents, definition.components()); + return javaItem.gatherComponents(convertedComponents); } @SuppressWarnings("unchecked") From fbbf210e445149ab06fb7db1b9f1c7fd782d3427 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 17:02:09 +0000 Subject: [PATCH 089/118] Documentation whoopsy --- .../geyser/registry/populator/CustomItemRegistryPopulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 04c2c0656cd..1dca92a65c3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -679,7 +679,7 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { /** * Converts the API components to MCPL ones using the converters in {@link ComponentConverters}, and applies these on top of the default item components. * - *

Note that note every API component has a converter in {@link ComponentConverters}. See the documentation there.

+ *

Note that not every API component has a converter in {@link ComponentConverters}. See the documentation there.

* * @see ComponentConverters */ From ab174b50d23883f86a630cd0b293d31b40a70d47 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Jan 2025 17:10:07 +0000 Subject: [PATCH 090/118] Javadoc broke my build again (this isn't proper HTML) --- .../item/custom/ComponentConverters.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java index ad83e8839c8..912ac1b62f1 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java @@ -55,18 +55,14 @@ /** * This class is used to convert components from the API module to MCPL ones. * - *

- * Most components convert over nicely, and it is very much preferred to have every API component have a converter in here. However, this is not always possible. At the moment, there are 2 exceptions: - * - *

    - *
  • The {@link DataComponent#TOOL} component doesn't convert over to its MCPL counterpart as the only reason it's in the API as of right now is the {@code canDestroyInCreative} property. This is a 1.21.5 property, - * and once Geyser for 1.21.5 releases, this component should have a converter in here.
  • - *
  • The MCPL counterpart of the {@link DataComponent#REPAIRABLE} component is just an ID holder set, which can't be used in the custom item registry populator. - * Also see {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator#computeRepairableProperties(Repairable, NbtMapBuilder)}.
  • - *
- * - * For both of these cases proper accommodations have been made in the {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator}. - *

+ *

Most components convert over nicely, and it is very much preferred to have every API component have a converter in here. However, this is not always possible. At the moment, there are 2 exceptions: + *

    + *
  • The {@link DataComponent#TOOL} component doesn't convert over to its MCPL counterpart as the only reason it's in the API as of right now is the {@code canDestroyInCreative} property. This is a 1.21.5 property, + * and once Geyser for 1.21.5 releases, this component should have a converter in here.
  • + *
  • The MCPL counterpart of the {@link DataComponent#REPAIRABLE} component is just an ID holder set, which can't be used in the custom item registry populator. + * Also see {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator#computeRepairableProperties(Repairable, NbtMapBuilder)}.
  • + *
+ * For both of these cases proper accommodations have been made in the {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator}. */ public class ComponentConverters { private static final Map, ComponentConverter> converters = new HashMap<>(); From 422c5a9136c7b2ce6f1f980080a40894552ab245 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 17 Jan 2025 23:31:22 +0000 Subject: [PATCH 091/118] Update MCPL --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 90f31450a34..5898228764c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ protocol-common = "3.0.0.Beta5-20241213.160944-20" protocol-codec = "3.0.0.Beta5-20241213.160944-20" raknet = "1.0.0.CR3-20240416.144209-1" minecraftauth = "4.1.1" -mcprotocollib = "1.21.4-20241222.190029-11" +mcprotocollib = "1.21.4-20250117.194238-13" adventure = "4.14.0" adventure-platform = "4.3.0" junit = "5.9.2" From a5d6fb17225675241f4d58c92cc993c4c535a1c1 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 17 Jan 2025 23:34:19 +0000 Subject: [PATCH 092/118] Consistency renames, start on has component predicate --- .../api/item/custom/CustomItemData.java | 6 ++-- .../v2/predicate/CustomItemPredicate.java | 12 ++++---- .../predicate/RangeDispatchItemPredicate.java | 4 +-- ...va => RangeDispatchPredicateProperty.java} | 2 +- .../condition/ConditionPredicateProperty.java | 29 +++++++++++++++++++ .../predicate/RangeDispatchPredicate.java | 4 +-- .../loader/ProviderRegistryLoader.java | 4 +-- .../registry/mappings/util/NodeReader.java | 4 +-- .../mappings/versions/MappingsReader_v2.java | 4 +-- 9 files changed, 49 insertions(+), 20 deletions(-) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{RangeDispatchProperty.java => RangeDispatchPredicateProperty.java} (97%) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 4c7d7d36e9d..1f1eddea3c1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -32,7 +32,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.api.util.TriState; @@ -147,10 +147,10 @@ default CustomItemDefinition.Builder toDefinition(Identifier javaItem) { CustomItemOptions options = customItemOptions(); if (options.customModelData().isPresent()) { - definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchProperty.CUSTOM_MODEL_DATA, options.customModelData().getAsInt())); + definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchPredicateProperty.CUSTOM_MODEL_DATA, options.customModelData().getAsInt())); } if (options.damagePredicate().isPresent()) { - definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchProperty.DAMAGE, options.damagePredicate().getAsInt())); + definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchPredicateProperty.DAMAGE, options.damagePredicate().getAsInt())); } if (options.unbreakable() != TriState.NOT_SET) { definition.predicate(CustomItemPredicate.condition(ConditionProperty.UNBREAKABLE, Objects.requireNonNull(options.unbreakable().toBoolean()))); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java index 2bd08643241..cf482f4c813 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -59,19 +59,19 @@ static MatchItemPredicate match(MatchPredicateProperty property, T dat return GeyserApi.api().provider(MatchItemPredicate.class, property, data); } - static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchPredicateProperty property, double threshold) { return rangeDispatch(property, threshold, 1.0); } - static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchPredicateProperty property, double threshold, double scale) { return rangeDispatch(property, threshold, scale, false, 0); } - static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, boolean normalizeIfPossible) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchPredicateProperty property, double threshold, boolean normalizeIfPossible) { return rangeDispatch(property, threshold, 1.0, normalizeIfPossible, 0); } - static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchPredicateProperty property, double threshold, double scale, boolean normalizeIfPossible) { return rangeDispatch(property, threshold, scale, normalizeIfPossible, 0); } @@ -82,9 +82,9 @@ static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, * @param threshold the threshold the property should be above. * @param scale factor to multiply the property value with before comparing it with the threshold. Defaults to 1.0. * @param normalizeIfPossible if the property value should be normalised to a value between 0.0 and 1.0 before scaling and comparing. Defaults to false. Only works for certain properties. - * @param index only used for the {@link RangeDispatchProperty#CUSTOM_MODEL_DATA} property, determines which float of the item's custom model data to check. Defaults to 0. + * @param index only used for the {@link RangeDispatchPredicateProperty#CUSTOM_MODEL_DATA} property, determines which float of the item's custom model data to check. Defaults to 0. */ - static RangeDispatchItemPredicate rangeDispatch(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) { + static RangeDispatchItemPredicate rangeDispatch(RangeDispatchPredicateProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) { return GeyserApi.api().provider(RangeDispatchItemPredicate.class, property, threshold, scale, normalizeIfPossible, index); } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java index d0269c16915..1b7c228c290 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchItemPredicate.java @@ -26,11 +26,11 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; /** - * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#rangeDispatch(RangeDispatchProperty, double, double, boolean, int) + * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#rangeDispatch(RangeDispatchPredicateProperty, double, double, boolean, int) */ public interface RangeDispatchItemPredicate extends CustomItemPredicate { - RangeDispatchProperty property(); + RangeDispatchPredicateProperty property(); double threshold(); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicateProperty.java similarity index 97% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicateProperty.java index b1d1de5afda..5315d5bcd93 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicateProperty.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; -public enum RangeDispatchProperty { +public enum RangeDispatchPredicateProperty { /** * Checks the item's bundle fullness. Returns the total stack count of all the items in a bundle. * diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java new file mode 100644 index 00000000000..679710f9ec3 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.item.custom.v2.predicate.condition; + +public class ConditionPredicateProperty { +} diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java index 081afbf79f8..1a5958ff34a 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/RangeDispatchPredicate.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.item.custom.predicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; -public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements RangeDispatchItemPredicate { +public record RangeDispatchPredicate(RangeDispatchPredicateProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements RangeDispatchItemPredicate { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 36ba9ac8cca..6686061a1df 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -45,7 +45,7 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.api.util.Identifier; @@ -103,7 +103,7 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); providers.put(ConditionItemPredicate.class, args -> new ConditionPredicate((ConditionProperty) args[0], (boolean) args[1], (int) args[2])); providers.put(MatchItemPredicate.class, args -> new MatchPredicate<>((MatchPredicateProperty) args[0], args[1])); - providers.put(RangeDispatchItemPredicate.class, args -> new RangeDispatchPredicate((RangeDispatchProperty) args[0], (double) args[1], (double) args[2], (boolean) args[3], (int) args[4])); + providers.put(RangeDispatchItemPredicate.class, args -> new RangeDispatchPredicate((RangeDispatchPredicateProperty) args[0], (double) args[1], (double) args[2], (boolean) args[3], (int) args[4])); // cameras providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 5fecd5d5a12..ff6b1889346 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -30,7 +30,7 @@ import org.geysermc.geyser.api.item.custom.v2.component.Equippable; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; @@ -105,7 +105,7 @@ public interface NodeReader { NodeReader CHARGE_TYPE = ofEnum(ChargeType.class); - NodeReader RANGE_DISPATCH_PROPERTY = ofEnum(RangeDispatchProperty.class); + NodeReader RANGE_DISPATCH_PROPERTY = ofEnum(RangeDispatchPredicateProperty.class); NodeReader CONSUMABLE_ANIMATION = ofEnum(Consumable.Animation.class); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 9e946836d18..cb83b97832b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -36,7 +36,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.Identifier; @@ -236,7 +236,7 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo } } case "range_dispatch" -> { - RangeDispatchProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.RANGE_DISPATCH_PROPERTY, context); + RangeDispatchPredicateProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.RANGE_DISPATCH_PROPERTY, context); double threshold = MappingsUtil.readOrThrow(node, "threshold", NodeReader.DOUBLE, context); double scale = MappingsUtil.readOrDefault(node, "scale", NodeReader.DOUBLE, 1.0, context); From af76cd0af4a2a530ce92b0c05655e25553c22c3c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 17 Jan 2025 23:49:49 +0000 Subject: [PATCH 093/118] Update API module with has component predicate --- .../api/item/custom/CustomItemData.java | 5 ++- .../v2/predicate/ConditionItemPredicate.java | 10 +++-- .../v2/predicate/ConditionProperty.java | 45 ------------------- .../v2/predicate/CustomItemPredicate.java | 17 ++++--- .../condition/ConditionPredicateProperty.java | 35 ++++++++++++++- .../custom/predicate/ConditionPredicate.java | 1 - .../loader/ProviderRegistryLoader.java | 1 - .../registry/mappings/util/NodeReader.java | 1 - .../mappings/versions/MappingsReader_v2.java | 1 - .../CustomItemRegistryPopulator.java | 1 - 10 files changed, 54 insertions(+), 63 deletions(-) delete mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 1f1eddea3c1..ba80efbdd44 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -30,9 +30,9 @@ import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.api.util.TriState; @@ -153,7 +153,8 @@ default CustomItemDefinition.Builder toDefinition(Identifier javaItem) { definition.predicate(CustomItemPredicate.rangeDispatch(RangeDispatchPredicateProperty.DAMAGE, options.damagePredicate().getAsInt())); } if (options.unbreakable() != TriState.NOT_SET) { - definition.predicate(CustomItemPredicate.condition(ConditionProperty.UNBREAKABLE, Objects.requireNonNull(options.unbreakable().toBoolean()))); + definition.predicate(CustomItemPredicate.condition(ConditionPredicateProperty.HAS_COMPONENT, + Objects.requireNonNull(options.unbreakable().toBoolean()), new Identifier("minecraft", "unbreakable"))); } return definition; } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java index a266ec78be3..6ceb0d68b8c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionItemPredicate.java @@ -25,14 +25,16 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; + /** - * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#condition(ConditionProperty, boolean, int) + * @see org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate#condition(ConditionPredicateProperty, boolean, Object) */ -public interface ConditionItemPredicate extends CustomItemPredicate { +public interface ConditionItemPredicate extends CustomItemPredicate { - ConditionProperty property(); + ConditionPredicateProperty property(); boolean expected(); - int index(); + T data(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java deleted file mode 100644 index 0f7d34d3fc3..00000000000 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionProperty.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2025 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.item.custom.v2.predicate; - -public enum ConditionProperty { - /** - * Checks if the item is broken (has 1 durability point left). - */ - BROKEN, - /** - * Checks if the item is damaged (has non-full durability). - */ - DAMAGED, - /** - * Checks if the item is unbreakable. - */ - UNBREAKABLE, - /** - * Returns one of the item's custom model data flags, defaults to false. - */ - CUSTOM_MODEL_DATA -} diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java index cf482f4c813..3a1747d7a13 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java @@ -26,16 +26,21 @@ package org.geysermc.geyser.api.item.custom.v2.predicate; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; public interface CustomItemPredicate { - static ConditionItemPredicate condition(ConditionProperty property) { + static ConditionItemPredicate condition(ConditionPredicateProperty property) { return condition(property, true); } - static ConditionItemPredicate condition(ConditionProperty property, boolean expected) { - return condition(property, expected, 0); + static ConditionItemPredicate condition(ConditionPredicateProperty property, T data) { + return condition(property, true, data); + } + + static ConditionItemPredicate condition(ConditionPredicateProperty property, boolean expected) { + return condition(property, expected, null); } /** @@ -43,10 +48,10 @@ static ConditionItemPredicate condition(ConditionProperty property, boolean expe * * @param property the property to check. * @param expected whether the property should be true or false. Defaults to true. - * @param index only used for the {@link ConditionProperty#CUSTOM_MODEL_DATA} property, determines which flag of the item's custom model data to check. Defaults to 0. + * @param data the data used by the predicate. Only used by some predicates, defaults to null. */ - static ConditionItemPredicate condition(ConditionProperty property, boolean expected, int index) { - return GeyserApi.api().provider(ConditionItemPredicate.class, property, expected, index); + static ConditionItemPredicate condition(ConditionPredicateProperty property, boolean expected, T data) { + return GeyserApi.api().provider(ConditionItemPredicate.class, property, expected, data); } /** diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java index 679710f9ec3..81eb7f92585 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/condition/ConditionPredicateProperty.java @@ -25,5 +25,38 @@ package org.geysermc.geyser.api.item.custom.v2.predicate.condition; -public class ConditionPredicateProperty { +import org.geysermc.geyser.api.util.Identifier; + +public final class ConditionPredicateProperty { + + /** + * Checks if the item is broken (has 1 durability point left). + */ + public static final ConditionPredicateProperty BROKEN = createNoData(); + /** + * Checks if the item is damaged (has non-full durability). + */ + public static final ConditionPredicateProperty DAMAGED = createNoData(); + /** + * Returns one of the item's custom model data flags, defaults to false. Data in the predicate is an integer that sets the index of the flags to check. + */ + public static final ConditionPredicateProperty CUSTOM_MODEL_DATA = create(); + /** + * Returns true if the item stack has a component with the identifier set in the predicate data. + */ + public static final ConditionPredicateProperty HAS_COMPONENT = create(); + + public final boolean requiresData; + + private ConditionPredicateProperty(boolean requiresData) { + this.requiresData = requiresData; + } + + private static ConditionPredicateProperty create() { + return new ConditionPredicateProperty<>(true); + } + + private static ConditionPredicateProperty createNoData() { + return new ConditionPredicateProperty<>(false); + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java index b169fb483e2..aa7205c67b0 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.item.custom.predicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements ConditionItemPredicate { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 6686061a1df..5206f1a405a 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -42,7 +42,6 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index ff6b1889346..80a0456b0dc 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -28,7 +28,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.api.item.custom.v2.component.Consumable; import org.geysermc.geyser.api.item.custom.v2.component.Equippable; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index cb83b97832b..aa1663d7f2e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -34,7 +34,6 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 1dca92a65c3..9fadf8d76d7 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -44,7 +44,6 @@ import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.Repairable; import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; -import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; From 505a74bcf56ba1ce1949aa5bb17bfed5cc825a87 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 00:09:57 +0000 Subject: [PATCH 094/118] Update implementations and implement has component predicate --- .../custom/predicate/ConditionPredicate.java | 3 +- .../loader/ProviderRegistryLoader.java | 3 +- .../registry/mappings/util/NodeReader.java | 23 ++++++++++- .../mappings/versions/MappingsReader_v2.java | 17 +++++++-- .../CustomItemRegistryPopulator.java | 9 ++++- .../translator/item/CustomItemTranslator.java | 38 ++++++++++++++----- 6 files changed, 75 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java index aa7205c67b0..8474f38cc54 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/predicate/ConditionPredicate.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.item.custom.predicate; import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; -public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements ConditionItemPredicate { +public record ConditionPredicate(ConditionPredicateProperty property, boolean expected, T data) implements ConditionItemPredicate { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 5206f1a405a..2fb7ab39b05 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -45,6 +45,7 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.MatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.api.util.Identifier; @@ -100,7 +101,7 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov // items v2 providers.put(CustomItemDefinition.Builder.class, args -> new GeyserCustomItemDefinition.Builder((Identifier) args[0], (Identifier) args[1])); providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); - providers.put(ConditionItemPredicate.class, args -> new ConditionPredicate((ConditionProperty) args[0], (boolean) args[1], (int) args[2])); + providers.put(ConditionItemPredicate.class, args -> new ConditionPredicate<>((ConditionPredicateProperty) args[0], (boolean) args[1], args[2])); providers.put(MatchItemPredicate.class, args -> new MatchPredicate<>((MatchPredicateProperty) args[0], args[1])); providers.put(RangeDispatchItemPredicate.class, args -> new RangeDispatchPredicate((RangeDispatchPredicateProperty) args[0], (double) args[1], (double) args[2], (boolean) args[3], (int) args[4])); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 80a0456b0dc..9d565c5e8a1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -30,12 +30,15 @@ import org.geysermc.geyser.api.item.custom.v2.component.Equippable; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import java.util.Arrays; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; @@ -100,7 +103,14 @@ public interface NodeReader { NodeReader PREDICATE_STRATEGY = ofEnum(PredicateStrategy.class); - NodeReader CONDITION_PROPERTY = ofEnum(ConditionProperty.class); + NodeReader> CONDITION_PROPERTY = ofMap( + Map.of( + "broken", ConditionPredicateProperty.BROKEN, + "damaged", ConditionPredicateProperty.DAMAGED, + "custom_model_data", ConditionPredicateProperty.CUSTOM_MODEL_DATA, + "has_component", ConditionPredicateProperty.HAS_COMPONENT + ) + ); NodeReader CHARGE_TYPE = ofEnum(ChargeType.class); @@ -121,6 +131,17 @@ static > NodeReader ofEnum(Class clazz) { }); } + static NodeReader ofMap(Map map) { + return NON_EMPTY_STRING.andThen(String::toLowerCase).andThen(s -> { + T value = map.get(s); + if (value == null) { + throw new InvalidCustomMappingsFileException("unknown element, must be one of [" + + String.join(", ", map.keySet()).toLowerCase() + "]"); + } + return value; + }); + } + static NodeReader boundedInt(int min, int max) { return INT.validate(i -> i >= min && i <= max, "integer must be in range [" + min + ", " + max + "]"); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index aa1663d7f2e..acb02013e1e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString; import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.Identifier; @@ -211,12 +212,20 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo switch (type) { case "condition" -> { - ConditionProperty conditionProperty = MappingsUtil.readOrThrow(node, "property", NodeReader.CONDITION_PROPERTY, context); + ConditionPredicateProperty conditionProperty = MappingsUtil.readOrThrow(node, "property", NodeReader.CONDITION_PROPERTY, context); boolean expected = MappingsUtil.readOrDefault(node, "expected", NodeReader.BOOLEAN, true, context); - int index = MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context); - // Note that index is only used for the CUSTOM_MODEL_DATA property, but we allow specifying it for other properties anyway - builder.predicate(CustomItemPredicate.condition(conditionProperty, expected, index)); + if (!conditionProperty.requiresData) { + builder.predicate(CustomItemPredicate.condition((ConditionPredicateProperty) conditionProperty, expected)); + } else if (conditionProperty == ConditionPredicateProperty.CUSTOM_MODEL_DATA) { + int index = MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context); + builder.predicate(CustomItemPredicate.condition(ConditionPredicateProperty.CUSTOM_MODEL_DATA, expected, index)); + } else if (conditionProperty == ConditionPredicateProperty.HAS_COMPONENT) { + Identifier component = MappingsUtil.readOrThrow(node, "component", NodeReader.IDENTIFIER, context); + builder.predicate(CustomItemPredicate.condition(ConditionPredicateProperty.HAS_COMPONENT, expected, component)); + } else { + throw new InvalidCustomMappingsFileException("reading condition predicate", "unimplemented reading of condition predicate property!", context); + } } case "match" -> { String property = MappingsUtil.readOrThrow(node, "property", NodeReader.NON_EMPTY_STRING, context); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 9fadf8d76d7..7862295061e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -45,6 +45,7 @@ import org.geysermc.geyser.api.item.custom.v2.component.Repairable; import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; @@ -74,6 +75,7 @@ import java.util.Set; public class CustomItemRegistryPopulator { + private static final Identifier UNBREAKABLE_COMPONENT = new Identifier("minecraft", "unbreakable"); // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. // TODO these don't seem to be applying correctly private static final Map BEDROCK_ANIMATIONS = Map.of( @@ -668,8 +670,11 @@ private static NbtMap xyzToScaleList(float x, float y, float z) { private static boolean isUnbreakableItem(CustomItemDefinition definition) { for (CustomItemPredicate predicate : definition.predicates()) { - if (predicate instanceof ConditionPredicate condition && condition.property() == ConditionProperty.UNBREAKABLE && condition.expected()) { - return true; + if (predicate instanceof ConditionPredicate condition && condition.property() == ConditionPredicateProperty.HAS_COMPONENT && condition.expected()) { + Identifier component = (Identifier) condition.data(); + if (UNBREAKABLE_COMPONENT.equals(component)) { + return true; + } } } return false; diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index be6ce15c046..3e011a20fc2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -32,6 +32,7 @@ import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; +import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.item.custom.predicate.RangeDispatchPredicate; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; @@ -103,13 +104,29 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, } private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, int stackSize, DataComponents components) { - if (predicate instanceof ConditionPredicate condition) { - return switch (condition.property()) { - case BROKEN -> nextDamageWillBreak(components); - case DAMAGED -> isDamaged(components); - case UNBREAKABLE -> components.getOrDefault(DataComponentType.UNBREAKABLE, false); - case CUSTOM_MODEL_DATA -> getCustomBoolean(components, condition.index()); - } == condition.expected(); + if (predicate instanceof ConditionPredicate condition) { + ConditionPredicateProperty property = condition.property(); + boolean expected = condition.expected(); + if (property == ConditionPredicateProperty.BROKEN) { + return nextDamageWillBreak(components) == expected; + } else if (property == ConditionPredicateProperty.DAMAGED) { + return isDamaged(components) == expected; + } else if (property == ConditionPredicateProperty.CUSTOM_MODEL_DATA) { + Integer index = (Integer) condition.data(); + return getCustomBoolean(components, index) == expected; + } else if (property == ConditionPredicateProperty.HAS_COMPONENT) { + Identifier identifier = (Identifier) condition.data(); + if (identifier == null) { + return false; + } + Key component = MinecraftKey.identifierToKey(identifier); + for (DataComponentType componentType : components.getDataComponents().keySet()) { + if (componentType.getKey().equals(component)) { + return expected; + } + } + return !expected; + } } else if (predicate instanceof MatchPredicate match) { // TODO not much of a fan of the casts here, find a solution for the types? if (match.property() == MatchPredicateProperty.CHARGE_TYPE) { ChargeType expected = (ChargeType) match.data(); @@ -161,10 +178,13 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica return propertyValue >= rangeDispatch.threshold(); } - throw new IllegalStateException("Unimplemented predicate type"); + throw new IllegalStateException("Unimplemented predicate type: " + predicate); } - private static boolean getCustomBoolean(DataComponents components, int index) { + private static boolean getCustomBoolean(DataComponents components, Integer index) { + if (index == null) { + return false; + } Boolean b = getSafeCustomModelData(components, CustomModelData::flags, index); return b != null && b; } From dee7b5aa60057c6493877824feca802f41df9f93 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 00:14:59 +0000 Subject: [PATCH 095/118] Update reading of match predicate property --- .../registry/mappings/util/NodeReader.java | 15 ++++++++++--- .../mappings/versions/MappingsReader_v2.java | 21 ++++++++++--------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 9d565c5e8a1..a46ca16a8e4 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -32,9 +32,9 @@ import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.condition.ConditionPredicateProperty; import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType; +import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.api.util.Identifier; -import org.geysermc.geyser.item.custom.predicate.ConditionPredicate; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import java.util.Arrays; @@ -103,7 +103,7 @@ public interface NodeReader { NodeReader PREDICATE_STRATEGY = ofEnum(PredicateStrategy.class); - NodeReader> CONDITION_PROPERTY = ofMap( + NodeReader> CONDITION_PREDICATE_PROPERTY = ofMap( Map.of( "broken", ConditionPredicateProperty.BROKEN, "damaged", ConditionPredicateProperty.DAMAGED, @@ -112,9 +112,18 @@ public interface NodeReader { ) ); + NodeReader> MATCH_PREDICATE_PROPERTY = ofMap( + Map.of( + "charge_type", MatchPredicateProperty.CHARGE_TYPE, + "trim_material", MatchPredicateProperty.TRIM_MATERIAL, + "context_dimension", MatchPredicateProperty.CONTEXT_DIMENSION, + "custom_model_data", MatchPredicateProperty.CUSTOM_MODEL_DATA + ) + ); + NodeReader CHARGE_TYPE = ofEnum(ChargeType.class); - NodeReader RANGE_DISPATCH_PROPERTY = ofEnum(RangeDispatchPredicateProperty.class); + NodeReader RANGE_DISPATCH_PREDICATE_PROPERTY = ofEnum(RangeDispatchPredicateProperty.class); NodeReader CONSUMABLE_ANIMATION = ofEnum(Consumable.Animation.class); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index acb02013e1e..93b78b8263e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -212,7 +212,7 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo switch (type) { case "condition" -> { - ConditionPredicateProperty conditionProperty = MappingsUtil.readOrThrow(node, "property", NodeReader.CONDITION_PROPERTY, context); + ConditionPredicateProperty conditionProperty = MappingsUtil.readOrThrow(node, "property", NodeReader.CONDITION_PREDICATE_PROPERTY, context); boolean expected = MappingsUtil.readOrDefault(node, "expected", NodeReader.BOOLEAN, true, context); if (!conditionProperty.requiresData) { @@ -228,23 +228,24 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNo } } case "match" -> { - String property = MappingsUtil.readOrThrow(node, "property", NodeReader.NON_EMPTY_STRING, context); + MatchPredicateProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.MATCH_PREDICATE_PROPERTY, context); - switch (property) { - case "charge_type" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CHARGE_TYPE, + if (property == MatchPredicateProperty.CHARGE_TYPE) { + builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CHARGE_TYPE, MappingsUtil.readOrThrow(node, "value", NodeReader.CHARGE_TYPE, context))); - case "trim_material" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.TRIM_MATERIAL, + } else if (property == MatchPredicateProperty.TRIM_MATERIAL || property == MatchPredicateProperty.CONTEXT_DIMENSION) { + builder.predicate(CustomItemPredicate.match((MatchPredicateProperty) property, MappingsUtil.readOrThrow(node, "value", NodeReader.IDENTIFIER, context))); - case "context_dimension" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CONTEXT_DIMENSION, - MappingsUtil.readOrThrow(node, "value", NodeReader.IDENTIFIER, context))); - case "custom_model_data" -> builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CUSTOM_MODEL_DATA, + } else if (property == MatchPredicateProperty.CUSTOM_MODEL_DATA) { + builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataString(MappingsUtil.readOrThrow(node, "value", NodeReader.STRING, context), MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context)))); - default -> throw new InvalidCustomMappingsFileException("reading match predicate", "unknown property " + property, context); + } else { + throw new InvalidCustomMappingsFileException("reading match predicate", "unimplemented reading of match predicate property!", context); } } case "range_dispatch" -> { - RangeDispatchPredicateProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.RANGE_DISPATCH_PROPERTY, context); + RangeDispatchPredicateProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.RANGE_DISPATCH_PREDICATE_PROPERTY, context); double threshold = MappingsUtil.readOrThrow(node, "threshold", NodeReader.DOUBLE, context); double scale = MappingsUtil.readOrDefault(node, "scale", NodeReader.DOUBLE, 1.0, context); From 6705513c8a14641a064da85ed28ac8496e4b26df Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 00:28:11 +0000 Subject: [PATCH 096/118] Fix broken/damaged predicates --- .../geysermc/geyser/translator/item/CustomItemTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index 3e011a20fc2..f417792fdda 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -227,7 +227,7 @@ private static boolean isDamaged(DataComponents components) { } private static boolean isDamageableItem(DataComponents components) { - return components.getOrDefault(DataComponentType.UNBREAKABLE, false) && components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) > 0; + return components.get(DataComponentType.UNBREAKABLE) == null && components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) > 0; } private CustomItemTranslator() { From bd7e4712d6aaed0a0e87d9ce02bd078b6cbae7ea Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 00:38:40 +0000 Subject: [PATCH 097/118] Remove enchantable stuff from chargeable properties as that is decided by enchantable Java component, hand equipped because that's decided by custom item definition --- .../registry/populator/CustomItemRegistryPopulator.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 7862295061e..40ae286b564 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -462,10 +462,6 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb // setting high use_duration prevents the consume animation from playing itemProperties.putInt("use_duration", Integer.MAX_VALUE); - // display item as tool (mainly for crossbow and bow) - itemProperties.putBoolean("hand_equipped", true); - // Make bows, tridents, and crossbows enchantable - itemProperties.putInt("enchantable_value", 1); componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() .putFloat("use_duration", 100F) @@ -474,7 +470,6 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb switch (mapping) { case "minecraft:bow" -> { - itemProperties.putString("enchantable_slot", "bow"); itemProperties.putInt("frame_count", 3); componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() @@ -494,11 +489,9 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb componentBuilder.putInt("minecraft:use_duration", 999); } case "minecraft:trident" -> { - itemProperties.putString("enchantable_slot", "trident"); componentBuilder.putInt("minecraft:use_duration", 999); } case "minecraft:crossbow" -> { - itemProperties.putString("enchantable_slot", "crossbow"); itemProperties.putInt("frame_count", 10); componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() From ea850f4a4965a88b96042706dd42d9bcee55179f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 00:58:31 +0000 Subject: [PATCH 098/118] Clean up custom item registry populator --- .../CustomItemRegistryPopulator.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 40ae286b564..8c1a96b9759 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -76,8 +76,9 @@ public class CustomItemRegistryPopulator { private static final Identifier UNBREAKABLE_COMPONENT = new Identifier("minecraft", "unbreakable"); + // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. - // TODO these don't seem to be applying correctly + // These don't all work correctly on Bedrock - their behaviour is described below private static final Map BEDROCK_ANIMATIONS = Map.of( Consumable.ItemUseAnimation.NONE, 0, // Does nothing in 1st person, eating in 3rd person Consumable.ItemUseAnimation.EAT, 1, // Appears to look correctly @@ -252,6 +253,12 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD boolean canDestroyInCreative = toolProperties == null ? !"sword".equals(vanillaMapping.getToolType()) : toolProperties.canDestroyBlocksInCreative(); computeCreativeDestroyProperties(canDestroyInCreative, itemProperties, componentBuilder); + switch (vanillaMapping.getBedrockIdentifier()) { + case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); + case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, vanillaMapping.getBedrockIdentifier()); + case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> computeThrowableProperties(componentBuilder); + } + // Using API component here because MCPL one is just an ID holder set Repairable repairable = customItemDefinition.components().get(DataComponent.REPAIRABLE); if (repairable != null) { @@ -287,13 +294,6 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeUseCooldownProperties(useCooldown, componentBuilder); } - // TODO not really a fan of this switch statement - switch (vanillaMapping.getBedrockIdentifier()) { - case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); - case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, vanillaMapping.getBedrockIdentifier()); - case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> computeThrowableProperties(componentBuilder); - } - computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); componentBuilder.putCompound("item_properties", itemProperties.build()); @@ -355,7 +355,6 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp .build()) .putInt("max_durability", maxDamage) .build()); - itemProperties.putBoolean("use_duration", true); } } @@ -451,21 +450,23 @@ private static void computeEnchantableProperties(int enchantmentValue, NbtMapBui private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) { // carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here // however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot - // it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome + // it also causes the item to glitch when right-clicked to "equip" so this should only be added here later if these issues can be overcome // all block items registered should be given this component to prevent double placement - componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build()); + componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder() + .putString("block", blockItem) + .putBoolean("canUseBlockAsIcon", false) + .putList("use_on", NbtType.STRING) + .build()); } private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping) { - // TODO check this, it's probably wrong by now - // setting high use_duration prevents the consume animation from playing itemProperties.putInt("use_duration", Integer.MAX_VALUE); componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() - .putFloat("use_duration", 100F) .putFloat("movement_modifier", 0.35F) + .putFloat("use_duration", 100.0F) .build()); switch (mapping) { @@ -478,19 +479,17 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb .putCompound("item", NbtMap.builder() .putString("name", "minecraft:arrow") .build()) - .putBoolean("use_offhand", true) .putBoolean("search_inventory", true) + .putBoolean("use_in_creative", false) + .putBoolean("use_offhand", true) .build() )) - .putFloat("max_draw_duration", 0f) .putBoolean("charge_on_draw", true) + .putFloat("max_draw_duration", 0.0F) .putBoolean("scale_power_by_draw_duration", true) .build()); - componentBuilder.putInt("minecraft:use_duration", 999); - } - case "minecraft:trident" -> { - componentBuilder.putInt("minecraft:use_duration", 999); } + case "minecraft:trident" -> itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(Consumable.ItemUseAnimation.SPEAR)); case "minecraft:crossbow" -> { itemProperties.putInt("frame_count", 10); @@ -501,20 +500,19 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb .putString("name", "minecraft:arrow") .build()) .putBoolean("use_offhand", true) + .putBoolean("use_in_creative", false) .putBoolean("search_inventory", true) .build() )) - .putFloat("max_draw_duration", 1f) .putBoolean("charge_on_draw", true) + .putFloat("max_draw_duration", 1.0F) .putBoolean("scale_power_by_draw_duration", true) .build()); - componentBuilder.putInt("minecraft:use_duration", 999); } } } private static void computeConsumableProperties(Consumable consumable, @Nullable FoodProperties foodProperties, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - // TODO check the animations, it didn't work properly // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); @@ -534,7 +532,7 @@ private static void computeConsumableProperties(Consumable consumable, @Nullable .build()); componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder() - .putFloat("movement_modifier", 0.2F) // TODO is this the right value + .putFloat("movement_modifier", 0.35F) .putFloat("use_duration", consumable.consumeSeconds()) .build()); } @@ -542,21 +540,23 @@ private static void computeConsumableProperties(Consumable consumable, @Nullable private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) { // all items registered that place entities should be given this component to prevent double placement // it is okay that the entity here does not match the actual one since we control what entity actually spawns - componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build()); + componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder() + .putList("dispense_on", NbtType.STRING) + .putString("entity", "minecraft:minecart") + .putList("use_on", NbtType.STRING) + .build()); } private static void computeThrowableProperties(NbtMapBuilder componentBuilder) { - // TODO check this, it's probably wrong by now - // allows item to be thrown when holding down right click (individual presses are required w/o this component) componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build()); + // this must be set to something for the swing animation to play // it is okay that the projectile here does not match the actual one since we control what entity actually spawns componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build()); } private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) { - // TODO the non null check can probably be removed when no longer using MCPL in API Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null"); componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder() .putString("category", cooldown.cooldownGroup().asString()) From 020e78e78208e64599a8fa7020bc8e119624fe57 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 10:41:43 +0000 Subject: [PATCH 099/118] Make new item definition register method throw exception --- .../GeyserDefineCustomItemsEvent.java | 5 +- ...CustomItemDefinitionRegisterException.java | 33 +++++++++++ .../CustomItemRegistryPopulator.java | 58 ++++++++++--------- 3 files changed, 67 insertions(+), 29 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/exception/CustomItemDefinitionRegisterException.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index cacfa8eecde..d3dc962f5a9 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -27,6 +27,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.event.Event; +import org.geysermc.geyser.api.exception.CustomItemDefinitionRegisterException; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; @@ -90,9 +91,9 @@ default Map> getExistingCustomItems() { * * @param identifier the base (java) item * @param customItemDefinition the custom item definition to register - * @return if the item was registered + * @throws CustomItemDefinitionRegisterException when an error occurred while registering the item. */ - boolean register(@NonNull String identifier, @NonNull CustomItemDefinition customItemDefinition); + void register(@NonNull String identifier, @NonNull CustomItemDefinition customItemDefinition) throws CustomItemDefinitionRegisterException; /** * Registers a custom item with no base item. This is used for mods. diff --git a/api/src/main/java/org/geysermc/geyser/api/exception/CustomItemDefinitionRegisterException.java b/api/src/main/java/org/geysermc/geyser/api/exception/CustomItemDefinitionRegisterException.java new file mode 100644 index 00000000000..c553bc1d4ed --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/exception/CustomItemDefinitionRegisterException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.exception; + +public class CustomItemDefinitionRegisterException extends Exception { + + public CustomItemDefinitionRegisterException(String message) { + super(message); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 8c1a96b9759..a52aa30802a 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -36,6 +36,7 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.exception.CustomItemDefinitionRegisterException; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; @@ -71,7 +72,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; public class CustomItemRegistryPopulator { @@ -95,11 +95,11 @@ public static void populate(Map items, Multimap { - Optional error = validate(identifier, item, customItems, items); - if (error.isEmpty()) { + String error = validate(identifier, item, customItems, items); + if (error == null) { customItems.get(identifier).add(item); } else { - GeyserImpl.getInstance().getLogger().error("Not registering custom item definition (bedrock identifier=" + item.bedrockIdentifier() + "): " + error.get()); + GeyserImpl.getInstance().getLogger().error("Not registering custom item definition (bedrock identifier=" + item.bedrockIdentifier() + "): " + error); } }); @@ -108,18 +108,22 @@ public static void populate(Map items, Multimap error = validate(identifier, definition, customItems, items); - if (error.isEmpty()) { + public void register(@NonNull String identifier, @NonNull CustomItemDefinition definition) throws CustomItemDefinitionRegisterException { + String error = validate(identifier, definition, customItems, items); + if (error == null) { customItems.get(identifier).add(definition); - return true; + } else { + throw new CustomItemDefinitionRegisterException("Not registering custom item definition (bedrock identifier=" + definition.bedrockIdentifier() + "): " + error); } - GeyserImpl.getInstance().getLogger().error("Not registering custom item definition (bedrock identifier=" + definition.bedrockIdentifier() + "): " + error.get()); - return false; } @Override @@ -148,43 +152,43 @@ public static GeyserCustomMappingData registerCustomItem(Item javaItem, GeyserMa } /** - * @return an empty optional if there are no errors with the registration, and an optional with an error message if there are + * @return null if there are no errors with the registration, and an error message if there are */ - private static Optional validate(String vanillaIdentifier, CustomItemDefinition item, Multimap registered, Map mappings) { + private static String validate(String vanillaIdentifier, CustomItemDefinition item, Multimap registered, Map mappings) { if (!mappings.containsKey(vanillaIdentifier)) { - return Optional.of("Unknown Java item " + vanillaIdentifier); + return "unknown Java item " + vanillaIdentifier; } Identifier bedrockIdentifier = item.bedrockIdentifier(); if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - return Optional.of("Custom item bedrock identifier namespace can't be minecraft"); + return "custom item bedrock identifier namespace can't be minecraft"; } else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) { - return Optional.of("Custom item definition model can't be in the minecraft namespace without a predicate"); + return "custom item definition model can't be in the minecraft namespace without a predicate"; } for (Map.Entry entry : registered.entries()) { if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) { - return Optional.of("Conflicts with another custom item definition with the same bedrock identifier"); + return "conflicts with another custom item definition with the same bedrock identifier"; } - Optional error = checkPredicate(entry, vanillaIdentifier, item); - if (error.isPresent()) { - return Optional.of("Conflicts with custom item definition (bedrock identifier=" + entry.getValue().bedrockIdentifier() + "): " + error.get()); + String error = checkPredicate(entry, vanillaIdentifier, item); + if (error != null) { + return "conflicts with custom item definition (bedrock identifier=" + entry.getValue().bedrockIdentifier() + "): " + error; } } - return Optional.empty(); + return null; } /** - * @return an error message if there was a conflict, or an empty optional otherwise + * @return an error message if there was a conflict, or null otherwise */ - private static Optional checkPredicate(Map.Entry existing, String vanillaIdentifier, CustomItemDefinition newItem) { + private static String checkPredicate(Map.Entry existing, String vanillaIdentifier, CustomItemDefinition newItem) { // If the definitions are for different Java items or models then it doesn't matter if (!vanillaIdentifier.equals(existing.getKey()) || !newItem.model().equals(existing.getValue().model())) { - return Optional.empty(); + return null; } // If they both don't have predicates they conflict if (existing.getValue().predicates().isEmpty() && newItem.predicates().isEmpty()) { - return Optional.of("Both entries don't have predicates, one must have a predicate"); + return "both entries don't have predicates, one must have a predicate"; } // If their predicates are equal then they also conflict if (existing.getValue().predicates().size() == newItem.predicates().size()) { @@ -195,11 +199,11 @@ private static Optional checkPredicate(Map.Entry Date: Sat, 18 Jan 2025 10:47:32 +0000 Subject: [PATCH 100/118] Charge type javadocs --- .../item/custom/v2/predicate/match/ChargeType.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java index 950ee128a5d..a9a70c47987 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java @@ -25,8 +25,20 @@ package org.geysermc.geyser.api.item.custom.v2.predicate.match; +/** + * Values returned by the {@link MatchPredicateProperty#CHARGE_TYPE} predicate property. + */ public enum ChargeType { + /** + * Returned if there are no projectiles loaded in the crossbow. + */ NONE, + /** + * Returned if there are any projectiles (except fireworks) loaded in the crossbow. + */ ARROW, + /** + * Returned if there are firework rocket projectiles loaded in the crossbow. + */ ROCKET } From fc0103b5bf0fb4ab20e9f7f0c878d89e2e6c380e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 11:00:00 +0000 Subject: [PATCH 101/118] Make air model constant --- .../geysermc/geyser/translator/item/CustomItemTranslator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java index f417792fdda..f22e486edc1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java @@ -63,6 +63,7 @@ * This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator. */ public final class CustomItemTranslator { + private static final Key FALLBACK_MODEL = MinecraftKey.key("air"); @Nullable public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, DataComponents components, ItemMapping mapping) { @@ -75,7 +76,7 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, return null; } - Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); + Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, FALLBACK_MODEL); Collection customItems = allCustomItems.get(itemModel); if (customItems.isEmpty()) { return null; From 2662c815bee5d849f18fa73e72185edf1ea7ff17 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 11:05:39 +0000 Subject: [PATCH 102/118] Rename creative category internal name to bedrock name --- .../geyser/api/util/CreativeCategory.java | 16 ++++++++-------- .../populator/CustomBlockRegistryPopulator.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java b/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java index aa434fcaa06..430b580340c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java +++ b/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java @@ -38,21 +38,21 @@ public enum CreativeCategory { ITEMS("items", 4), NONE("none", 6); - private final String internalName; + private final String bedrockName; private final int id; - CreativeCategory(String internalName, int id) { - this.internalName = internalName; + CreativeCategory(String bedrockName, int id) { + this.bedrockName = bedrockName; this.id = id; } /** - * Gets the internal name of the category. + * Gets the bedrock name (used in behaviour packs) of the category. * * @return the name of the category */ - public @NonNull String internalName() { - return internalName; + public @NonNull String bedrockName() { + return bedrockName; } /** @@ -65,13 +65,13 @@ public int id() { } /** - * Gets the creative category from its internal name. + * Gets the creative category from its bedrock name. * * @return the creative category, or null if not found. */ public static @Nullable CreativeCategory fromName(String name) { for (CreativeCategory category : values()) { - if (category.internalName.equals(name)) { + if (category.bedrockName.equals(name)) { return category; } } 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 a43df3f52cf..a6382a9ab3c 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 @@ -317,7 +317,7 @@ static BlockPropertyData generateBlockPropertyData(CustomBlockData customBlock, // 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", creativeCategory.internalName()) + .putString("category", creativeCategory.bedrockName()) .putString("group", creativeGroup) .putBoolean("is_hidden_in_commands", false) .build()) From 6f678140236183e43e536ed81cd0a56f7a79b5d5 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 11:12:16 +0000 Subject: [PATCH 103/118] Make sure deprecated getExistingCustomItems method still functions --- .../GeyserDefineCustomItemsEvent.java | 12 ++++------ .../GeyserDefineCustomItemsEventImpl.java | 23 +++++++++++++++++++ .../CustomItemRegistryPopulator.java | 11 --------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index d3dc962f5a9..f39c02249a9 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -33,7 +33,6 @@ import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -45,17 +44,14 @@ public interface GeyserDefineCustomItemsEvent extends Event { /** - * Gets a multimap of all the already registered custom items indexed by the item's extended java item's identifier. - * This will always return an empty map since the switch to custom item definitions, use {@link GeyserDefineCustomItemsEvent#getExistingCustomItemDefinitions()}. + * Gets a multimap of all the already registered (using the deprecated method) custom items indexed by the item's extended java item's identifier. * * @deprecated use {@link GeyserDefineCustomItemsEvent#getExistingCustomItemDefinitions()} - * @return a multimap of all the already registered custom items + * @return a multimap of all the already custom items registered using {@link GeyserDefineCustomItemsEvent#register(String, CustomItemData)} */ - @Deprecated(forRemoval = true) + @Deprecated @NonNull - default Map> getExistingCustomItems() { - return Collections.emptyMap(); - } + Map> getExistingCustomItems(); /** * Gets a multimap of all the already registered custom item definitions indexed by the item's extended java item's identifier. diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java index d7492c7612f..8fb0806fa8f 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java @@ -26,10 +26,14 @@ package org.geysermc.geyser.event.type; import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent; +import org.geysermc.geyser.api.exception.CustomItemDefinitionRegisterException; +import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; +import org.geysermc.geyser.api.util.Identifier; import java.util.Collection; import java.util.Collections; @@ -37,6 +41,7 @@ import java.util.Map; public abstract class GeyserDefineCustomItemsEventImpl implements GeyserDefineCustomItemsEvent { + private final Multimap deprecatedCustomItems = MultimapBuilder.hashKeys().arrayListValues().build(); private final Multimap customItems; private final List nonVanillaCustomItems; @@ -45,6 +50,12 @@ public GeyserDefineCustomItemsEventImpl(Multimap c this.nonVanillaCustomItems = nonVanillaCustomItems; } + @Override + @Deprecated + public @NonNull Map> getExistingCustomItems() { + return Collections.unmodifiableMap(deprecatedCustomItems.asMap()); + } + @Override public @NonNull Map> getExistingCustomItemDefinitions() { return Collections.unmodifiableMap(customItems.asMap()); @@ -54,4 +65,16 @@ public GeyserDefineCustomItemsEventImpl(Multimap c public @NonNull List getExistingNonVanillaCustomItems() { return Collections.unmodifiableList(this.nonVanillaCustomItems); } + + @Override + @Deprecated + public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) { + try { + register(identifier, customItemData.toDefinition(new Identifier(identifier)).build()); + deprecatedCustomItems.put(identifier, customItemData); + return true; + } catch (CustomItemDefinitionRegisterException exception) { + return false; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index a52aa30802a..bcf8da156e7 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -105,17 +105,6 @@ public static void populate(Map items, Multimap Date: Sat, 18 Jan 2025 11:18:00 +0000 Subject: [PATCH 104/118] Data component map javadoc --- .../api/item/custom/v2/component/DataComponentMap.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java index 4e1ee9f4b6e..2f9808442df 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponentMap.java @@ -27,9 +27,18 @@ import java.util.Set; +/** + * A map of data components to their values. Mainly used internally when mapping custom items. + */ public interface DataComponentMap { + /** + * @return the value of the given component, or null if it is not in the map. + */ T get(DataComponent type); + /** + * @return all data components in this map. + */ Set> keySet(); } From 99b01ee0f3594fed397a8d8cec6a2937ecd5766e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 11:25:36 +0000 Subject: [PATCH 105/118] Apply suggestions from code review Co-authored-by: chris --- .../api/event/lifecycle/GeyserDefineCustomItemsEvent.java | 2 +- .../geyser/api/item/custom/v2/CustomItemDefinition.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java index f39c02249a9..b6de281217c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java @@ -85,7 +85,7 @@ public interface GeyserDefineCustomItemsEvent extends Event { * Registers a custom item with a base Java item. This is used to register items with custom textures and properties * based on NBT data. * - * @param identifier the base (java) item + * @param identifier of the Java edition base item * @param customItemDefinition the custom item definition to register * @throws CustomItemDefinitionRegisterException when an error occurred while registering the item. */ diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 27b04f8e8cd..6764e4f4c63 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -50,8 +50,7 @@ *
  • Lastly by the amount of predicates, from most to least.
  • * * - *

    This ensures predicates will be checked in a correct order, and that in most cases specifying a priority value isn't necessary, and in the few cases - * where Geyser doesn't properly sort definitions, specifying a priority value will.

    + *

    This ensures predicates will be checked in the correct order. In most cases, specifying a priority value isn't necessary, but it can be added to ensure the intended order.

    */ public interface CustomItemDefinition { From b2776a3798efaee3c265cf6b4c82b7d6b42cafb6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 11:26:25 +0000 Subject: [PATCH 106/118] Forgot one Co-authored-by: chris --- .../geyser/api/item/custom/v2/CustomItemDefinition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 6764e4f4c63..7842ad70548 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -55,7 +55,7 @@ public interface CustomItemDefinition { /** - * The Bedrock identifier for this custom item. This can't be in the {@code minecraft} namespace. + * The Bedrock identifier for this custom item. It cannot be in the {@code minecraft} namespace. */ @NonNull Identifier bedrockIdentifier(); From ec9e9fc83dc3784720f2bd638274da8bba00286b Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 22:31:52 +0000 Subject: [PATCH 107/118] I messed up the merge conflicts, whoops --- gradle/libs.versions.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5e8a3faa46..f685416bf7c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,6 +71,8 @@ fastutil-int-byte-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int- fastutil-int-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-boolean-maps", version.ref = "fastutil" } fastutil-object-int-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-int-maps", version.ref = "fastutil" } fastutil-object-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-object-maps", version.ref = "fastutil" } +fastutil-object-boolean-maps = { group = "com.nukkitx.fastutil", name = "fastutil-object-boolean-maps", version.ref = "fastutil" } +fastutil-reference-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-reference-object-maps", version.ref = "fastutil" } adventure-text-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" } # Remove when we remove our Adventure bump adventure-text-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" } @@ -153,7 +155,7 @@ blossom = { id = "net.kyori.blossom", version.ref = "blossom" } [bundles] jackson = [ "jackson-annotations", "jackson-databind", "jackson-dataformat-yaml" ] -fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps" ] +fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps", "fastutil-object-boolean-maps", "fastutil-reference-object-maps" ] adventure = [ "adventure-text-serializer-gson", "adventure-text-serializer-legacy", "adventure-text-serializer-plain" ] log4j = [ "log4j-api", "log4j-core", "log4j-slf4j2-impl" ] jline = [ "jline-terminal", "jline-terminal-jna", "jline-reader" ] From d22e815398005e793c51f2618637a1f69c0479c4 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 18 Jan 2025 22:37:48 +0000 Subject: [PATCH 108/118] Work on component javadoc --- .../item/custom/v2/CustomItemDefinition.java | 1 + .../item/custom/v2/component/Consumable.java | 30 +++++++++++ .../custom/v2/component/DataComponent.java | 51 ++++++++++++++++--- .../CustomItemRegistryPopulator.java | 21 ++++---- 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java index 7842ad70548..5f71dc38acc 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java @@ -118,6 +118,7 @@ public interface CustomItemDefinition { *
  • {@code minecraft:use_cooldown} ({@link DataComponent#USE_COOLDOWN})
  • *
  • {@code minecraft:enchantable} ({@link DataComponent#ENCHANTABLE})
  • *
  • {@code minecraft:tool} ({@link DataComponent#TOOL})
  • + *
  • {@code minecraft:repairable} ({@link DataComponent#REPAIRABLE})
  • * * *

    Note: some components, for example {@code minecraft:rarity}, {@code minecraft:enchantment_glint_override}, and {@code minecraft:attribute_modifiers} are translated automatically, diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java index 373e7cf37c1..3e4ec86832a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java @@ -35,16 +35,46 @@ public record Consumable(@Positive float consumeSeconds, Animation animation) { } } + /** + * Not all animations work perfectly on bedrock. Bedrock behaviour is noted per animation. + */ public enum Animation { + /** + * Does nothing in 1st person, appears as eating in 3rd person. + */ NONE, + /** + * Appears to look correctly. + */ EAT, + /** + * Appears to look correctly. + */ DRINK, + /** + * Does nothing in 1st person, eating in 3rd person. + */ BLOCK, + /** + * Does nothing in 1st person, eating in 3rd person. + */ BOW, + /** + * Does nothing in 1st person, but looks like spear in 3rd person. + */ SPEAR, + /** + * Does nothing in 1st person, eating in 3rd person. + */ CROSSBOW, + /** + * Does nothing in 1st person, but looks like spyglass in 3rd person. + */ SPYGLASS, TOOT_HORN, + /** + * Brush in 1st and 3rd person. Will look weird when not displayed handheld. + */ BRUSH } } diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index 8ad00b83758..041210e724d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java @@ -25,36 +25,75 @@ package org.geysermc.geyser.api.item.custom.v2.component; +import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.util.Identifier; import java.util.function.Predicate; +/** + * Data components used to indicate item behaviour of custom items. It is expected that any components set on a {@link CustomItemDefinition} are always present on the item server-side. + * + * @see CustomItemDefinition#components() + */ public final class DataComponent { + /** + * Marks the item as consumable. Of this component, only {@code consume_seconds} and {@code animation} properties are translated. Consume effects are done server side, + * and consume sounds and particles aren't possible. + * + *

    Note that due to a bug on Bedrock, not all consume animations appear perfectly. See {@link Consumable.Animation}

    + * + * @see Consumable + */ public static final DataComponent CONSUMABLE = create("consumable"); + /** + * Marks the item as equippable. Of this component, only the {@code slot} property is translated. Other properties are done server-side, are done differently on Bedrock (e.g. {@code asset_id} is done via attachables), + * or are not possible on Bedrock at all (e.g. {@code camera_overlay}). + * + *

    Note that on Bedrock, equippables can't have a stack size above 1.

    + * + * @see Equippable + */ public static final DataComponent EQUIPPABLE = create("equippable"); + /** + * Food properties of the item. All properties properly translate over to Bedrock. + * + * @see FoodProperties + */ public static final DataComponent FOOD = create("food"); /** - * Must be at or above 0. + * Max damage value of the item. Must be at or above 0. Items with a max damage value above 0 can't have a stack size above 1. */ public static final DataComponent MAX_DAMAGE = create("max_damage", i -> i >= 0); /** - * Must be between 1 and 99. + * Max stack size of the item. Must be between 1 and 99. Items with a max stack size value above 1 can't have a max damage value above 0. */ public static final DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda + /** + * Marks the item to have a use cooldown. To properly function, the item must be able to be used: it must be consumable or have some other kind of use logic. + * + * @see UseCooldown + */ public static final DataComponent USE_COOLDOWN = create("use_cooldown"); /** - * Must be at or above 0. + * Marks the item to be enchantable. Must be at or above 0. * - *

    Note that, on Bedrock, this will be mapped to the {@code minecraft:enchantable} component with {@code slot=all}. This should, but does not guarantee, - * allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.

    + *

    This component does not translate over perfectly, due to the way enchantments work on Bedrock. The component will be mapped to the {@code minecraft:enchantable} bedrock component with {@code slot=all}. + * This should, but does not guarantee, allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.

    */ public static final DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0); /** - * At the moment only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option, which will be a feature in Java 1.21.5, but is already in use in Geyser. + * This component is only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option, which will be a feature in Java 1.21.5, but is already in use in Geyser. * *

    Like other components, when not set this will fall back to the default value.

    + * + * @see ToolProperties */ public static final DataComponent TOOL = create("tool"); + /** + * Marks which items can be used to repair the item. + * + * @see Repairable + */ public static final DataComponent REPAIRABLE = create("repairable"); private final Identifier identifier; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index bcf8da156e7..ab1a42b4781 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -78,17 +78,18 @@ public class CustomItemRegistryPopulator { private static final Identifier UNBREAKABLE_COMPONENT = new Identifier("minecraft", "unbreakable"); // In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int. - // These don't all work correctly on Bedrock - their behaviour is described below + // These don't all work correctly on Bedrock - see the Consumable.Animation Javadoc in the API private static final Map BEDROCK_ANIMATIONS = Map.of( - Consumable.ItemUseAnimation.NONE, 0, // Does nothing in 1st person, eating in 3rd person - Consumable.ItemUseAnimation.EAT, 1, // Appears to look correctly - Consumable.ItemUseAnimation.DRINK, 2, // Appears to look correctly - Consumable.ItemUseAnimation.BLOCK, 3, // Does nothing in 1st person, eating in 3rd person - Consumable.ItemUseAnimation.BOW, 4, // Does nothing in 1st person, eating in 3rd person - Consumable.ItemUseAnimation.SPEAR, 6, // Does nothing, but looks like spear in 3rd person. Still has eating animation in 3rd person though, looks weird - Consumable.ItemUseAnimation.CROSSBOW, 9, // Does nothing in 1st person, eating in 3rd person - Consumable.ItemUseAnimation.SPYGLASS, 10, // Does nothing, but looks like spyglass in 3rd person. Same problems as spear. - Consumable.ItemUseAnimation.BRUSH, 12 // Brush in 1st and 3rd person. Same problems as spear. Looks weird when not displayed handheld. + Consumable.ItemUseAnimation.NONE, 0, + Consumable.ItemUseAnimation.EAT, 1, + Consumable.ItemUseAnimation.DRINK, 2, + Consumable.ItemUseAnimation.BLOCK, 3, + Consumable.ItemUseAnimation.BOW, 4, + Consumable.ItemUseAnimation.SPEAR, 6, + Consumable.ItemUseAnimation.CROSSBOW, 9, + Consumable.ItemUseAnimation.SPYGLASS, 10, + Consumable.ItemUseAnimation.TOOT_HORN, 11, + Consumable.ItemUseAnimation.BRUSH, 12 ); public static void populate(Map items, Multimap customItems, List nonVanillaCustomItems) { From 4b7ef99d0e6f1e364c49c09c9db8e08257fed4d6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 12:49:12 +0000 Subject: [PATCH 109/118] Some fixes --- .../api/item/custom/v2/component/Consumable.java | 3 +-- .../populator/CustomItemRegistryPopulator.java | 12 +++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java index 3e4ec86832a..561b2dc7c42 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java @@ -36,7 +36,7 @@ public record Consumable(@Positive float consumeSeconds, Animation animation) { } /** - * Not all animations work perfectly on bedrock. Bedrock behaviour is noted per animation. + * Not all animations work perfectly on bedrock. Bedrock behaviour is noted per animation. The {@code toot_horn} animation doesn't exist on bedrock, and is therefore not listed here. */ public enum Animation { /** @@ -71,7 +71,6 @@ public enum Animation { * Does nothing in 1st person, but looks like spyglass in 3rd person. */ SPYGLASS, - TOOT_HORN, /** * Brush in 1st and 3rd person. Will look weird when not displayed handheld. */ diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index ab1a42b4781..5b619186c8e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -88,7 +88,6 @@ public class CustomItemRegistryPopulator { Consumable.ItemUseAnimation.SPEAR, 6, Consumable.ItemUseAnimation.CROSSBOW, 9, Consumable.ItemUseAnimation.SPYGLASS, 10, - Consumable.ItemUseAnimation.TOOT_HORN, 11, Consumable.ItemUseAnimation.BRUSH, 12 ); @@ -509,11 +508,14 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb private static void computeConsumableProperties(Consumable consumable, @Nullable FoodProperties foodProperties, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { // this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20)); - itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation())); - componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() - .putString("value", consumable.animation().toString().toLowerCase()) - .build()); + Integer animationId = BEDROCK_ANIMATIONS.get(consumable.animation()); + if (animationId != null) { + itemProperties.putInt("use_animation", animationId); + componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder() + .putString("value", consumable.animation().toString().toLowerCase()) + .build()); + } int nutrition = foodProperties == null ? 0 : foodProperties.getNutrition(); float saturationModifier = foodProperties == null ? 0.0F : foodProperties.getSaturationModifier(); From a0b785c5d63fa7b45b249292b850408b639d6f82 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 12:57:08 +0000 Subject: [PATCH 110/118] Whoops --- .../org/geysermc/geyser/item/custom/ComponentConverters.java | 1 - .../geyser/registry/populator/CustomItemRegistryPopulator.java | 1 - 2 files changed, 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java index 912ac1b62f1..5830e07d3f2 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java @@ -78,7 +78,6 @@ public class ComponentConverters { case SPEAR -> Consumable.ItemUseAnimation.SPEAR; case CROSSBOW -> Consumable.ItemUseAnimation.CROSSBOW; case SPYGLASS -> Consumable.ItemUseAnimation.SPYGLASS; - case TOOT_HORN -> Consumable.ItemUseAnimation.TOOT_HORN; case BRUSH -> Consumable.ItemUseAnimation.BRUSH; }; itemMap.put(DataComponentType.CONSUMABLE, new Consumable(value.consumeSeconds(), convertedAnimation, BuiltinSound.ENTITY_GENERIC_EAT, diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 5b619186c8e..c3b76298f75 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -37,7 +37,6 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.exception.CustomItemDefinitionRegisterException; -import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; From 92d32966ccaa41fbd77f9f96dc1b841979ceee17 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 13:37:17 +0000 Subject: [PATCH 111/118] Work on changing mappings reader to Gson --- .../components/DataComponentReader.java | 8 +- .../components/DataComponentReaders.java | 6 +- .../registry/mappings/util/MappingsUtil.java | 68 ++++++--- .../registry/mappings/util/NodeReader.java | 38 ++--- .../mappings/versions/MappingsReader_v2.java | 141 +++++++++--------- 5 files changed, 138 insertions(+), 123 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java index ae689c15057..853b44a32c0 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; @@ -38,9 +38,9 @@ protected DataComponentReader(DataComponent type) { this.type = type; } - protected abstract V readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException; + protected abstract V readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException; - void read(CustomItemDefinition.Builder builder, JsonNode node, String... context) throws InvalidCustomMappingsFileException { - builder.component(type, readDataComponent(node, context)); + void read(CustomItemDefinition.Builder builder, JsonElement element, String... context) throws InvalidCustomMappingsFileException { + builder.component(type, readDataComponent(element, context)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index 90517e107ef..2069056dc25 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; @@ -47,12 +47,12 @@ public class DataComponentReaders { private static final Map> READERS = new HashMap<>(); - public static void readDataComponent(CustomItemDefinition.Builder builder, Key key, @NonNull JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { + public static void readDataComponent(CustomItemDefinition.Builder builder, Key key, @NonNull JsonElement element, String baseContext) throws InvalidCustomMappingsFileException { DataComponentReader reader = READERS.get(key); if (reader == null) { throw new InvalidCustomMappingsFileException("reading data components", "unknown data component " + key, baseContext); } - reader.read(builder, node, "component " + key, baseContext); + reader.read(builder, element, "component " + key, baseContext); } static { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java index 4a60a181e6f..417ac2c8043 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -25,7 +25,9 @@ package org.geysermc.geyser.registry.mappings.util; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import java.util.ArrayList; @@ -33,42 +35,59 @@ import java.util.function.Consumer; public class MappingsUtil { + private static final String OBJECT_ERROR = "element was not an object"; + private static final String REQUIRED_ERROR = "key is required but was not present"; + private static final String PRIMITIVE_ERROR = "key must be a primitive"; - public static T readOrThrow(JsonNode node, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { - JsonNode object = node.get(name); - if (object == null) { - throw new InvalidCustomMappingsFileException(formatTask(name), "key is required but was not present", context); + public static T readOrThrow(JsonElement object, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { + JsonElement element = getJsonElement(object, name, context); + if (element == null) { + throw new InvalidCustomMappingsFileException(formatTask(name), REQUIRED_ERROR, context); + } else if (!element.isJsonPrimitive()) { + throw new InvalidCustomMappingsFileException(formatTask(name), PRIMITIVE_ERROR, context); } - return converter.read(object, formatTask(name), context); + return converter.read((JsonPrimitive) element, formatTask(name), context); } - public static T readOrDefault(JsonNode node, String name, NodeReader converter, T defaultValue, String... context) throws InvalidCustomMappingsFileException { - JsonNode object = node.get(name); - if (object == null) { + public static T readOrDefault(JsonElement object, String name, NodeReader converter, T defaultValue, String... context) throws InvalidCustomMappingsFileException { + JsonElement element = getJsonElement(object, name, context); + if (element == null) { return defaultValue; + } else if (!element.isJsonPrimitive()) { + throw new InvalidCustomMappingsFileException(formatTask(name), PRIMITIVE_ERROR, context); } - return converter.read(object, formatTask(name), context); + return converter.read((JsonPrimitive) element, formatTask(name), context); } - public static List readArrayOrThrow(JsonNode node, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { - JsonNode array = node.get(name); - if (array == null) { - throw new InvalidCustomMappingsFileException(formatTask(name), "key is required but was not present", context); - } else if (!array.isArray()) { + public static List readArrayOrThrow(JsonElement object, String name, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { + JsonElement element = getJsonElement(object, name, context); + if (element == null) { + throw new InvalidCustomMappingsFileException(formatTask(name), REQUIRED_ERROR, context); + } else if (!element.isJsonArray()) { throw new InvalidCustomMappingsFileException(formatTask(name), "key must be an array", context); } + JsonArray array = element.getAsJsonArray(); List objects = new ArrayList<>(); - for (int i = 0; i < node.size(); i++) { - JsonNode object = node.get(i); - objects.add(converter.read(object, "reading object " + i + " in key \"" + name + "\"", context)); + for (int i = 0; i < array.size(); i++) { + JsonElement item = array.get(i); + String task = "reading object " + i + " in key \"" + name + "\""; + + if (!item.isJsonPrimitive()) { + throw new InvalidCustomMappingsFileException(task, PRIMITIVE_ERROR, context); + } + objects.add(converter.read((JsonPrimitive) item, task, context)); } return objects; } - public static void readIfPresent(JsonNode node, String name, Consumer consumer, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { - if (node.has(name)) { - consumer.accept(converter.read(node.get(name), formatTask(name), context)); + public static void readIfPresent(JsonElement node, String name, Consumer consumer, NodeReader converter, String... context) throws InvalidCustomMappingsFileException { + JsonElement element = getJsonElement(node, name, context); + if (element != null) { + if (!element.isJsonPrimitive()) { + throw new InvalidCustomMappingsFileException(formatTask(name), PRIMITIVE_ERROR, context); + } + consumer.accept(converter.read((JsonPrimitive) element, formatTask(name), context)); } } @@ -79,6 +98,13 @@ public static void requireObject(JsonNode node, String task, String... context) } } + private static JsonElement getJsonElement(JsonElement element, String name, String... context) throws InvalidCustomMappingsFileException { + if (!element.isJsonObject()) { + throw new InvalidCustomMappingsFileException(formatTask(name), OBJECT_ERROR, context); + } + return element.getAsJsonObject().get(name); + } + private static String formatTask(String name) { return "reading key \"" + name + "\""; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index a46ca16a8e4..8c9da5bd204 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.util; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonPrimitive; import org.geysermc.geyser.api.item.custom.v2.component.Consumable; import org.geysermc.geyser.api.item.custom.v2.component.Equippable; import org.geysermc.geyser.api.item.custom.v2.predicate.PredicateStrategy; @@ -46,54 +46,44 @@ public interface NodeReader { NodeReader INT = node -> { - if (!node.isTextual() && !node.isIntegralNumber()) { - throw new InvalidCustomMappingsFileException("expected node to be an integer"); + if (node.getAsNumber() instanceof Integer i) { + return i; } - return node.isTextual() ? Integer.parseInt(node.textValue()) : node.intValue(); // Not using asInt because that catches the exception parseInt throws, which we don't want + throw new InvalidCustomMappingsFileException("expected node to be an integer"); }; NodeReader NON_NEGATIVE_INT = INT.validate(i -> i >= 0, "integer must be non-negative"); NodeReader POSITIVE_INT = INT.validate(i -> i > 0, "integer must be positive"); - NodeReader DOUBLE = node -> { - if (!node.isTextual() && !node.isNumber()) { - throw new InvalidCustomMappingsFileException("expected node to be a number"); - } - return node.isTextual() ? Double.parseDouble(node.textValue()) : node.doubleValue(); // Not using asDouble because that catches the exception parseDouble throws, which we don't want - }; + NodeReader DOUBLE = JsonPrimitive::getAsDouble; NodeReader NON_NEGATIVE_DOUBLE = DOUBLE.validate(d -> d >= 0, "number must be non-negative"); NodeReader POSITIVE_DOUBLE = DOUBLE.validate(d -> d > 0, "number must be positive"); NodeReader BOOLEAN = node -> { - if (node.isTextual()) { - String s = node.textValue(); + // Not directly using getAsBoolean here since that doesn't convert integers and doesn't throw an error when the string is not "true" or "false" + if (node.isString()) { + String s = node.getAsString(); if (s.equals("true")) { return true; } else if (s.equals("false")) { return false; } - } else if (node.isIntegralNumber()) { - int i = node.intValue(); + } else if (node.isNumber() && node.getAsNumber() instanceof Integer i) { if (i == 1) { return true; } else if (i == 0) { return false; } } else if (node.isBoolean()) { - return node.booleanValue(); + return node.getAsBoolean(); } throw new InvalidCustomMappingsFileException("expected node to be a boolean"); }; - NodeReader STRING = node -> { - if (!node.isTextual() && !node.isNumber() && !node.isBoolean()) { - throw new InvalidCustomMappingsFileException("expected node to be a string"); - } - return node.asText(); - }; + NodeReader STRING = JsonPrimitive::getAsString; NodeReader NON_EMPTY_STRING = STRING.validate(s -> !s.isEmpty(), "string must not be empty"); @@ -156,11 +146,11 @@ static NodeReader boundedInt(int min, int max) { } /** - * {@link NodeReader#read(JsonNode, String, String...)} is preferably used as that properly formats the error when one is thrown. + * {@link NodeReader#read(JsonPrimitive, String, String...)} is preferably used as that properly formats the error when one is thrown. */ - T read(JsonNode node) throws InvalidCustomMappingsFileException; + T read(JsonPrimitive node) throws InvalidCustomMappingsFileException; - default T read(JsonNode node, String task, String... context) throws InvalidCustomMappingsFileException { + default T read(JsonPrimitive node, String task, String... context) throws InvalidCustomMappingsFileException { try { return read(node); } catch (Exception exception) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 93b78b8263e..ae22411fce9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -26,7 +26,10 @@ package org.geysermc.geyser.registry.mappings.versions; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; @@ -48,7 +51,6 @@ import org.geysermc.geyser.util.MinecraftKey; import java.nio.file.Path; -import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; @@ -60,15 +62,15 @@ public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - JsonNode items = mappingsRoot.get("items"); + public void readItemMappingsV2(Path file, JsonObject mappingsRoot, BiConsumer consumer) { + JsonObject items = mappingsRoot.getAsJsonObject("items"); - if (items != null && items.isObject()) { - items.fields().forEachRemaining(entry -> { - if (entry.getValue().isArray()) { - entry.getValue().forEach(data -> { + if (items != null) { + items.entrySet().forEach(entry -> { + if (entry.getValue() instanceof JsonArray array) { + array.forEach(definition -> { try { - readItemDefinitionEntry(data, entry.getKey(), null, consumer); + readItemDefinitionEntry(definition, entry.getKey(), null, consumer); } catch (InvalidCustomMappingsFileException exception) { GeyserImpl.getInstance().getLogger().error( "Error reading definition for item " + entry.getKey() + " in custom mappings file: " + file.toString(), exception); @@ -89,7 +91,7 @@ public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer definitionConsumer) throws InvalidCustomMappingsFileException { String context = "item definition(s) for Java item " + itemIdentifier; @@ -98,11 +100,13 @@ private void readItemDefinitionEntry(JsonNode data, String itemIdentifier, Ident // Read model of group if it's present, or default to the model of the parent group, if that's present // If the parent group model is not present (or there is no parent group), and this group also doesn't have a model, then it is expected the definitions supply their model themselves Identifier groupModel = MappingsUtil.readOrDefault(data, "model", NodeReader.IDENTIFIER, model, context); - JsonNode definitions = data.get("definitions"); - if (definitions == null || !definitions.isArray()) { + // The method above should have already thrown a properly formatted error if data is not a JSON object + JsonElement definitions = data.getAsJsonObject().get("definitions"); + + if (definitions == null || !definitions.isJsonArray()) { throw new InvalidCustomMappingsFileException("reading item definitions in group", "group has no definitions key, or it wasn't an array", context); } else { - for (JsonNode definition : definitions) { + for (JsonElement definition : definitions.getAsJsonArray()) { // Recursively read all the entries in the group - they can be more groups or definitions readItemDefinitionEntry(definition, itemIdentifier, groupModel, definitionConsumer); } @@ -116,16 +120,12 @@ private void readItemDefinitionEntry(JsonNode data, String itemIdentifier, Ident } @Override - public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonNode node) throws InvalidCustomMappingsFileException { - if (node == null || !node.isObject()) { - throw new InvalidCustomMappingsFileException("Invalid item definition entry"); - } - - Identifier bedrockIdentifier = MappingsUtil.readOrThrow(node, "bedrock_identifier", NodeReader.IDENTIFIER, "item definition"); + public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonElement element) throws InvalidCustomMappingsFileException { + Identifier bedrockIdentifier = MappingsUtil.readOrThrow(element, "bedrock_identifier", NodeReader.IDENTIFIER, "item definition"); // We now know the Bedrock identifier, make a base context so that the error can be easily located in the JSON file String context = "item definition (bedrock identifier=" + bedrockIdentifier + ")"; - Identifier model = MappingsUtil.readOrDefault(node, "model", NodeReader.IDENTIFIER, parentModel, context); + Identifier model = MappingsUtil.readOrDefault(element, "model", NodeReader.IDENTIFIER, parentModel, context); if (model == null) { throw new InvalidCustomMappingsFileException("reading item model", "no model present", context); @@ -136,49 +136,52 @@ public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonNod } CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, model); - MappingsUtil.readIfPresent(node, "display_name", builder::displayName, NodeReader.NON_EMPTY_STRING, context); - MappingsUtil.readIfPresent(node, "priority", builder::priority, NodeReader.INT, context); + MappingsUtil.readIfPresent(element, "display_name", builder::displayName, NodeReader.NON_EMPTY_STRING, context); + MappingsUtil.readIfPresent(element, "priority", builder::priority, NodeReader.INT, context); - readPredicates(builder, node.get("predicate"), context); - MappingsUtil.readIfPresent(node, "predicate_strategy", builder::predicateStrategy, NodeReader.PREDICATE_STRATEGY, context); + // Mappings util methods used above already threw a properly formatted error if the element is not a JSON object + readPredicates(builder, element.getAsJsonObject().get("predicate"), context); + MappingsUtil.readIfPresent(element, "predicate_strategy", builder::predicateStrategy, NodeReader.PREDICATE_STRATEGY, context); - builder.bedrockOptions(readBedrockOptions(node.get("bedrock_options"), context)); + builder.bedrockOptions(readBedrockOptions(element.getAsJsonObject().get("bedrock_options"), context)); - JsonNode componentsNode = node.get("components"); - if (componentsNode != null) { - if (componentsNode.isObject()) { - for (Iterator> iterator = componentsNode.fields(); iterator.hasNext();) { - Map.Entry entry = iterator.next(); + JsonElement componentsElement = element.getAsJsonObject().get("components"); + if (componentsElement != null) { + if (componentsElement instanceof JsonObject components) { + for (Map.Entry entry : components.entrySet()) { DataComponentReaders.readDataComponent(builder, MinecraftKey.key(entry.getKey()), entry.getValue(), context); } } else { - throw new InvalidCustomMappingsFileException("reading components", "expected components key to be an object", context); + throw new InvalidCustomMappingsFileException("reading components", "components key must be an object", context); } } return builder.build(); } - private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { + private CustomItemBedrockOptions.Builder readBedrockOptions(JsonElement element, String baseContext) throws InvalidCustomMappingsFileException { CustomItemBedrockOptions.Builder builder = CustomItemBedrockOptions.builder(); - if (node == null || !node.isObject()) { + if (element == null) { return builder; } String[] context = {"bedrock options", baseContext}; - MappingsUtil.readIfPresent(node, "icon", builder::icon, NodeReader.NON_EMPTY_STRING, context); - MappingsUtil.readIfPresent(node, "allow_offhand", builder::allowOffhand, NodeReader.BOOLEAN, context); - MappingsUtil.readIfPresent(node, "display_handheld", builder::displayHandheld, NodeReader.BOOLEAN, context); - MappingsUtil.readIfPresent(node, "protection_value", builder::protectionValue, NodeReader.NON_NEGATIVE_INT, context); - MappingsUtil.readIfPresent(node, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); - MappingsUtil.readIfPresent(node, "creative_group", builder::creativeGroup, NodeReader.NON_EMPTY_STRING, context); - MappingsUtil.readIfPresent(node, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); - MappingsUtil.readIfPresent(node, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode, context); - - if (node.get("tags") instanceof ArrayNode tags) { + MappingsUtil.readIfPresent(element, "icon", builder::icon, NodeReader.NON_EMPTY_STRING, context); + MappingsUtil.readIfPresent(element, "allow_offhand", builder::allowOffhand, NodeReader.BOOLEAN, context); + MappingsUtil.readIfPresent(element, "display_handheld", builder::displayHandheld, NodeReader.BOOLEAN, context); + MappingsUtil.readIfPresent(element, "protection_value", builder::protectionValue, NodeReader.NON_NEGATIVE_INT, context); + MappingsUtil.readIfPresent(element, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); + MappingsUtil.readIfPresent(element, "creative_group", builder::creativeGroup, NodeReader.NON_EMPTY_STRING, context); + MappingsUtil.readIfPresent(element, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); + MappingsUtil.readIfPresent(element, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode, context); + + if (element.getAsJsonObject().get("tags") instanceof JsonArray tags) { Set tagsSet = new ObjectOpenHashSet<>(); - for (JsonNode tag : tags) { - tagsSet.add(NodeReader.NON_EMPTY_STRING.read(tag, "reading tag", context)); + for (JsonElement tag : tags) { + if (!tag.isJsonPrimitive()) { + throw new InvalidCustomMappingsFileException("reading tag", "tag must be a string", context); + } + tagsSet.add(NodeReader.NON_EMPTY_STRING.read((JsonPrimitive) tag, "reading tag", context)); } builder.tags(tagsSet); } @@ -186,15 +189,15 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonNode node, Strin return builder; } - private void readPredicates(CustomItemDefinition.Builder builder, JsonNode node, String context) throws InvalidCustomMappingsFileException { - if (node == null) { + private void readPredicates(CustomItemDefinition.Builder builder, JsonElement element, String context) throws InvalidCustomMappingsFileException { + if (element == null) { return; } - if (node.isObject()) { - readPredicate(builder, node, context); - } else if (node.isArray()) { - for (JsonNode predicate : node) { + if (element.isJsonObject()) { + readPredicate(builder, element, context); + } else if (element.isJsonArray()) { + for (JsonElement predicate : element.getAsJsonArray()) { readPredicate(builder, predicate, context); } } else { @@ -202,55 +205,51 @@ private void readPredicates(CustomItemDefinition.Builder builder, JsonNode node, } } - private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonNode node, String baseContext) throws InvalidCustomMappingsFileException { - if (!node.isObject()) { - throw new InvalidCustomMappingsFileException("reading predicate", "expected predicate to be an object", baseContext); - } - - String type = MappingsUtil.readOrThrow(node, "type", NodeReader.NON_EMPTY_STRING, "predicate", baseContext); + private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonElement element, String baseContext) throws InvalidCustomMappingsFileException { + String type = MappingsUtil.readOrThrow(element, "type", NodeReader.NON_EMPTY_STRING, "predicate", baseContext); String[] context = {type + " predicate", baseContext}; switch (type) { case "condition" -> { - ConditionPredicateProperty conditionProperty = MappingsUtil.readOrThrow(node, "property", NodeReader.CONDITION_PREDICATE_PROPERTY, context); - boolean expected = MappingsUtil.readOrDefault(node, "expected", NodeReader.BOOLEAN, true, context); + ConditionPredicateProperty conditionProperty = MappingsUtil.readOrThrow(element, "property", NodeReader.CONDITION_PREDICATE_PROPERTY, context); + boolean expected = MappingsUtil.readOrDefault(element, "expected", NodeReader.BOOLEAN, true, context); if (!conditionProperty.requiresData) { builder.predicate(CustomItemPredicate.condition((ConditionPredicateProperty) conditionProperty, expected)); } else if (conditionProperty == ConditionPredicateProperty.CUSTOM_MODEL_DATA) { - int index = MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context); + int index = MappingsUtil.readOrDefault(element, "index", NodeReader.NON_NEGATIVE_INT, 0, context); builder.predicate(CustomItemPredicate.condition(ConditionPredicateProperty.CUSTOM_MODEL_DATA, expected, index)); } else if (conditionProperty == ConditionPredicateProperty.HAS_COMPONENT) { - Identifier component = MappingsUtil.readOrThrow(node, "component", NodeReader.IDENTIFIER, context); + Identifier component = MappingsUtil.readOrThrow(element, "component", NodeReader.IDENTIFIER, context); builder.predicate(CustomItemPredicate.condition(ConditionPredicateProperty.HAS_COMPONENT, expected, component)); } else { throw new InvalidCustomMappingsFileException("reading condition predicate", "unimplemented reading of condition predicate property!", context); } } case "match" -> { - MatchPredicateProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.MATCH_PREDICATE_PROPERTY, context); + MatchPredicateProperty property = MappingsUtil.readOrThrow(element, "property", NodeReader.MATCH_PREDICATE_PROPERTY, context); if (property == MatchPredicateProperty.CHARGE_TYPE) { builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CHARGE_TYPE, - MappingsUtil.readOrThrow(node, "value", NodeReader.CHARGE_TYPE, context))); + MappingsUtil.readOrThrow(element, "value", NodeReader.CHARGE_TYPE, context))); } else if (property == MatchPredicateProperty.TRIM_MATERIAL || property == MatchPredicateProperty.CONTEXT_DIMENSION) { builder.predicate(CustomItemPredicate.match((MatchPredicateProperty) property, - MappingsUtil.readOrThrow(node, "value", NodeReader.IDENTIFIER, context))); + MappingsUtil.readOrThrow(element, "value", NodeReader.IDENTIFIER, context))); } else if (property == MatchPredicateProperty.CUSTOM_MODEL_DATA) { builder.predicate(CustomItemPredicate.match(MatchPredicateProperty.CUSTOM_MODEL_DATA, - new CustomModelDataString(MappingsUtil.readOrThrow(node, "value", NodeReader.STRING, context), - MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context)))); + new CustomModelDataString(MappingsUtil.readOrThrow(element, "value", NodeReader.STRING, context), + MappingsUtil.readOrDefault(element, "index", NodeReader.NON_NEGATIVE_INT, 0, context)))); } else { throw new InvalidCustomMappingsFileException("reading match predicate", "unimplemented reading of match predicate property!", context); } } case "range_dispatch" -> { - RangeDispatchPredicateProperty property = MappingsUtil.readOrThrow(node, "property", NodeReader.RANGE_DISPATCH_PREDICATE_PROPERTY, context); + RangeDispatchPredicateProperty property = MappingsUtil.readOrThrow(element, "property", NodeReader.RANGE_DISPATCH_PREDICATE_PROPERTY, context); - double threshold = MappingsUtil.readOrThrow(node, "threshold", NodeReader.DOUBLE, context); - double scale = MappingsUtil.readOrDefault(node, "scale", NodeReader.DOUBLE, 1.0, context); - boolean normalizeIfPossible = MappingsUtil.readOrDefault(node, "normalize", NodeReader.BOOLEAN, false, context); - int index = MappingsUtil.readOrDefault(node, "index", NodeReader.NON_NEGATIVE_INT, 0, context); + double threshold = MappingsUtil.readOrThrow(element, "threshold", NodeReader.DOUBLE, context); + double scale = MappingsUtil.readOrDefault(element, "scale", NodeReader.DOUBLE, 1.0, context); + boolean normalizeIfPossible = MappingsUtil.readOrDefault(element, "normalize", NodeReader.BOOLEAN, false, context); + int index = MappingsUtil.readOrDefault(element, "index", NodeReader.NON_NEGATIVE_INT, 0, context); builder.predicate(CustomItemPredicate.rangeDispatch(property, threshold, scale, normalizeIfPossible, index)); } From af371b1b2628122b70b5171cbe8d6326c3395052 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 14:06:20 +0000 Subject: [PATCH 112/118] Update data component readers to use Gson --- .../components/readers/ConsumableReader.java | 12 +++++------- .../components/readers/EnchantableReader.java | 15 ++++++++------- .../components/readers/EquippableReader.java | 7 +++---- .../components/readers/FoodPropertiesReader.java | 12 +++++------- .../components/readers/IntComponentReader.java | 10 +++++++--- .../components/readers/RepairableReader.java | 6 ++---- .../components/readers/ToolPropertiesReader.java | 8 +++----- .../components/readers/UseCooldownReader.java | 8 ++++---- 8 files changed, 37 insertions(+), 41 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java index 809b96f2b43..077094d7e23 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.Consumable; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; @@ -41,12 +41,10 @@ public ConsumableReader() { } @Override - protected Consumable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - MappingsUtil.requireObject(node, "reading component", context); + protected Consumable readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { + float consumeSeconds = MappingsUtil.readOrDefault(element, "consume_seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), 1.6F, context); + Consumable.Animation animation = MappingsUtil.readOrDefault(element, "animation", NodeReader.CONSUMABLE_ANIMATION, Consumable.Animation.EAT, context); - float consumeSeconds = MappingsUtil.readOrDefault(node, "consume_seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), 1.6F, context); - Consumable.Animation animation = MappingsUtil.readOrDefault(node, "animation", NodeReader.CONSUMABLE_ANIMATION, Consumable.Animation.EAT, context); - - return new Consumable(consumeSeconds, animation); // TODO are sound and particles supported on bedrock? + return new Consumable(consumeSeconds, animation); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java index 952dd651ef3..ed6b05108fd 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java @@ -25,7 +25,8 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; @@ -65,12 +66,12 @@ public EnchantableReader() { } @Override - protected Integer readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { + protected Integer readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { try { - return NodeReader.NON_NEGATIVE_INT.read(node, "reading component", context); - } catch (InvalidCustomMappingsFileException exception) { - MappingsUtil.requireObject(node, "reading component", context); - return MappingsUtil.readOrThrow(node, "value", NodeReader.NON_NEGATIVE_INT); - } + if (element instanceof JsonPrimitive primitive) { + return NodeReader.NON_NEGATIVE_INT.read(primitive, "reading component", context); + } + } catch (InvalidCustomMappingsFileException ignored) {} + return MappingsUtil.readOrThrow(element, "value", NodeReader.NON_NEGATIVE_INT); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java index 32f104fc22d..cfdf2cac029 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.Equippable; @@ -41,9 +41,8 @@ public EquippableReader() { } @Override - protected Equippable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - MappingsUtil.requireObject(node, "reading component", context); - Equippable.EquipmentSlot slot = MappingsUtil.readOrThrow(node, "slot", NodeReader.EQUIPMENT_SLOT, context); + protected Equippable readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { + Equippable.EquipmentSlot slot = MappingsUtil.readOrThrow(element, "slot", NodeReader.EQUIPMENT_SLOT, context); return new Equippable(slot); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java index 85ee67f1a9d..e07c41a43ec 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.FoodProperties; @@ -41,12 +41,10 @@ public FoodPropertiesReader() { } @Override - protected FoodProperties readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - MappingsUtil.requireObject(node, "reading component", context); - - int nutrition = MappingsUtil.readOrDefault(node, "nutrition", NodeReader.NON_NEGATIVE_INT, 0, context); - float saturation = MappingsUtil.readOrDefault(node, "saturation", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); - boolean canAlwaysEat = MappingsUtil.readOrDefault(node, "can_always_eat", NodeReader.BOOLEAN, false, context); + protected FoodProperties readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { + int nutrition = MappingsUtil.readOrDefault(element, "nutrition", NodeReader.NON_NEGATIVE_INT, 0, context); + float saturation = MappingsUtil.readOrDefault(element, "saturation", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); + boolean canAlwaysEat = MappingsUtil.readOrDefault(element, "can_always_eat", NodeReader.BOOLEAN, false, context); return new FoodProperties(nutrition, saturation, canAlwaysEat); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java index 64d5ea2710c..09be7e79129 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/IntComponentReader.java @@ -25,7 +25,8 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; @@ -47,7 +48,10 @@ public IntComponentReader(DataComponent type, int minimum) { } @Override - protected Integer readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - return NodeReader.boundedInt(minimum, maximum).read(node, "reading component", context); + protected Integer readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { + if (!element.isJsonPrimitive()) { + throw new InvalidCustomMappingsFileException("reading component", "value must be a primitive", context); + } + return NodeReader.boundedInt(minimum, maximum).read((JsonPrimitive) element, "reading component", context); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java index 77bf9612b3a..79ee41d64d0 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.Repairable; @@ -44,9 +44,7 @@ public RepairableReader() { } @Override - protected Repairable readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - MappingsUtil.requireObject(node, "reading component", context); - + protected Repairable readDataComponent(@NonNull JsonElement node, String... context) throws InvalidCustomMappingsFileException { try { Identifier item = MappingsUtil.readOrThrow(node, "items", NodeReader.IDENTIFIER, context); return new Repairable(item); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java index 9ce80241892..3c8a08f8707 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; @@ -41,9 +41,7 @@ public ToolPropertiesReader() { } @Override - protected ToolProperties readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - MappingsUtil.requireObject(node, "reading component", context); - - return new ToolProperties(MappingsUtil.readOrDefault(node, "can_destroy_blocks_in_creative", NodeReader.BOOLEAN, true, context)); + protected ToolProperties readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { + return new ToolProperties(MappingsUtil.readOrDefault(element, "can_destroy_blocks_in_creative", NodeReader.BOOLEAN, true, context)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java index 63b06c9c203..85197b4ffa2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java @@ -25,7 +25,7 @@ package org.geysermc.geyser.registry.mappings.components.readers; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.item.custom.v2.component.UseCooldown; @@ -42,9 +42,9 @@ public UseCooldownReader() { } @Override - protected UseCooldown readDataComponent(@NonNull JsonNode node, String... context) throws InvalidCustomMappingsFileException { - float seconds = MappingsUtil.readOrThrow(node, "seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), context); - Identifier cooldownGroup = MappingsUtil.readOrThrow(node, "cooldown_group", NodeReader.IDENTIFIER, context); + protected UseCooldown readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { + float seconds = MappingsUtil.readOrThrow(element, "seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), context); + Identifier cooldownGroup = MappingsUtil.readOrThrow(element, "cooldown_group", NodeReader.IDENTIFIER, context); return new UseCooldown(seconds, cooldownGroup); } From 2c9596d0c0606c5d41aa8523620828ba6dc54f48 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 14:21:23 +0000 Subject: [PATCH 113/118] Mappings reader gson part 1 --- .../mappings/versions/MappingsReader.java | 50 +++++++++---------- .../mappings/versions/MappingsReader_v2.java | 13 +++-- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java index fe1432eb73b..3e03cc6e3a6 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader.java @@ -25,7 +25,8 @@ package org.geysermc.geyser.registry.mappings.versions; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; @@ -37,51 +38,48 @@ import java.util.function.BiConsumer; public abstract class MappingsReader { - public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); - public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer); + public abstract void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer); + public abstract void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer); - public abstract CustomItemDefinition readItemMappingEntry(Identifier identifier, JsonNode node) throws InvalidCustomMappingsFileException; - public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException; + public abstract CustomItemDefinition readItemMappingEntry(Identifier identifier, JsonElement node) throws InvalidCustomMappingsFileException; + public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonElement node) throws InvalidCustomMappingsFileException; - protected static @Nullable CustomRenderOffsets renderOffsetsFromJsonNode(JsonNode node) { - if (node == null || !node.isObject()) { + protected @Nullable CustomRenderOffsets fromJsonObject(JsonObject node) { + if (node == null) { return null; } return new CustomRenderOffsets( - getHandOffsets(node, "main_hand"), - getHandOffsets(node, "off_hand") + getHandOffsets(node, "main_hand"), + getHandOffsets(node, "off_hand") ); } - protected static CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonNode node, String hand) { - JsonNode tmpNode = node.get(hand); - if (tmpNode == null || !tmpNode.isObject()) { + protected CustomRenderOffsets.@Nullable Hand getHandOffsets(JsonObject node, String hand) { + if (!(node.get(hand) instanceof JsonObject tmpNode)) { return null; } return new CustomRenderOffsets.Hand( - getPerspectiveOffsets(tmpNode, "first_person"), - getPerspectiveOffsets(tmpNode, "third_person") + getPerspectiveOffsets(tmpNode, "first_person"), + getPerspectiveOffsets(tmpNode, "third_person") ); } - protected static CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonNode node, String perspective) { - JsonNode tmpNode = node.get(perspective); - if (tmpNode == null || !tmpNode.isObject()) { + protected CustomRenderOffsets.@Nullable Offset getPerspectiveOffsets(JsonObject node, String perspective) { + if (!(node.get(perspective) instanceof JsonObject tmpNode)) { return null; } return new CustomRenderOffsets.Offset( - getOffsetXYZ(tmpNode, "position"), - getOffsetXYZ(tmpNode, "rotation"), - getOffsetXYZ(tmpNode, "scale") + getOffsetXYZ(tmpNode, "position"), + getOffsetXYZ(tmpNode, "rotation"), + getOffsetXYZ(tmpNode, "scale") ); } - protected static CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonNode node, String offsetType) { - JsonNode tmpNode = node.get(offsetType); - if (tmpNode == null || !tmpNode.isObject()) { + protected CustomRenderOffsets.@Nullable OffsetXYZ getOffsetXYZ(JsonObject node, String offsetType) { + if (!(node.get(offsetType) instanceof JsonObject tmpNode)) { return null; } @@ -90,9 +88,9 @@ public abstract class MappingsReader { } return new CustomRenderOffsets.OffsetXYZ( - tmpNode.get("x").floatValue(), - tmpNode.get("y").floatValue(), - tmpNode.get("z").floatValue() + tmpNode.get("x").getAsFloat(), + tmpNode.get("y").getAsFloat(), + tmpNode.get("z").getAsFloat() ); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index ae22411fce9..786f98a4f97 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.registry.mappings.versions; -import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -58,7 +57,7 @@ public class MappingsReader_v2 extends MappingsReader { @Override - public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer) { readItemMappingsV2(file, mappingsRoot, consumer); } @@ -84,9 +83,9 @@ public void readItemMappingsV2(Path file, JsonObject mappingsRoot, BiConsumer consumer) { - JsonNode blocksNode = mappingsRoot.get("blocks"); - if (blocksNode != null) { + public void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer) { + JsonElement blocks = mappingsRoot.get("blocks"); + if (blocks != null) { throw new UnsupportedOperationException("Unimplemented; use the v1 format of block mappings"); } } @@ -173,7 +172,7 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonElement element, MappingsUtil.readIfPresent(element, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); MappingsUtil.readIfPresent(element, "creative_group", builder::creativeGroup, NodeReader.NON_EMPTY_STRING, context); MappingsUtil.readIfPresent(element, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); - MappingsUtil.readIfPresent(element, "render_offsets", builder::renderOffsets, MappingsReader::renderOffsetsFromJsonNode, context); + MappingsUtil.readIfPresent(element, "render_offsets", builder::renderOffsets, MappingsReader::fromJsonObject, context); if (element.getAsJsonObject().get("tags") instanceof JsonArray tags) { Set tagsSet = new ObjectOpenHashSet<>(); @@ -258,7 +257,7 @@ private void readPredicate(CustomItemDefinition.Builder builder, @NonNull JsonEl } @Override - public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { + public CustomBlockMapping readBlockMappingEntry(String identifier, JsonElement node) throws InvalidCustomMappingsFileException { throw new InvalidCustomMappingsFileException("Unimplemented; use the v1 format of block mappings"); } } From 4095f895f9675162d64da4497de7ddbde3cfbfef Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 14:24:52 +0000 Subject: [PATCH 114/118] Mappings reader gson part 2 --- .../mappings/versions/MappingsReader_v1.java | 392 +++++++++--------- 1 file changed, 197 insertions(+), 195 deletions(-) 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 3f1615510e8..dc6962a1c40 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 @@ -25,8 +25,10 @@ package org.geysermc.geyser.registry.mappings.versions; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,9 +36,14 @@ 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.*; +import org.geysermc.geyser.api.block.custom.component.BoxComponent; +import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; +import org.geysermc.geyser.api.block.custom.component.GeometryComponent; +import org.geysermc.geyser.api.block.custom.component.MaterialInstance; +import org.geysermc.geyser.api.block.custom.component.PlacementConditions; 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.block.custom.component.TransformationComponent; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; @@ -59,7 +66,13 @@ import org.geysermc.geyser.util.MinecraftKey; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; @@ -69,34 +82,33 @@ * A class responsible for reading custom item and block mappings from a JSON file */ public class MappingsReader_v1 extends MappingsReader { - @Override - public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readItemMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer) { this.readItemMappingsV1(file, mappingsRoot, consumer); } /** * Read item block from a JSON node - * + * * @param file The path to the file - * @param mappingsRoot The {@link JsonNode} containing the mappings + * @param mappingsRoot The {@link JsonObject} containing the mappings * @param consumer The consumer to accept the mappings - * @see #readBlockMappingsV1(Path, JsonNode, BiConsumer) + * @see #readBlockMappingsV1(Path, JsonObject, BiConsumer) */ @Override - public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer consumer) { + public void readBlockMappings(Path file, JsonObject mappingsRoot, BiConsumer consumer) { this.readBlockMappingsV1(file, mappingsRoot, consumer); } - public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - JsonNode itemsNode = mappingsRoot.get("items"); + public void readItemMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer consumer) { + JsonObject itemsNode = mappingsRoot.getAsJsonObject("items"); - if (itemsNode != null && itemsNode.isObject()) { - itemsNode.fields().forEachRemaining(entry -> { - if (entry.getValue().isArray()) { - entry.getValue().forEach(data -> { + if (itemsNode != null) { + itemsNode.entrySet().forEach(entry -> { + if (entry.getValue() instanceof JsonArray array) { + array.forEach(data -> { try { - CustomItemDefinition customItemData = this.readItemMappingEntry(new Identifier(entry.getKey()), data); + CustomItemDefinition customItemData = this.readItemMappingEntry(new Identifier(entry.getKey()), (JsonObject) data); consumer.accept(entry.getKey(), customItemData); } catch (InvalidCustomMappingsFileException e) { GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e); @@ -109,21 +121,19 @@ public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer consumer) { - JsonNode blocksNode = mappingsRoot.get("blocks"); - - if (blocksNode != null && blocksNode.isObject()) { - blocksNode.fields().forEachRemaining(entry -> { - if (entry.getValue().isObject()) { + public void readBlockMappingsV1(Path file, JsonObject mappingsRoot, BiConsumer consumer) { + if (mappingsRoot.get("blocks") instanceof JsonObject blocksNode) { + blocksNode.entrySet().forEach(entry -> { + if (entry.getValue() instanceof JsonObject jsonObject) { try { String identifier = MinecraftKey.key(entry.getKey()).asString(); - CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue()); + CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, jsonObject); consumer.accept(identifier, customBlockMapping); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error("Error in registering blocks for custom mapping file: " + file.toString()); @@ -134,85 +144,86 @@ public void readBlockMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer tagsSet = new ObjectOpenHashSet<>(); - tags.forEach(tag -> tagsSet.add(tag.asText())); + tags.forEach(tag -> tagsSet.add(tag.getAsString())); customItemData.tags(tagsSet); } @@ -221,28 +232,29 @@ public CustomItemDefinition readItemMappingEntry(Identifier identifier, JsonNode /** * 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 + * @param element The {@link JsonObject} containing the block mapping entry * @return The {@link CustomBlockMapping} record to be read by {@link org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator} * @throws InvalidCustomMappingsFileException If the JSON node is invalid */ @Override - public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException { - if (node == null || !node.isObject()) { - throw new InvalidCustomMappingsFileException("Invalid block mappings entry:" + node); + public CustomBlockMapping readBlockMappingEntry(String identifier, JsonElement element) throws InvalidCustomMappingsFileException { + if (element == null || !element.isJsonObject()) { + throw new InvalidCustomMappingsFileException("Invalid block mappings entry:" + element); } + JsonObject object = element.getAsJsonObject(); - String name = node.get("name").asText(); + String name = object.get("name").getAsString(); if (name == null || name.isEmpty()) { throw new InvalidCustomMappingsFileException("A block entry has no name"); } - boolean includedInCreativeInventory = node.has("included_in_creative_inventory") && node.get("included_in_creative_inventory").asBoolean(); + boolean includedInCreativeInventory = object.has("included_in_creative_inventory") && object.get("included_in_creative_inventory").getAsBoolean(); CreativeCategory creativeCategory = CreativeCategory.NONE; - if (node.has("creative_category")) { - String categoryName = node.get("creative_category").asText(); + if (object.has("creative_category")) { + String categoryName = object.get("creative_category").getAsString(); try { creativeCategory = CreativeCategory.valueOf(categoryName.toUpperCase()); } catch (IllegalArgumentException e) { @@ -251,37 +263,34 @@ public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node } String creativeGroup = ""; - if (node.has("creative_group")) { - creativeGroup = node.get("creative_group").asText(); + if (object.has("creative_group")) { + creativeGroup = object.get("creative_group").getAsString(); } // 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(); + boolean onlyOverrideStates = object.has("only_override_states") && object.get("only_override_states").getAsBoolean(); // Create the data for the overall block CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder() - .name(name) - .includedInCreativeInventory(includedInCreativeInventory) - .creativeCategory(creativeCategory) - .creativeGroup(creativeGroup); + .name(name) + .includedInCreativeInventory(includedInCreativeInventory) + .creativeCategory(creativeCategory) + .creativeGroup(creativeGroup); if (BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().containsKey(identifier)) { // There is only one Java block state to override - CustomBlockComponentsMapping componentsMapping = createCustomBlockComponentsMapping(node, identifier, name); + CustomBlockComponentsMapping componentsMapping = createCustomBlockComponentsMapping(object, identifier, name); CustomBlockData blockData = customBlockDataBuilder - .components(componentsMapping.components()) - .build(); + .components(componentsMapping.components()) + .build(); return new CustomBlockMapping(blockData, Map.of(identifier, new CustomBlockStateMapping(blockData.defaultBlockState(), componentsMapping.extendedCollisionBox())), identifier, !onlyOverrideStates); } Map componentsMap = new LinkedHashMap<>(); - JsonNode stateOverrides = node.get("state_overrides"); - if (stateOverrides != null && stateOverrides.isObject()) { + if (object.get("state_overrides") instanceof JsonObject stateOverrides) { // Load components for specific Java block states - Iterator> fields = stateOverrides.fields(); - while (fields.hasNext()) { - Map.Entry overrideEntry = fields.next(); + for (Map.Entry overrideEntry : stateOverrides.entrySet()) { String state = identifier + "[" + overrideEntry.getKey() + "]"; if (!BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().containsKey(state)) { throw new InvalidCustomMappingsFileException("Unknown Java block state: " + state + " for state_overrides."); @@ -296,10 +305,10 @@ public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node if (!onlyOverrideStates) { // Create components for any remaining Java block states BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().keySet() - .stream() - .filter(s -> s.startsWith(identifier + "[")) - .filter(Predicate.not(componentsMap::containsKey)) - .forEach(state -> componentsMap.put(state, createCustomBlockComponentsMapping(null, state, name))); + .stream() + .filter(s -> s.startsWith(identifier + "[")) + .filter(Predicate.not(componentsMap::containsKey)) + .forEach(state -> componentsMap.put(state, createCustomBlockComponentsMapping(null, state, name))); } if (componentsMap.isEmpty()) { @@ -309,7 +318,7 @@ public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node // 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 String firstState = componentsMap.keySet().iterator().next(); - CustomBlockComponentsMapping firstComponentsMapping = createCustomBlockComponentsMapping(node, firstState, name); + CustomBlockComponentsMapping firstComponentsMapping = createCustomBlockComponentsMapping(object, firstState, name); customBlockDataBuilder.components(firstComponentsMapping.components()); return createCustomBlockMapping(customBlockDataBuilder, componentsMap, identifier, !onlyOverrideStates); @@ -336,7 +345,7 @@ private CustomBlockMapping createCustomBlockMapping(CustomBlockData.Builder cust String value = parts[1]; valuesMap.computeIfAbsent(property, k -> new LinkedHashSet<>()) - .add(value); + .add(value); conditions[i] = String.format("q.block_property('%s') == '%s'", property, value); blockStateBuilder = blockStateBuilder.andThen(builder -> builder.stringProperty(property, value)); @@ -349,8 +358,8 @@ private CustomBlockMapping createCustomBlockMapping(CustomBlockData.Builder cust valuesMap.forEach((key, value) -> customBlockDataBuilder.stringProperty(key, new ArrayList<>(value))); CustomBlockData customBlockData = customBlockDataBuilder - .permutations(permutations) - .build(); + .permutations(permutations) + .build(); // Build CustomBlockStates for each Java block state we wish to override Map states = blockStateBuilders.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> new CustomBlockStateMapping(e.getValue().builder().apply(customBlockData.blockStateBuilder()), e.getValue().extendedCollisionBox()))); @@ -360,22 +369,22 @@ private CustomBlockMapping createCustomBlockMapping(CustomBlockData.Builder cust /** * 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 element the state override or base block {@link JsonObject} * @param stateKey the Java block state identifier * @param name the name of the custom block * @return the {@link CustomBlockComponents} object */ - private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode node, String stateKey, String name) { + private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonElement element, String stateKey, String name) { // This is needed to find the correct selection box for the given block int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1); BoxComponent boxComponent = createBoxComponent(id); BoxComponent extendedBoxComponent = createExtendedBoxComponent(id); CustomBlockComponents.Builder builder = new GeyserCustomBlockComponents.Builder() - .collisionBox(boxComponent) - .selectionBox(boxComponent); + .collisionBox(boxComponent) + .selectionBox(boxComponent); - if (node == null) { + if (!(element instanceof JsonObject node)) { // No other components were defined return new CustomBlockComponentsMapping(builder.build(), extendedBoxComponent); } @@ -397,28 +406,28 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode // We set this to max value by default so that we may dictate the correct destroy time ourselves float destructibleByMining = Float.MAX_VALUE; if (node.has("destructible_by_mining")) { - destructibleByMining = node.get("destructible_by_mining").floatValue(); + destructibleByMining = node.get("destructible_by_mining").getAsFloat(); } builder.destructibleByMining(destructibleByMining); if (node.has("geometry")) { - if (node.get("geometry").isTextual()) { + if (node.get("geometry").isJsonPrimitive()) { builder.geometry(new GeyserGeometryComponent.Builder() - .identifier(node.get("geometry").asText()) - .build()); + .identifier(node.get("geometry").getAsString()) + .build()); } else { - JsonNode geometry = node.get("geometry"); + JsonObject geometry = node.getAsJsonObject("geometry"); GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder(); if (geometry.has("identifier")) { - geometryBuilder.identifier(geometry.get("identifier").asText()); + geometryBuilder.identifier(geometry.get("identifier").getAsString()); } if (geometry.has("bone_visibility")) { - JsonNode boneVisibility = geometry.get("bone_visibility"); - if (boneVisibility.isObject()) { + if (geometry.get("bone_visibility") instanceof JsonObject boneVisibility) { Map boneVisibilityMap = new Object2ObjectOpenHashMap<>(); - boneVisibility.fields().forEachRemaining(entry -> { + boneVisibility.entrySet().forEach(entry -> { String key = entry.getKey(); - String value = entry.getValue().isBoolean() ? (entry.getValue().asBoolean() ? "1" : "0") : entry.getValue().asText(); + String value = entry.getValue() instanceof JsonPrimitive primitive && primitive.isBoolean() + ? (entry.getValue().getAsBoolean() ? "1" : "0") : entry.getValue().getAsString(); boneVisibilityMap.put(key, value); }); geometryBuilder.boneVisibility(boneVisibilityMap); @@ -430,30 +439,30 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode String displayName = name; if (node.has("display_name")) { - displayName = node.get("display_name").asText(); + displayName = node.get("display_name").getAsString(); } builder.displayName(displayName); if (node.has("friction")) { - builder.friction(node.get("friction").floatValue()); + builder.friction(node.get("friction").getAsFloat()); } if (node.has("light_emission")) { - builder.lightEmission(node.get("light_emission").asInt()); + builder.lightEmission(node.get("light_emission").getAsInt()); } if (node.has("light_dampening")) { - builder.lightDampening(node.get("light_dampening").asInt()); + builder.lightDampening(node.get("light_dampening").getAsInt()); } boolean placeAir = true; if (node.has("place_air")) { - placeAir = node.get("place_air").asBoolean(); + placeAir = node.get("place_air").getAsBoolean(); } builder.placeAir(placeAir); if (node.has("transformation")) { - JsonNode transformation = node.get("transformation"); + JsonObject transformation = node.getAsJsonObject("transformation"); int rotationX = 0; int rotationY = 0; @@ -466,22 +475,22 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode float transformZ = 0; if (transformation.has("rotation")) { - JsonNode rotation = transformation.get("rotation"); - rotationX = rotation.get(0).asInt(); - rotationY = rotation.get(1).asInt(); - rotationZ = rotation.get(2).asInt(); + JsonArray rotation = transformation.getAsJsonArray("rotation"); + rotationX = rotation.get(0).getAsInt(); + rotationY = rotation.get(1).getAsInt(); + rotationZ = rotation.get(2).getAsInt(); } if (transformation.has("scale")) { - JsonNode scale = transformation.get("scale"); - scaleX = scale.get(0).floatValue(); - scaleY = scale.get(1).floatValue(); - scaleZ = scale.get(2).floatValue(); + JsonArray scale = transformation.getAsJsonArray("scale"); + scaleX = scale.get(0).getAsFloat(); + scaleY = scale.get(1).getAsFloat(); + scaleZ = scale.get(2).getAsFloat(); } if (transformation.has("translation")) { - JsonNode translation = transformation.get("translation"); - transformX = translation.get(0).floatValue(); - transformY = translation.get(1).floatValue(); - transformZ = translation.get(2).floatValue(); + JsonArray translation = transformation.getAsJsonArray("translation"); + transformX = translation.get(0).getAsFloat(); + transformY = translation.get(1).getAsFloat(); + transformZ = translation.get(2).getAsFloat(); } builder.transformation(new TransformationComponent(rotationX, rotationY, rotationZ, scaleX, scaleY, scaleZ, transformX, transformY, transformZ)); } @@ -493,12 +502,10 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode } if (node.has("material_instances")) { - JsonNode materialInstances = node.get("material_instances"); - if (materialInstances.isObject()) { - materialInstances.fields().forEachRemaining(entry -> { + if (node.get("material_instances") instanceof JsonObject materialInstances) { + materialInstances.entrySet().forEach(entry -> { String key = entry.getKey(); - JsonNode value = entry.getValue(); - if (value.isObject()) { + if (entry.getValue() instanceof JsonObject value) { MaterialInstance materialInstance = createMaterialInstanceComponent(value); builder.materialInstance(key, materialInstance); } @@ -506,16 +513,10 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode } } - if (node.has("placement_filter")) { - JsonNode placementFilter = node.get("placement_filter"); - if (placementFilter.isObject()) { - if (placementFilter.has("conditions")) { - JsonNode conditions = placementFilter.get("conditions"); - if (conditions.isArray()) { - List filter = createPlacementFilterComponent(conditions); - builder.placementFilter(filter); - } - } + if (node.get("placement_filter") instanceof JsonObject placementFilter) { + if (placementFilter.get("conditions") instanceof JsonArray conditions) { + List filter = createPlacementFilterComponent(conditions); + builder.placementFilter(filter); } } @@ -524,9 +525,9 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode // 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) { + if (node.get("tags") instanceof JsonArray tags) { Set tagsSet = new ObjectOpenHashSet<>(); - tags.forEach(tag -> tagsSet.add(tag.asText())); + tags.forEach(tag -> tagsSet.add(tag.getAsString())); builder.tags(tagsSet); } @@ -535,7 +536,7 @@ private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode /** * Creates a {@link BoxComponent} based on a Java block's collision with provided bounds and offsets - * + * * @param javaId the block's Java ID * @param heightTranslation the height translation of the box * @return the {@link BoxComponent} @@ -574,18 +575,18 @@ private BoxComponent createBoxComponent(int javaId, float heightTranslation) { maxZ = MathUtils.clamp(maxZ, 0, 1); return new BoxComponent( - 16 * (1 - maxX) - 8, // For some odd reason X is mirrored on Bedrock - 16 * minY, - 16 * minZ - 8, - 16 * (maxX - minX), - 16 * (maxY - minY), - 16 * (maxZ - minZ) + 16 * (1 - maxX) - 8, // For some odd reason X is mirrored on Bedrock + 16 * minY, + 16 * minZ - 8, + 16 * (maxX - minX), + 16 * (maxY - minY), + 16 * (maxZ - minZ) ); } /** * Creates a {@link BoxComponent} based on a Java block's collision - * + * * @param javaId the block's Java ID * @return the {@link BoxComponent} */ @@ -595,7 +596,7 @@ private BoxComponent createBoxComponent(int javaId) { /** * Creates the {@link BoxComponent} for an extended collision box based on a Java block's collision - * + * * @param javaId the block's Java ID * @return the {@link BoxComponent} or null if the block's collision box would not exceed 16 y units */ @@ -615,22 +616,22 @@ private BoxComponent createBoxComponent(int javaId) { /** * Creates a {@link BoxComponent} from a JSON Node - * - * @param node the JSON node + * + * @param element the JSON node * @return the {@link BoxComponent} */ - private @Nullable BoxComponent createBoxComponent(JsonNode node) { - if (node != null && node.isObject()) { + private @Nullable BoxComponent createBoxComponent(JsonElement element) { + if (element instanceof JsonObject node) { 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(); + JsonArray origin = node.getAsJsonArray("origin"); + float originX = origin.get(0).getAsFloat(); + float originY = origin.get(1).getAsFloat(); + float originZ = origin.get(2).getAsFloat(); - JsonNode size = node.get("size"); - float sizeX = size.get(0).floatValue(); - float sizeY = size.get(1).floatValue(); - float sizeZ = size.get(2).floatValue(); + JsonArray size = node.getAsJsonArray("size"); + float sizeX = size.get(0).getAsFloat(); + float sizeY = size.get(1).getAsFloat(); + float sizeZ = size.get(2).getAsFloat(); return new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ); } @@ -641,72 +642,73 @@ private BoxComponent createBoxComponent(int javaId) { /** * 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 * @return the {@link MaterialInstance} */ - private MaterialInstance createMaterialInstanceComponent(JsonNode node) { + private MaterialInstance createMaterialInstanceComponent(JsonObject node) { // Set default values, and use what the user provides if they have provided something String texture = null; if (node.has("texture")) { - texture = node.get("texture").asText(); + texture = node.get("texture").getAsString(); } String renderMethod = "opaque"; if (node.has("render_method")) { - renderMethod = node.get("render_method").asText(); + renderMethod = node.get("render_method").getAsString(); } boolean faceDimming = true; if (node.has("face_dimming")) { - faceDimming = node.get("face_dimming").asBoolean(); + faceDimming = node.get("face_dimming").getAsBoolean(); } boolean ambientOcclusion = true; if (node.has("ambient_occlusion")) { - ambientOcclusion = node.get("ambient_occlusion").asBoolean(); + ambientOcclusion = node.get("ambient_occlusion").getAsBoolean(); } return new GeyserMaterialInstance.Builder() - .texture(texture) - .renderMethod(renderMethod) - .faceDimming(faceDimming) - .ambientOcclusion(ambientOcclusion) - .build(); + .texture(texture) + .renderMethod(renderMethod) + .faceDimming(faceDimming) + .ambientOcclusion(ambientOcclusion) + .build(); } /** * Creates the list of {@link PlacementConditions} for the passed conditions node - * + * * @param node the conditions node * @return the list of {@link PlacementConditions} */ - private List createPlacementFilterComponent(JsonNode node) { + private List createPlacementFilterComponent(JsonArray node) { List conditions = new ArrayList<>(); // The structure of the placement filter component is the most complex of the current components // Each condition effectively separated into two arrays: one of allowed faces, and one of blocks/block Molang queries - node.forEach(condition -> { + node.forEach(json -> { + if (!(json instanceof JsonObject condition)) { + return; + } Set 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()))); + if (condition.get("allowed_faces") instanceof JsonArray allowedFaces) { + allowedFaces.forEach(face -> faces.add(Face.valueOf(face.getAsString().toUpperCase()))); } } LinkedHashMap blockFilters = new LinkedHashMap<>(); if (condition.has("block_filter")) { - JsonNode blockFilter = condition.get("block_filter"); - if (blockFilter.isArray()) { + if (condition.get("block_filter") instanceof JsonArray blockFilter) { blockFilter.forEach(filter -> { - if (filter.isObject()) { - if (filter.has("tags")) { - JsonNode tags = filter.get("tags"); - blockFilters.put(tags.asText(), BlockFilterType.TAG); + if (filter instanceof JsonObject jsonObject) { + if (jsonObject.has("tags")) { + JsonElement tags = jsonObject.get("tags"); + blockFilters.put(tags.getAsString(), BlockFilterType.TAG); } - } else if (filter.isTextual()) { - blockFilters.put(filter.asText(), BlockFilterType.BLOCK); + } else if (filter instanceof JsonPrimitive primitive && primitive.isString()) { + blockFilters.put(filter.getAsString(), BlockFilterType.BLOCK); } }); } @@ -720,7 +722,7 @@ private List createPlacementFilterComponent(JsonNode node) /** * 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 */ From a7fbcc6cee48dc21301cb78f763b82ed5cf94376 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 14:25:47 +0000 Subject: [PATCH 115/118] Mappings reader gson part 3 --- .../mappings/MappingsConfigReader.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java index b7d7f1abd82..5a559929b20 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java @@ -25,7 +25,8 @@ package org.geysermc.geyser.registry.mappings; -import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,8 +35,8 @@ import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; import org.geysermc.geyser.registry.mappings.versions.MappingsReader; import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1; -import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v2; +import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -47,14 +48,13 @@ public class MappingsConfigReader { public MappingsConfigReader() { this.mappingReaders.put(1, new MappingsReader_v1()); - this.mappingReaders.put(2, new MappingsReader_v2()); } public Path[] getCustomMappingsFiles() { try { return Files.walk(this.customMappingsDirectory) - .filter(child -> child.toString().endsWith(".json")) - .toArray(Path[]::new); + .filter(child -> child.toString().endsWith(".json")) + .toArray(Path[]::new); } catch (IOException e) { return new Path[0]; } @@ -75,7 +75,7 @@ public boolean ensureMappingsDirectory(Path mappingsDirectory) { return true; } - public void loadItemMappingsFromJson(BiConsumer consumer) { + public void loadItemMappingsFromJson(BiConsumer consumer) { if (!ensureMappingsDirectory(this.customMappingsDirectory)) { return; } @@ -97,10 +97,10 @@ public void loadBlockMappingsFromJson(BiConsumer con } } - public @Nullable JsonNode getMappingsRoot(Path file) { - JsonNode mappingsRoot; - try { - mappingsRoot = GeyserImpl.JSON_MAPPER.readTree(file.toFile()); + public @Nullable JsonObject getMappingsRoot(Path file) { + JsonObject mappingsRoot; + try (FileReader reader = new FileReader(file.toFile())) { + mappingsRoot = (JsonObject) new JsonParser().parse(reader); } catch (IOException e) { GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e); return null; @@ -114,8 +114,8 @@ public void loadBlockMappingsFromJson(BiConsumer con return mappingsRoot; } - public int getFormatVersion(JsonNode mappingsRoot, Path file) { - int formatVersion = mappingsRoot.get("format_version").asInt(); + public int getFormatVersion(JsonObject mappingsRoot, Path file) { + int formatVersion = mappingsRoot.get("format_version").getAsInt(); if (!this.mappingReaders.containsKey(formatVersion)) { GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion); return -1; @@ -124,7 +124,7 @@ public int getFormatVersion(JsonNode mappingsRoot, Path file) { } public void readItemMappingsFromJson(Path file, BiConsumer consumer) { - JsonNode mappingsRoot = getMappingsRoot(file); + JsonObject mappingsRoot = getMappingsRoot(file); if (mappingsRoot == null) { return; @@ -140,7 +140,7 @@ public void readItemMappingsFromJson(Path file, BiConsumer consumer) { - JsonNode mappingsRoot = getMappingsRoot(file); + JsonObject mappingsRoot = getMappingsRoot(file); if (mappingsRoot == null) { return; From 0ae556c6657a078fbe73cec41443290b066eec38 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 14:30:34 +0000 Subject: [PATCH 116/118] Whoops almost removed v2 mappings --- .../geyser/registry/mappings/MappingsConfigReader.java | 4 +++- .../geyser/registry/mappings/versions/MappingsReader_v2.java | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java index 5a559929b20..58eaa507ede 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/MappingsConfigReader.java @@ -35,6 +35,7 @@ import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; import org.geysermc.geyser.registry.mappings.versions.MappingsReader; import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1; +import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v2; import java.io.FileReader; import java.io.IOException; @@ -48,6 +49,7 @@ public class MappingsConfigReader { public MappingsConfigReader() { this.mappingReaders.put(1, new MappingsReader_v1()); + this.mappingReaders.put(2, new MappingsReader_v2()); } public Path[] getCustomMappingsFiles() { @@ -75,7 +77,7 @@ public boolean ensureMappingsDirectory(Path mappingsDirectory) { return true; } - public void loadItemMappingsFromJson(BiConsumer consumer) { + public void loadItemMappingsFromJson(BiConsumer consumer) { if (!ensureMappingsDirectory(this.customMappingsDirectory)) { return; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 786f98a4f97..271b5f10710 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -99,6 +99,7 @@ private void readItemDefinitionEntry(JsonElement data, String itemIdentifier, Id // Read model of group if it's present, or default to the model of the parent group, if that's present // If the parent group model is not present (or there is no parent group), and this group also doesn't have a model, then it is expected the definitions supply their model themselves Identifier groupModel = MappingsUtil.readOrDefault(data, "model", NodeReader.IDENTIFIER, model, context); + // The method above should have already thrown a properly formatted error if data is not a JSON object JsonElement definitions = data.getAsJsonObject().get("definitions"); @@ -171,8 +172,6 @@ private CustomItemBedrockOptions.Builder readBedrockOptions(JsonElement element, MappingsUtil.readIfPresent(element, "protection_value", builder::protectionValue, NodeReader.NON_NEGATIVE_INT, context); MappingsUtil.readIfPresent(element, "creative_category", builder::creativeCategory, NodeReader.CREATIVE_CATEGORY, context); MappingsUtil.readIfPresent(element, "creative_group", builder::creativeGroup, NodeReader.NON_EMPTY_STRING, context); - MappingsUtil.readIfPresent(element, "texture_size", builder::textureSize, NodeReader.POSITIVE_INT, context); - MappingsUtil.readIfPresent(element, "render_offsets", builder::renderOffsets, MappingsReader::fromJsonObject, context); if (element.getAsJsonObject().get("tags") instanceof JsonArray tags) { Set tagsSet = new ObjectOpenHashSet<>(); From 3fff60199782803fafcc9ff92c9be29027d9a795 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 14:50:51 +0000 Subject: [PATCH 117/118] Some fixes and stuff --- .../geyser/registry/mappings/util/MappingsUtil.java | 7 ------- .../geysermc/geyser/registry/mappings/util/NodeReader.java | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java index 417ac2c8043..8eb78b1e636 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/MappingsUtil.java @@ -91,13 +91,6 @@ public static void readIfPresent(JsonElement node, String name, Consumer } } - // TODO don't use this method but throw an error in the above reading methods - public static void requireObject(JsonNode node, String task, String... context) throws InvalidCustomMappingsFileException { - if (node == null || !node.isObject()) { - throw new InvalidCustomMappingsFileException(task, "expected an object", context); - } - } - private static JsonElement getJsonElement(JsonElement element, String name, String... context) throws InvalidCustomMappingsFileException { if (!element.isJsonObject()) { throw new InvalidCustomMappingsFileException(formatTask(name), OBJECT_ERROR, context); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 8c9da5bd204..a67f3404b24 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -46,8 +46,9 @@ public interface NodeReader { NodeReader INT = node -> { - if (node.getAsNumber() instanceof Integer i) { - return i; + double i = node.getAsDouble(); + if (i == (int) i) { // Make sure the number is round + return (int) i; } throw new InvalidCustomMappingsFileException("expected node to be an integer"); }; From de95eb5da0f0f3ed9cb2e7e15f041e98cec38722 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 19 Jan 2025 15:01:10 +0000 Subject: [PATCH 118/118] Bye bye render offsets --- .../api/item/custom/CustomItemData.java | 2 -- .../custom/v2/CustomItemBedrockOptions.java | 25 ------------------- .../GeyserCustomItemBedrockOptions.java | 21 ++-------------- .../CustomItemRegistryPopulator.java | 23 ----------------- 4 files changed, 2 insertions(+), 69 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index ba80efbdd44..caae5d058d1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -140,8 +140,6 @@ default CustomItemDefinition.Builder toDefinition(Identifier javaItem) { .displayHandheld(displayHandheld()) .creativeCategory(creativeCategory().isEmpty() ? CreativeCategory.NONE : CreativeCategory.values()[creativeCategory().getAsInt()]) .creativeGroup(creativeGroup()) - .textureSize(textureSize()) - .renderOffsets(renderOffsets()) .tags(tags()) ); diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java index 6b81a3c4637..3f43d7720fe 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemBedrockOptions.java @@ -87,25 +87,6 @@ public interface CustomItemBedrockOptions { @Nullable String creativeGroup(); - /** - * Gets the item's texture size. This is to resize the item if the texture is not 16x16. - * - * @deprecated resizing is done with render offsets, which are deprecated in favour of attachables. - * @return the item's texture size - */ - @Deprecated - int textureSize(); - - /** - * Gets the item's render offsets. If it is null, the item will be rendered normally, with no offsets. - * - * @deprecated attachables are now preferred instead of using render offsets. - * @return the item's render offsets - */ - @Nullable - @Deprecated - CustomRenderOffsets renderOffsets(); - /** * Gets the item's set of tags that can be used in Molang. * Equivalent to "tag:some_tag" @@ -133,12 +114,6 @@ interface Builder { Builder creativeGroup(@Nullable String creativeGroup); - @Deprecated - Builder textureSize(int textureSize); - - @Deprecated - Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); - Builder tags(@Nullable Set tags); CustomItemBedrockOptions build(); diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java index 726cfa00eb6..3119f9381e7 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemBedrockOptions.java @@ -36,8 +36,7 @@ import java.util.Set; public record GeyserCustomItemBedrockOptions(@Nullable String icon, boolean allowOffhand, boolean displayHandheld, int protectionValue, - @NonNull CreativeCategory creativeCategory, @Nullable String creativeGroup, - int textureSize, @Nullable CustomRenderOffsets renderOffsets, @NonNull Set tags) implements CustomItemBedrockOptions { + @NonNull CreativeCategory creativeCategory, @Nullable String creativeGroup, @NonNull Set tags) implements CustomItemBedrockOptions { public static class Builder implements CustomItemBedrockOptions.Builder { private String icon = null; @@ -46,8 +45,6 @@ public static class Builder implements CustomItemBedrockOptions.Builder { private int protectionValue = 0; private CreativeCategory creativeCategory = CreativeCategory.NONE; private String creativeGroup = null; - private int textureSize = 16; - private CustomRenderOffsets renderOffsets = null; private Set tags = new HashSet<>(); @Override @@ -86,20 +83,6 @@ public Builder creativeGroup(@Nullable String creativeGroup) { return this; } - @Override - @Deprecated - public Builder textureSize(int textureSize) { - this.textureSize = textureSize; - return this; - } - - @Override - @Deprecated - public Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets) { - this.renderOffsets = renderOffsets; - return this; - } - @Override public Builder tags(@Nullable Set tags) { this.tags = Objects.requireNonNullElseGet(tags, Set::of); @@ -109,7 +92,7 @@ public Builder tags(@Nullable Set tags) { @Override public CustomItemBedrockOptions build() { return new GeyserCustomItemBedrockOptions(icon, allowOffhand, displayHandheld, protectionValue, - creativeCategory, creativeGroup, textureSize, renderOffsets, tags); + creativeCategory, creativeGroup, tags); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index c3b76298f75..5b630ef0182 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -286,8 +286,6 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD computeUseCooldownProperties(useCooldown, componentBuilder); } - computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); - componentBuilder.putCompound("item_properties", itemProperties.build()); builder.putCompound("components", componentBuilder.build()); @@ -560,27 +558,6 @@ private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBui ); } - private static void computeRenderOffsets(CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) { - // TODO remove this one day when, probably when removing the old format, as render offsets are deprecated - CustomRenderOffsets renderOffsets = bedrockOptions.renderOffsets(); - if (renderOffsets != null) { - componentBuilder.remove("minecraft:render_offsets"); - componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets)); - } else if (bedrockOptions.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) { - float scale1 = (float) (0.075 / (bedrockOptions.textureSize() / 16f)); - float scale2 = (float) (0.125 / (bedrockOptions.textureSize() / 16f)); - float scale3 = (float) (0.075 / (bedrockOptions.textureSize() / 16f * 2.4f)); - - componentBuilder.putCompound("minecraft:render_offsets", - NbtMap.builder().putCompound("main_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale3, scale3, scale3)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()) - .putCompound("off_hand", NbtMap.builder() - .putCompound("first_person", xyzToScaleList(scale1, scale2, scale1)) - .putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build()); - } - } - private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) { NbtMapBuilder builder = NbtMap.builder();