Skip to content

Item Behaviors

Item behaviors are used to add functionality to items. There are some default implementations, but you can also create your own.

Default Item Behaviors

Default Item Behaviors

These are the default item behaviors that Nova provides:

Allows you to make a custom consumable item. Example:

val EXAMPLE_ITEM = registerItem("example_item", Consumable)
configs/example_item.yml
# The type of food (normal, fast, always_eatable).
food_type: normal
# The time it takes for the food to be consumed, in ticks.
consume_time: 40
# The nutrition value this food provides.
nutrition: 4
# The saturation modifier this food provides.
saturation_modifier: 0.3
# The amount of health to be restored immediately.
instant_health: 5
# A list of effects to apply to the player when this food is consumed.
effects: []
Saturation & Nutrition

This is how the saturation_modifier and nutrition value affects your player's food level and saturation:

foodLevel
min(player.foodLevel + options.nutrition, 20)
saturation
min(saturation + nutrition * saturationModifier * 2.0f, foodLevel)

You can find the nutrition and saturationModifier for vanilla items by decompiling the mojang-mapped class net.minecraft.world.food.Foods.

Example Effect
effects:
# A level 1 speed effect that lasts 10 seconds.
- type: speed 
  duration: 200 
  amplifier: 0 
  ambient: true 
  particles: true 
  icon: true 

Allows you to make an item that can be equipped in a players armor slots.

val EXAMPLE_ITEM = registerItem("example_item", Wearable(ArmorType.CHESTPLATE, Sound.ITEM_ARMOR_EQUIP_DIAMOND))
configs/example_item.yml
armor: 8.0
armor_toughness: 3.0
knockback_resistance: 2.0

If you need some examples for the armor, armorToughness and knockback_resistance values, you can check out the Minecraft wiki.

See Tools.

Makes an item damageable.

val EXAMPLE_ITEM = registerItem("example_item", Damageable)
configs/example_item.yml
# The maximum durability of the item.
max_durability: 200
# The damage the item takes when it is used to attack an entity.
item_damage_on_attack_entity: 1
# The damage the item takes when it is used to break a block.
item_damage_on_break_block: 2
# The repair ingredient that can be used in anvils.
repair_ingredient: "minecraft:paper"

Gives your item the ability to strip wood, logs, oxidization layers and wax.

val EXAMPLE_ITEM = registerItem("example_item", Stripping)

Gives your item the ability to create dirt paths.

val EXAMPLE_ITEM = registerItem("example_item", Flattening)

Gives your item the ability to extinguish campfires.

val EXAMPLE_ITEM = registerItem("example_item", Extinguishing)

Gives your item the ability to till dirt.

val EXAMPLE_ITEM = registerItem("example_item", Tilling)

Allows your item to be used as fuel in furnaces.

val EXAMPLE_ITEM = registerItem("example_item", Fuel)
configs/example_item.yml
burn_time: 20 

Makes your item fire resistant.

val EXAMPLE_ITEM = registerItem("example_item", FireResistant)

Allows you to make an item that stores energy. This should mostly be used with other custom item behaviors, since there is no default implementation for consuming energy.

val EXAMPLE_ITEM = registerItem("example_item", Chargeable)

The above example uses the durability bar to display the item's charge. If you don't want this, you can disable this behavior:

val EXAMPLE_ITEM = registerItem("example_item", Chargeable(false))

The energy capacity can then be configured in the material config file:

configs/example_item.yml
max_energy: 100000
Using hardcoded material options (not recommended)

If you don't want your material options to be configurable or your specific use-case does not work well with configurable values, you can using the factory functions named after the material options interfaces.
For example, this is how you would create hardcoded ToolOptions:

Hardcoded ToolOptions
@OptIn(HardcodedMaterialOptions::class)
val toolOptions = ToolOptions(
    ToolLevel.STONE,
    ToolCategory.PICKAXE,
    breakSpeed = 12.0,
    attackDamage = 4.0,
    attackSpeed = 1.0,
    canSweepAttack = false,
    canBreakBlocksInCreative = false
)

Since hardcoding those values is strongly discouraged, you need to opt-in via the @OptIn(HardcodedMaterialOptions::class) annotation.

Custom Item Behaviors

There are of course a lot of cases that don't fit into any of the default item behaviors which is why you can easily make your own. Just create a new class and implement the ItemBehavior interface. Instead of registering event handlers, you can override the handle...() functions, which are invoked when something is done with an ItemStack of a NovaItem with that behavior.

fun getVanillaMaterialProperties

Gets a list of VanillaMaterialPropertys.
Vanilla material properties define what properties the item should have client-side. Based on the given properties, a corresponding vanilla material will be used. Nova will always try to find a vanilla material with the exact same properties as requested. If there is no such material, Nova might also choose a vanilla material with more vanilla material properties. If there is no material that has all requested properties, properties of low importance will be ignored.

These are the available vanilla material properties:

Property Name Effect
DAMAGEABLE The item has a durability bar.
FIRE_RESISTANT The item will not catch on fire.
CREATIVE_NON_BLOCK_BREAKING The item cannot break blocks in creative mode.
CONSUMABLE_NORMAL The item can be consumed normally.
CONSUMABLE_ALWAYS The item can always be consumed.
CONSUMABLE_FAST The item can be consumed fast, the eating process start without a delay.
HELMET The item can render a custom helmet texture.
CHESTPLATE The item can render a custom chestplate texture.
LEGGINGS The item can render a custom leggings texture.
BOOTS The item can render a custom boots texture.

fun getAttributeModifiers

Gets a list of AttributeModifiers.

Example Attribute Modifiers
override fun getAttributeModifiers(): List<AttributeModifier> =
    listOf(AttributeModifier(
        name = "Example Attribute Modifier (${novaMaterial.id}})", 
        attribute = Attributes.MOVEMENT_SPEED, 
        operation = Operation.MULTIPLY_TOTAL, 
        value = 0.1, 
        showInLore = true, 
        EquipmentSlot.MAINHAND 
    ))

fun getDefaultCompound

This function is used to specify default CBF data for all ItemStacks of NovaItems that use this ItemBehavior.
All default compounds from all ItemBehehaviors are merged together and always applied to new ItemStack of that type.

fun updatePacketItemData

This method is called every time a packet that includes an ItemStack of a material with this ItemBehavior is sent to a player.
Here, you can customize how the item is displayed for the player. Using the given PacketItemData, you can modify things like the display name, lore (normal and advanced tooltips), the durability bar and more.

Confused? Take a look at Understanding Packet Items.

ItemBehaviorHolder and ItemBehaviorFactory

ItemBehaviorHolder is a sealed interface with two implementations: ItemBehavior and ItemBehaviorFactory, where ItemBehaviorFactory creates ItemBehavior instances based on a NovaItem instance. This allows you to create factories for your ItemBehaviors that read from the item's config file.

Example custom ItemBehavior with ItemBehaviorFactory
class MyBehavior(value: Provider<Int>) : ItemBehavior {

   private val value by value 

   companion object : ItemBehaviorFactory<MyBehavior> {

      override fun create(item: NovaItem): MyBehavior {
         return MyBehavior(item.config.entry<Int>("value"))
      }

   }

}

Now, you could, for example, assign the same ItemBehaviorFactory to multiple items, while still accessing different configs.

@Init(stage = InitStage.PRE_PACK) 
object Items : ItemRegistry by ExampleAddon.registry {

    val EXAMPLE_ITEM_1 = registerItem("example_1", MyBehavior) // configs/example_1.yml
    val EXAMPLE_ITEM_2 = registerItem("example_2", MyBehavior) // configs/example_2.yml
    val EXAMPLE_ITEM_3 = registerItem("example_3", MyBehavior) // configs/example_3.yml


}

Item Data

Data for Nova's ItemStacks is stored in a NamespacedCompound, which serializes data using CBF.
You can retrieve the NamespacedCompound of an ItemStack by calling ItemStack.novaCompound.

NamespacedCompound

Unlike ItemStack.itemMeta, this NamespacedCompound is not a copy, so any changes you make to it will be reflected in the ItemStack.
However, the NamespacedCompound inside the ItemStack might be copied during normal tick logic, so you should not rely on the same (NMS) ItemStack to always contain the same NamespacedCompound instance.
For example, while modifying the ItemStack retrieved during an PlayerInteractEvent a few ticks later will still change the ItemStack in the world, modifying the NamespacedCompound you've retrieved during the event will not affect the ItemStack. Instead, you'll need to retreive the NovaCompound again.

Alternatively, you can also read and write data using ItemStack.storeData and ItemStack.retrieveData, which write data to the NamespacedCompound for you.

Inspecting Item Data

You can also run the command /nova debug itemData to take a look at the data of the item stack in your hand.
Some of this data might be of an unknown type and will be displayed in binary format. The type will be known after NamespacedCompound.get and NamespacedCompound.set calls.