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.
+ *
+ * - First by checking their priority values.
+ * - Then by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first.
+ * - Lastly 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.
@@ -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:
*
*
- * - First by checking their priority values.
+ * - First by checking their priority values, higher priority values going first.
* - Then by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first.
* - Lastly by the amount of predicates, from most to least.
*
@@ -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:
+ *
+ *
+ * - First by checking their priority values, higher priority values going first.
+ * - Then by checking if they both have a similar range dispatch predicate, the one with the highest threshold going first.
+ * - 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.
+ *
*/
-// 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:
*
*
* - First by checking their priority values, higher priority values going first.
@@ -48,9 +48,8 @@
* - 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 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 super Object>) 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