Skip to content

Instantly share code, notes, and snippets.

@Machine-Maker
Last active January 3, 2024 15:35
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Machine-Maker/2901c790219862ef1ad6070b8872a889 to your computer and use it in GitHub Desktop.
Save Machine-Maker/2901c790219862ef1ad6070b8872a889 to your computer and use it in GitHub Desktop.
Registry Modification

Registry Modification API

The Registry Modification API is a framework for adding future API which easily allows mutating or adding entries into both the built-in registries and the data-driven registries.

There are several definitions to cover first:

Definitions

Built-in Registry: A registry that is unmodifiable by datapacks. They are created/loaded via static initializers in classes like Blocks, Items, GameEvent, and many more.

Data-driven Registry: A registry which is loaded entirely from .json files within the vanilla jar. Code in the source exists to create the objects, but it is only used for the data generator functionality of the vanilla jars (used to create those .json files).

Writable Registry: A writable registry is a registry which is in a state to be accepting new entries. This relates to the next definition.

Freeze: In the context of registries, freezing means the registry cannot change anymore. After the initial load of entries via static initializers (for built-in registries) or .json files (for data-driven registries), registries are frozen preventing further changes.

API

All API surrounding registry modification takes place in a plugin bootstrap context. This means that much of the rest of the Bukkit/Paper API will not be available for use as it hasn't been initialized yet.

Keys

There will be a bunch of ...Keys utility classes containing all fields of type TypedKey<T>. These keys represent vanilla game objects that are in a Registry. These fields will always be an exact representation of what vanilla keys exist for that version of Minecraft. They may change on every minor version bump as Mojang frequently adds, renames, or removes these. Why do we need these if I can just get the key for a GameEvent from GameEvent.BLOCK_OPEN.getKey()? Well, when these registry events are ran, the vanilla Registry has not been created yet. In order for the fields inside the API's GameEvent to be filled when the class is statically initialized, the vanilla registry must have been fully created and frozen. So we cannot access the keys via those fields because those fields cannot exist yet. The fields in these ...Keys utility classes are the solution to this problem.

Each field is of type TypedKey<T> which the generic parameter being the API type, like GameEvent. TypeKey<T> will be used to specify what object you want to modify or what the key of a newly created object will be.

Events

There are 2 events used for either modification of an entry, or adding completely new entries. Support for modification or addition of entries is not universal, some registries may only support modification not addition, some both, some none at all. These events do not use the existing event system, but a separate Lifecycle Event System.

The registry addition event is handled right before an entry is about to be registered allowing modification of the entry. You check which object is being added to the registry by comparing the TypedKey<T>s in order to modify only specific objects.

The registry pre-freeze event is handled right before a registry is frozen. It provides a WritableRegistryView which allows adding of new entries (which calls the addition event). You can provide a new TypedKey<T> in order to create a new object.

In both of these events, interaction with the value is done through a builder pattern as it is the best way to keep existing immutable API types, like GameEvent while allowing modification to an almost-built GameEvent.

RegistryAccess

Currently in the API you can access instances of Registry via static constants on org.bukkit.Registry. Going forward, any data-driven registries that exist there will be deprecated and the new way of obtaining them is via RegistryAccess to which you pass a RegistryKey for the specific registry you want to obtain. All builtin or data-driven registry instances will be available through the RegistryAccess going forward, but only new built-in registries will have constants added to org.bukkit.Registry.

This is being done because data-driven registries do not exist when the org.bukkit.Registry class is loaded so the fields cannot properly be filled.

Examples

This example is modifying a specific GameEvent to have a larger radius and is adding a new GameEvent.

static final TypedKey<GameEvent> NEW_EVENT = GameEventKeys.create(Key.key("machine_maker", "best_event"));

@Override
public void bootstrap(@NotNull BootstrapContext context) {
    final LifecycleEventManager<BootstrapContext> lifecycles = context.getLifecycleManager();

    // registers a new handler for the prefreeze event for the game event registry
    lifecycles.registerEventHandler(RegistryEvents.GAME_EVENT.preFreeze().newHandler(event -> {
        // the RegistryView provided here is writable so you can register new objects
        event.registry().register(NEW_EVENT, builder -> {
            builder.range(2);
        });
    }));
    
    // registers a handler for the addition event
    lifecycles.registerEventHandler(RegistryEvents.GAME_EVENT.newAdditionHandler(event -> {
        // checks if the object being registered is the block open game event
        if (event.key().equals(GameEventKeys.BLOCK_OPEN)) {
            // multiplies the range by 2
            event.builder().range(event.builder().range() * 2);
        }
    }));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment