Skip to content

Custom item API v2 #5189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 202 commits into
base: master
Choose a base branch
from

Conversation

eclipseisoffline
Copy link
Member

@eclipseisoffline eclipseisoffline commented Dec 4, 2024

If you are planning to use a build from this PR to create custom items making use of the 1.21.4+ format, please read the following:

To use the format described in this PR, you have to use a build from it. You can download a build by clicking on "Checks", then "Artifacts", and then selecting your platform. This will download a ZIP-file, which you can then unzip to obtain a .jar file.

This PR is experimental, and bugs will likely occur. You are free to report any either here or on the GeyserMC Discord, in the #custom-resource-packs channel.

Documentation for the new JSON mappings format is available below. Example mappings making use of this PR's format, including a bedrock resourcepack, are available here. If you have any questions regarding the format, please ask those in the aforementioned Discord channel.

There are no public resourcepack converters available that support this PR's format.


This PR adds a new API for (vanilla and non-vanilla) custom items, along with a new JSON mappings format for it.

This new API was necessary to incorporate the amount of changes made recently to items in Minecraft Java, primarily the introduction of new item components and item model definitions, adding a new predicate system. The new API is designed to be somewhat similar to the component system and item model definitions.

The currently existing API and JSON mappings format will be deprecated, but will continue to be usable. Custom items made using the existing API will automatically be translated at runtime to the objects from the new API.

Along with introducing a new API for custom items, this PR also cleans up the custom item registry populator a bit and makes use of default item components to get the properties of vanilla items, and makes some other minor changes.

Before going into the specification, here is some vocabulary used in custom items on Java and Bedrock:

  • Java item: any real, existing item on vanilla Java. Java datapacks make custom items by overriding the components of a Java item.
  • Java item component: Java items have their properties and behaviour defined in item components. Every Java item has a set of default components, and item stacks can override these components.
  • Bedrock item component: Bedrock items also have components that determine item behaviour, similar to Java, however unlike Java items, these components can't be changed at runtime for item stacks, which is one of the major reasons a custom item API is required in the first place.
  • Java item model definition: these were introduced in Java 1.21.4 and exist in the assets/<namespace>/items/ resource pack directory. They decide which model a Java item should use based on a set of rules and item properties. Every Java item stores its item model definition in its minecraft:item_model item component, which can in term be overridden on an item stack by a datapack to use a custom item model definition defined in a resource pack.
  • Custom item definition: a custom item definition is a Geyser term. It represents a single Bedrock custom item that is used to map (part of) a Java item model definition. It contains info on the item's properties on Java and Bedrock.
    • Multiple custom item definitions, and thus multiple Bedrock items, can exist for the same Java item model definition, but for every combination of a Java item and Java item model definition, there can only be one custom item definition without predicates.
  • Non-vanilla custom item definition: also a Geyser term. Represents a Bedrock custom item that maps a Java non-vanilla item, and is as such only used for modded items. Like normal custom item definitions, it contains info on the item's properties on Java and Bedrock.
    • At the moment, there can only be one non-vanilla custom item definition per Java non-vanilla item. In the future, this might be changed to add support for non-vanilla custom item definitions making use out of predicates.
    • Non-vanilla custom item definitions have some additional features on top of normal custom item definitions to allow configuring options that are not possible to configure for custom items made with Java vanilla items. These include options like chargeable (shooter) items.

The new format

Currently, the new format for JSON-mappings looks somewhat like this:

{
  "format_version": 2,
  "items": {
    "minecraft:flint": [
      {
        "type": "definition",
        "model": "geyser_mc:test_item",
        "bedrock_identifier": "geyser_mc:test_item",
        "display_name": "An Example Item!",
        "bedrock_options": {
          "icon": "potato",
          "creative_category": "items"
        }
      },
      {
        "type": "group",
        "model": "geyser_mc:another_test_item",
        "definitions": [
          {
            "bedrock_identifier": "geyser_mc:another_test_item",
            "bedrock_options": {
              "icon": "carrot"
            },
            "components": {
              "minecraft:consumable": {
                "animation": "drink",
                "consume_seconds": 10
              }
            }
          },
          {
            "bedrock_identifier": "geyser_mc:another_test_item_nether",
            "bedrock_options": {
              "icon": "carrot"
            },
            "predicate": {
              "type": "match",
              "property": "context_dimension",
              "value": "minecraft:the_nether"
            },
            "components": {
              "minecraft:consumable": {
                "animation": "drink",
                "consume_seconds": 10
              }
            }
          }
        ]
      }
    ]
  }
}

The start of the format is similar to the current format. There is an items key, which is an object in which keys are Java items, and in which each value is an array of custom item definitions for that Java item, or an object specifying a group of such.

Specifically, each value in the array can either look like this:

{
  "type": "definition",
  "model": "geyser_mc:a_cool_item_model",
  "bedrock_identifier": "geyser_mc:a_cool_item"
}

Which is a single custom item definition for a Java item model definition, or like this:

{
  "type": "group",
  "model": "geyser_mc:a_cool_item_model", // Optional
  "definitions": [...]
}

Which is a group of custom item definitions. All definitions in the group inherit the Java model definition of the group, if any (so, a group is not required to have a model, in which case the definitions in the group have to specify the model themselves). Definitions in a group are also allowed to be a group, and definitions in a group can also override the Java model definition of the group.

Note that, when the type key is not specified, it is defaulted to definition.

The bedrock_identifier key of a custom item definition determines its identifier on Bedrock, which is also used in e.g. Bedrock attachables. If no namespace is given here, then the geyser_custom namespace is used. Every item definition is required to have a unique Bedrock identifier.

Alongside these 3 keys, there are 4 more keys item definitions can have:

  • bedrock_options
  • components
  • predicate
  • predicate_strategy
  • priority
  • display_name

Custom item definition bedrock options

The bedrock_options key is an object that sets options for the item on Bedrock, that cannot be set using Java item components. Possible keys are:

  • icon: determines the icon to use for the item. If not set, the item's bedrock identifier is used, : replaced with . and / with _ (for example, geyser_mc:a_cool_item turns into geyser_mc.a_cool_item).
  • allow_offhand: if the item should be allowed in the offhand slot. Defaults to true.
  • creative_category: sets the item's creative category (for the recipe book). Can be none, construction, nature, equipment, or items. Defaults to none.
  • display_handheld: if the item should display handheld, like a tool or weapon. Defaults to false.
  • protection_value: determines how many armour points should be shown when this item is worn. This is purely visual. Only has an effect when the item is equippable, and defaults to 0.
  • tags: Bedrock tags the item has, can be used in Molang.

The render_offsets and texture_size options have been removed. render_offsets has been deprecated for a long time, and is longer functional on the bedrock client, and texture_size depended on it. As an alternative, use attachables in your bedrock resourcepack instead.


Custom item definition components

The components key defines properties for the item, in the Java item component format. It is expected that the item will always have these components when the custom item definition is used. Currently, the following components are supported:

  • minecraft:consumable: doesn't support consume particles/sounds.
  • minecraft:equippable: doesn't support the camera overlay or swappable properties.
  • minecraft:food
  • minecraft:max_damage
  • minecraft:max_stack_size
  • minecraft:use_cooldown
  • minecraft:enchantable
    • On Bedrock, this will be mapped to the minecraft:enchantable component with slot=all. This should, but does not guarantee, allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.
  • minecraft:enchantment_glint_override

Some components, like minecraft:rarity and minecraft:attribute_modifiers, are already automatically translated and don't need to be specified here.


Custom item definition predicates

predicate is either an object (single predicate) or an array of objects (multiple predicates). For each combination of a Java item and a Java item model definition, there can be one item definition without a predicate, and one or multiple definitions with a predicate. There can't be multiple item definitions with the same predicates for the same Java item and Java item model definition. If the Java item model definition is in the minecraft namespace, there can't be an item definition without a predicate.

Each predicate has a type and a property key. Currently, there are 3 predicate types:

  • condition
  • match
  • range_dispatch

The condition predicate type checks for a boolean property and returns true if the property matches the expected value. It has 4 possible properties:

  • broken: if the item is broken (has only 1 durability point left).
  • damaged: if the item is damaged (not at full durability).
  • unbreakable: if the item is unbreakable.
  • fishing_rod_cast: if the player is currently holding a cast fishing rod.
  • custom_model_data: checks the item's custom model data flags. Defaults to false.

The condition predicate also has the expected key, which specifies if the property has to be true or false for this predicate to be true. Defaults to true.

The match predicate type checks for a text-like property and returns true if it matches the given value. It has 4 possible properties:

  • charge_type: the item currently charged in the crossbow (in the minecraft:charged_projectiles component). Can be none, arrow, or rocket.
  • trim_material: the trim material (resource location) of this item.
  • context_dimension: the dimension (resource location) the player is currently in.
  • custom_model_data: fetches a string from the item's custom model data strings.

The match predicate requires a value to be specified in the value key.

The range_dispatch predicate type checks for a numeric property and returns true if it is above the specified threshold. It has 4 possible properties:

  • bundle_fullness: checks the item's bundle fullness. Returns the total stack count of all the items in a bundle (in the minecraft:bundle_contents component).
  • damage: checks the item's damage value. Can be normalised.
  • count: checks the item's count. Can be normalised.
  • custom_model_data: checks the item's custom model data floats. Defaults to 0.0.

The range_dispatch predicate has 3 extra keys, one of them required:

  • threshold: the threshold required to return true (required).
  • scale: the factor to scale the property value by before comparing it with the threshold. Defaults to 1.0.
  • normalize: if the property value should be normalized before scaling it and comparing it with the threshold. Defaults to false, only works for certain properties.

All predicates can also have an index key, which determines which index to use when using a custom model data property. Defaults to 0.

Some may notice these predicates are similar to the ones possible in Java 1.21.4's item model definitions. I plan to possibly extend the current predicates. Some examples of how predicates can look:

{
  "type": "match",
  "property": "charge_type",
  "value": "arrow"
}
{
  "type": "condition",
  "property": "custom_model_data",
  "index": 1
}
{
  "type": "match",
  "property": "context_dimension",
  "value": "minecraft:overworld"
}

There is also a predicate_strategy key, which can be and or or and defaults to and. This was inspired by the advancement requirement strategies, and decides if all predicates (and), or only one predicate (or) of an item definition has to match for it to be used.


Custom item definition sorting and priority

Custom item definitions are automatically sorted so that the definitions' predicates are checked in a correct order when translating items. Specifically, Geyser sorts custom item definitions like this:

  1. First custom item definitions with a higher priority value are sorted above definitions with lower ones.
  2. Custom item definitions with similar range dispatch predicates are sorted by their threshold, with higher thresholds going first.
  3. Custom item definitions with more predicates are sorted over definitions with less.

This system ensures that in most cases, item definitions with predicates are checked in proper order, no matter which order they are put in the mappings. In the few cases that Geyser does not sort definitions correctly (that will most likely occur when using multiple range_dispatch predicates, or using them in combination with other predicates), the priority key can be used to specify which definitions should be checked first.


This new format has a few key benefits over the current format:

  • Item behaviour can be defined in ways that are not possible with the current format, for example item consuming, custom item cooldowns, item stack size, and more.
  • The predicate system is more extensive.
  • The format is written in a way that is recognisable to Java developers, by using formats similar to Java item components and Java item model definitions.

The API and non-vanilla custom items

Along with the introduction of a new JSON mappings format, the entire custom item API has received an overhaul too. Detailed changes won't be described here, but there are Javadocs documenting all the functionality somewhat well. Just as the v1 JSON mappings format will continue to be usable, the old item API can also continue to be used for the time being.


TODOs

A lot of the TODOs for this PR are now finished. There are still some left on this list and in the code, but none are of a very significant magnitude.

  • Range dispatch predicate
  • Non vanilla custom items
  • Possibly main hand match property not possible
  • Possibly using item, has component cast fishing rod, selected bundle item condition properties
    • Only has component and cast fishing rod were possible.
  • Consumable component properties (sound, consume particles, etc.) if those are possible
    • Might be server side, check Tested, consume particles are done entirely client side, and the sound is only sent to clients other than the client consuming the item. Doesn't seem possible to emulate this to me.
  • Fix consumable animations/, use animation/use duration properties on bedrock which seem to be randomly used with magic numbers
  • Fix protection value/enchantment value properties
  • Check and possibly fix Bedrock tool, chargeable, throwable and possibly other components that may have changed and not properly updated in the custom item registry populator
  • Check what to do with the minecraft:tool Java component
  • Make sure stack size is 1 when an equippable is set since Bedrock doesn't support armour with a stack size above 1, also validate other components (e.g. damage and stack size combination)
  • Add a custom model data predicate when converting v1 to v2 at runtime (when range dispatch predicate is added)
  • Unbreakable condition predicate, also implement in v1 to v2 converter
  • Custom item definition priority
  • Don't use MCPL in API module, when creating component classes in API make sure to do proper validation
  • Cleanup item registry populator: registeredItemNames may be identifiers, identifierToKey method needs to go elsewhere, customItemName is always the bedrock identifier
  • Don't use adventure in API module
  • More proper PR description of code changes
  • Block mapping reading in the V2 reader
  • Better error handling/printing when mapping reading/custom item registry populator fails
  • Clean up mapping reader/data component JSON reader with helper methods to lessen code duplication
  • Fix the broken/damaged conditional predicates
  • Take range dispatch scaling into account when sorting predicates
  • Map food nutrition/saturation properties, if bedrock sends them to the client
  • Update PR description for range dispatch predicates, unbreakable condition predicate and priority
  • Maybe predicate caching, so that predicates that occur in multiple definitions for the same item/item model aren't recalculated multiple times
  • Move predicate stuff in API to core
  • Replace unbreakable predicate with has unbreakable component once has component predicate is a thing
  • Predicates AND/OR strategy
  • Predicate validation: check if indices are non negative
  • Predicate/bedrock options node reader?
  • Custom model data arrows/projectiles (by defining projectile component, also make this an option for non-vanilla items)
  • Some components need fixing (block placer, chargeable (animation))
  • Predicate abstraction: eclipseisoffline/Geyser#2
  • Documentation
  • Other TODOs left in the code
  • A lot of testing, probably

Although the PR is not finished yet, the code is decently clean, and (especially the API module), somewhat well documented. Reviews are welcome!


Testing

Some testing has been done already with a lot of (relatively simple) custom items that make use out of components like consumable, equippable and use_cooldown and use the condition and match predicates. This seemed to work decent so far. Testing that still has to be done:

  • Verify that v1 mappings convert over correctly and can be used without much downsides
  • Range dispatch predicates
  • Creating custom item definitions that for a minecraft: (vanilla) item model with a predicate
  • Error handling
  • Predicate strategies
  • Anything you can think of that should work with this new system

(To do: add more here)

@Kas-tle
Copy link
Member

Kas-tle commented Dec 5, 2024

FYI I believe that the plan for items on Bedrock itself is to eventually deprecate render offsets on items in favor of attachables/texture meshes, so we should probably note that in any documentation.

Also as long as you're adding stuff, it might be nice to add some sort of a priority value at the top level of the mappings file for specifying the load order of files since I've seen a lot of cases where people want to separate the same item across files for organizational reasons, but cannot because it results in predicates being applied incorrectly.

Otherwise I think the plan for the format is solid. Haven't reviewed the code yet since it sounds like that's still a WIP.

@eclipseisoffline
Copy link
Member Author

I had a feeling that might be the case - maybe we should just mark the whole render offsets option as deprecated all together, telling users to use attachables/texture meshes instead?

As for priority values, I've been working on automatically sorting predicates. For example, let's say you have 3 item definitions for the same item model:

- one with predicate A
- one with predicates A and B (A && B)
- one with predicates A and C (A && C)
- one without predicates (fallback)

Geyser would then automatically sort them from most predicates to least, so that it'll match them in this order:
- A && B
- A && C
- A
- Fallback, if no predicates match

This way no matter the order you put predicates in the mappings, they'd always be checked in the right order so that the A predicate won't be used if A && C also matches.

This worked pretty well until I got to range dispatch predicates - I made these sort by comparing their threshold value, and predicates with higher threshold values will be checked first. This doesn't always work properly however, for example when you have a definition with multiple range dispatch predicates checking for different properties with different thresholds.

It might be a good idea to add an optional priority key to item definitions themself, for example like this:

{
  "type": "group",
  "model": "geyser_mc:test_item",
  "definitions": [
    {
      "bedrock_identifier": "geyser_mc:test_item_1",
      "priority": 1,
      "predicate": [
        {
          "type": "range_dispatch",
          "property": "custom_model_data",
          "threshold": 100.0,
          "index": 0
        },
        {
          "type": "range_dispatch",
          "property": "custom_model_data",
          "threshold": 50.0,
          "index": 1
        }
      ]
    },
    {
      "bedrock_identifier": "geyser_mc:test_item_2",
      "priority": 2,
      "predicate": [
        {
          "type": "range_dispatch",
          "property": "custom_model_data",
          "threshold": 20.0,
          "index": 1
        },
        {
          "type": "range_dispatch",
          "property": "custom_model_data",
          "threshold": 30.0,
          "index": 2
        }
      ]
    }
  ]
}

This way the second custom item definition would be checked first.

And yeah - code is definitely still WIP.

# Conflicts:
#	core/src/main/java/org/geysermc/geyser/item/type/Item.java
#	core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java
#	core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java
@eclipseisoffline
Copy link
Member Author

The removal of default item components can now be specified in definitions under the components key. Non-vanilla custom items cannot specify component removals, as they are specifying default item components, not a component patch.

@eclipseisoffline
Copy link
Member Author

@TheosisOfficial the feature you requested should now be implemented in the latest build of this PR. For every item that uses another vanilla item's model, you have to define a definition in a mapping file, like so:

{
  "format_version": 2,
  "items": {
    "minecraft:poisonous_potato": [
      {
        "type": "definition",
        "model": "minecraft:iron_ingot",
        "bedrock_identifier": "poisonous_iron",
        "bedrock_options": {
          "icon": "iron_ingot"
        }
      }
    ]
  }
}

@eclipseisoffline
Copy link
Member Author

@MinecraftNight4 could you please check if the issue you reported still occurs on the latest PR build?

@TheosisOfficial
Copy link

@TheosisOfficial the feature you requested should now be implemented in the latest build of this PR. For every item that uses another vanilla item's model, you have to define a definition in a mapping file, like so:

{
  "format_version": 2,
  "items": {
    "minecraft:poisonous_potato": [
      {
        "type": "definition",
        "model": "minecraft:iron_ingot",
        "bedrock_identifier": "poisonous_iron",
        "bedrock_options": {
          "icon": "iron_ingot"
        }
      }
    ]
  }
}

Will we always have to define a definition in a mapping file, rather than just having the item model be what is in its components already?

How would I make my custom items obey the mappings? (Sorry, I am inexperienced with resource pack stuff)

@eclipseisoffline
Copy link
Member Author

Marking this PR as ready to review. While there are still some small TODOs to be checked, almost all of the code is now in a mostly finished state, with almost all planned features implemented.

@eclipseisoffline eclipseisoffline marked this pull request as ready for review May 13, 2025 11:59
@eclipseisoffline
Copy link
Member Author

Will we always have to define a definition in a mapping file, rather than just having the item model be what is in its components already?

How would I make my custom items obey the mappings? (Sorry, I am inexperienced with resource pack stuff)

@TheosisOfficial Yes, unfortunately. Geyser needs to know which custom items it can expect from the server, so it can tell bedrock players that information when they join the server, since they don't support dynamically changing item components. Other than creating these mappings, no bedrock resourcepack should be required as long as you're using vanilla icons (which is what I did in my mappings example).

@TheosisOfficial
Copy link

TheosisOfficial commented May 13, 2025

Will we always have to define a definition in a mapping file, rather than just having the item model be what is in its components already?
How would I make my custom items obey the mappings? (Sorry, I am inexperienced with resource pack stuff)

@TheosisOfficial Yes, unfortunately. Geyser needs to know which custom items it can expect from the server, so it can tell bedrock players that information when they join the server, since they don't support dynamically changing item components. Other than creating these mappings, no bedrock resourcepack should be required as long as you're using vanilla icons (which is what I did in my mappings example).

How would I reference my custom items in the mapping file, so it only affects those items, and not all items of the same type? My custom items are all just a minecraft:music_disc_13 with a PDC to identify them, and its jukebox_playable component removed using Paper's data component API. Something to do with the bedrock_identifier? Do I put that somewhere in my custom item's metadata?

@eclipseisoffline eclipseisoffline added PR: Feature When a PR implements a new feature API The issue/feature request relates to the Geyser API labels May 13, 2025
Copy link
Member

@onebeastchris onebeastchris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Some more thoughts, will continue later

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks accidental, should target GeyserMC/mappings@f4be123

customModelDataInt = customModelData.floats().get(0);
}
Key itemModel = components.getOrDefault(DataComponentTypes.ITEM_MODEL, FALLBACK_MODEL);
Collection<GeyserCustomMappingData> customItems = allCustomItems.get(itemModel);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like it would be possible to register a custom item override for the air model.. how does java handle that? or should we return if ITEM_MODEL isn't set? (or is this handled elsewhere?)

if (!value) {
allMatch = false;
break;
} else if (needsOnlyOneMatch) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldnt this return the definition here?
Otherwise, allMatch can be false at this point (if the first predicate wasnt a match), and no definition would be returned

/**
* 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<Key, GeyserCustomMappingData> customItemDefinitions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this used to be NonNull, let's mark this nullable

int customProtocolId = nextFreeBedrockId++;

String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.name();
if (!registeredItemNames.add(customItemName)) {
Identifier customItemIdentifier = customItem.bedrockIdentifier(); // TODO don't forget to check if this works for non vanilla too, it probably does
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just marking the TODO

/**
* The display name of the item. If none is set, the display name is taken from the item's Bedrock identifier.
*/
@NonNull String displayName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts of moving this to CustomItemBedrockOptions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

further - can this be set to a bedrock translatable string? if yes, that's worth documenting here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API The issue/feature request relates to the Geyser API PR: Feature When a PR implements a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Custom Item components such as "item_model" and "asset_id" from a datapack do not work on bedrock/geyser
8 participants