Skip to content

0.17 ➝ 0.18

Minecraft version

Updated to 1.21.4

Addons are now loaded as Paper plugins

Addons are now loaded as Paper plugins. This includes some changes to the build setup, so please check out the updated Nova-Addon-Template.

This also means that id and name are no longer separate. Instead, the lowercase name of the plugin is now also the id. Please verify that this does not result in a different id than before.

The nova gradle plugin will automatically generate a paper-plugin.yml, as well as loader- bootstrapper- and plugin main class for you. You can also create your own loader- bootstrapper- and main classes. If you do so, please define them in the configuration of the nova gradle plugin, and NOT in the paper-plugin.yml, as the gradle plugin needs to inject some code into them. Also note that defining a custom loader will disable the library loading functionality via the libraryLoader dependency configuration.

Nova will automatically move configs, recipes, and other data from plugins/Nova/... to the data folder of your plugin.

Early initialization

All PRE_WORLD initialization phases are now earlier (during Bootstrap phase). At this time, most Bukkit classes are not usable yet, as they depend on a not yet existing MinecraftServer instance.

Common exceptions that may indicate that you're trying to access things too early are:

  • java.lang.IllegalStateException: Trying to access unbound value ...
  • Caused by: java.lang.NullPointerException: Cannot invoke ... because "org.bukkit.Bukkit.server" is null

Item models

With 1.21.4, Mojang has introduced Item model definitions. The existing model selection functionality of items and blocks has changed to accommodate this new functionality. Items can no longer have multiple (named) models, as this is now achievable through the new custom model data component.

See Item Model Definitions for a full tutorial on Nova's item model definition builder.

Basically,

models {
    selectModel {
        getModel("...")
    }
}

becomes

modelDefinition {
    model = buildModel { 
        getModel("...")
    }
}

The item model definition builder also offers numberedModels and rangedModels functions as replacements for selectModel(IntRange, ...). These can then be accessed via custom model data.

Block models

Block model selection has been simplified a bit and is no longer as nested as before:

stateBacked(BackingStateCategory.NOTE_BLOCK, BackingStateCategory.MUSHROOM_BLOCK) {
    getModel("...")
}
models {
    stateBacked(BackingStateCategory.NOTE_BLOCK, BackingStateCategory.MUSHROOM_BLOCK)
    selectModel {
        getModel("...")
    }
}

(same for entityBacked)

Item model definitions for block models

You can also use item model definitions for block models via entityItemBacked. This allows you to use minecraft:special models, such as chests or signs for blocks.

Default item/block behavior factory companion objects

The companion objects of the default item/block behaviors that allow configuration are no longer item/block behavior factories. This was done to merge the "hardcoded" and "configurable" ways to create instances of these item behaviors.

Basically, this means that behaviors(Tool, Damageable, Enchantable) becomes behaviors(Tool(), Damageable(), Enchantable(). If you don't change any of the default parameters, all values will always be read from the config. If you specify any parameter, a config entry will not be necessary, but existing config entries will still override the hardcoded value.

Equipment

In 1.21.2, Mojang introduced custom equipment. Following this naming, ArmorRegistry is now EquipmentRegistry and offers new functionality, such as custom armor for non-player entities.
Animated armor now also works for all clients, including those running shader mods.

@Init(stage = InitStage.PRE_PACK)
object Equipment : EquipmentRegistry by Machines.registry {

    val STAR = equipment("star") {
        humanoid {
            layer {
                texture("star")
                emissivityMap("star_emissivity_map")
            }
        }
        humanoidLeggings { // (1)!
            layer {
                texture("star")
                emissivityMap("star_emissivity_map")
            }
        }
    }

}
  1. Minecraft separates humanoid and humanoid_leggins into two categories, I don't know why either.
@Init(stage = InitStage.PRE_PACK)
object Armor : ArmorRegistry by Machines.registry {

    val STAR = armor("star") {
        texture {
            texture("armor/star_layer_1", "armor/star_layer_2")
            emissivityMap("armor/star_layer_1_emissivity_map", "armor/star_layer_2_emissivity_map")
        }
    }

}

Wearable is now named Equippable

Gui textures

The gui texture builder has been simplified:

val RECIPE_CRAFTING = guiTexture("recipe_crafting") {
    path("gui/recipe/crafting")
}
val RECIPE_CRAFTING = guiTexture("recipe_crafting") {
    texture {
        path("gui/recipe/crafting")
    }
}

Providers

Providers have been reworked. They are now thread-safe and lazy both up- and downstream.

  • The packages xyz.xenondevs.commons.provider.immutable and xyz.xenondevs.commons.provider.mutable have been collapsed into the parent package xyz.xenondevs.commons.provider.
  • Provider#update has been removed. Similar functionality can be replicated by creating a mutable provider and calling set on it.
  • Extension functions for collection-mapping functionality, such as flatMap, have been renamed (to flatMapCollection) in order to make room for flatMap((T) -> Provider<R>).
  • Provider.orElse(MutableProvider) has been removed as it was not compatible with lazy upstream propagation (Provider.orElse(Provider) still exists).
  • Nova's ConfigProvider no longer exists. Instead, equivalent extension functions are now available on Provider<ConfigurationNode>.

InvUI 2.0.0-alpha.1

InvUI has been updated to a pre-release version of 2.0.0.

See the GitHub release page for a list of breaking changes and new features.

Reactive API

InvUI now offers an experimental reactive API in the invui-kotlin module. This includes extension functions for Item.Builder#setItemProvider, PagedGui#setContent, ..., that accept a Provider. The item/gui/window will be automatically updated when the provider's value changes, removing the need for manual notifyWindows calls.

Reactive API example

Since the InvUI documentation is not updated yet, here is an example on how to use the reactive API:

val num: MutableProvider<Int> = mutableProvider(0) // stores the number of clicks on the '#' item
val query: MutableProvider<String> = mutableProvider("") // stores the anvil window text

fun example(player: Player) {
    // example item that shows click count
    Structure.addGlobalIngredient('#', Item.builder()
        .setItemProvider(num) { num -> ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setName("Count: $num") } // item updates based on num provider
        .addClickHandler { _, _ -> num.set(num.get() + 1) }) // click increments num

    // page buttons
    Structure.addGlobalIngredient('>', BoundItem.pagedGui()
        .setItemProvider { _, gui -> ItemBuilder(Material.ARROW).setName("Go to page ${gui.page + 2}/${gui.pageAmount}") }
        .addClickHandler { _, gui, click -> if (click.clickType == ClickType.LEFT) gui.page++ })
    Structure.addGlobalIngredient('<', BoundItem.pagedGui()
        .setItemProvider { _, gui -> ItemBuilder(Material.ARROW).setName("Go to page ${gui.page}/${gui.pageAmount}") }
        .addClickHandler { _, gui, click -> if (click.clickType == ClickType.LEFT) gui.page-- })

    // marker
    Structure.addGlobalIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL)

    AnvilWindow.split()
        .setTitle("InvUI")
        .addRenameHandler(query) // automatically writes text into query provider
        .setUpperGui(
            Gui.normal()
                .setStructure("###")
                .addIngredient('#', Item.simple(ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setName(""))) // this item does nothing
        )
        .setLowerGui(PagedGui.items()
            .setStructure(
                "x x x x x x x x x",
                "x x x x x x x x x",
                "x x x x x x x x x",
                "# # < # # # > # #")
            .setContent(query) { query -> // content updates based on query provider
                Material.entries
                    .filter { !it.isLegacy && it.isItem && it.name.contains(query, true) }
                    .map { Item.simple(ItemStack(it)) }
            })
        .open(player)
}

And this is what it looks like:

ResourceType in ResourcePath

ResourcePath now includes a ResourceType parameter to specify the type (and path / extension) of the resource.

Other changes

  • All usages of net.minecraft.resources.ResourceLocation have been placed with net.kyori.adventure.key.Key