Skip to content
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

Custom item API v2 #5189

Draft
wants to merge 82 commits into
base: master
Choose a base branch
from

Conversation

eclipseisoffline
Copy link
Contributor

@eclipseisoffline eclipseisoffline commented Dec 4, 2024

This PR adds a new format for mapping custom items.

This new format 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 format is designed to be somewhat similar to the component system and item model definitions.

The currently existing format will be deprecated, but will continue to be usable. Mappings in the existing format will automatically be translated at runtime to the new format.

Along with introducing a new format for item mappings, 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.

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.1
    • 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.

The new format

Currently, the new format looks somewhat like this:

{
  "format_version": 2,
  "items": {
    "minecraft:flint": [
      {
        "type": "definition",
        "model": "geyser_mc:test_item",
        "bedrock_identifier": "geyser_mc:test_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

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.
  • render_offsets: render offsets, similar to the existing format. This key is deprecated, as it is now preferred to use attachables rather than render offsets.
  • texture_size: the size of the texture on Bedrock. Defaults to 16. This key is deprecated, as it makes use of render offsets internally.
  • tags: Bedrock tags the item has, can be used in Molang.

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.

Some components, like minecraft:rarity, minecraft:enchantment_glint_override, 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.
  • 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.

Code changes unrelated to the API

To allow for matching trim materials and dimensions in custom item predicates, JavaRegistry<T> now stores its entries wrapped in a RegistryEntryData<T> object, which stores the entry key and the entry object.


TODOs

This PR still needs a bit of work. The one big thing left is support for non vanilla custom items. Other than that, here's a TODO list I've been working with:

  • 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
  • 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?
  • 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: 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
Contributor 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 eclipseisoffline changed the title Custom item api V2 Custom item API v2 Jan 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants