Creating Registries
Creating Registries
Section titled “Creating Registries”All custom content in Hytale is registered through the registry system during your plugin’s setup() phase. This page covers how registration works, what happens during the lifecycle, and how cleanup is handled automatically.
Package Location
Section titled “Package Location”- Registry base:
com.hypixel.hytale.registry.Registry - Registration:
com.hypixel.hytale.registry.Registration - Plugin base:
com.hypixel.hytale.server.core.plugin.PluginBase - Codec map registry:
com.hypixel.hytale.server.core.plugin.registry.CodecMapRegistry
Registration Lifecycle
Section titled “Registration Lifecycle”All registrations follow the same lifecycle pattern: register during setup(), use during runtime, and automatic cleanup on shutdown.
flowchart TD
A["Plugin setup()"] -->|"register content"| B["Registry.register()"]
B --> C{"Precondition<br/>check"}
C -->|"pass"| D["Wrap registration"]
C -->|"fail"| E["IllegalStateException"]
D --> F["Add to registrations list"]
F --> G["Content active"]
G -->|"plugin shutdown"| H["shutdownAndCleanup()"]
H -->|"LIFO order"| I["Unregister each entry"]
I --> J["Clear registrations"]
Preconditions
Section titled “Preconditions”Every plugin registry is constructed with a precondition that checks whether the plugin is in a valid state. Attempting to register content when the plugin is in NONE or DISABLED state throws an IllegalStateException:
import com.hypixel.hytale.server.core.plugin.PluginBase;
public class MyPlugin extends PluginBase { @Override protected void setup() { // safe -- plugin is in SETUP state getCommandRegistry().registerCommand(new MyCommand()); }}If a registry is not enabled (e.g., after shutdown), the register() method calls unregister() on the registration immediately and throws.
Wrapped Registrations
Section titled “Wrapped Registrations”When you call register(), the registry wraps your registration with two callbacks:
- isEnabled — returns
trueas long as the registry is enabled or the registration itself is still registered - unregister — removes the registration from the registry’s internal list and calls the original
unregister()callback
This wrapping means you can call unregister() on a returned Registration at any time to manually remove it before shutdown:
import com.hypixel.hytale.registry.Registration;
@Overrideprotected void setup() { Registration reg = getEventRegistry().register(SomeEvent.class, this::onEvent);
// later, if needed reg.unregister();}Registering Content
Section titled “Registering Content”Commands
Section titled “Commands”import com.hypixel.hytale.server.core.command.AbstractCommand;
@Overrideprotected void setup() { getCommandRegistry().registerCommand(new MyCommand());}Events
Section titled “Events”import com.hypixel.hytale.event.EventPriority;import com.hypixel.hytale.server.core.event.PlayerConnectEvent;
@Overrideprotected void setup() { getEventRegistry().register(PlayerConnectEvent.class, this::onConnect); getEventRegistry().register(EventPriority.LATE, PlayerConnectEvent.class, this::onConnectLate);}ECS Components and Systems
Section titled “ECS Components and Systems”import com.hypixel.hytale.component.ComponentType;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
@Overrideprotected void setup() { ComponentType<EntityStore, MyComponent> type = getEntityStoreRegistry().registerComponent(MyComponent.class, MyComponent::new);
ComponentType<EntityStore, SavedComponent> savedType = getEntityStoreRegistry().registerComponent( SavedComponent.class, "my_saved", SavedComponent.CODEC);
getEntityStoreRegistry().registerSystem(new MySystem());}Store references to ComponentType and ResourceType — you need them later to access component data on entities.
Codec Map Entries
Section titled “Codec Map Entries”For content that plugs into existing serialization maps (e.g., adding a new entity effect type, interaction type, or tag pattern):
import com.hypixel.hytale.server.core.plugin.registry.CodecMapRegistry;
@Overrideprotected void setup() { getCodecRegistry(SomeAsset.CODEC_MAP) .register("my_custom_type", MyCustomType.class, MyCustomType.CODEC);}CodecMapRegistry acquires the global asset write lock when unregistering entries, so cleanup is thread-safe even if assets are being loaded concurrently.
import java.util.concurrent.CompletableFuture;
@Overrideprotected void setup() { CompletableFuture<Void> task = CompletableFuture.runAsync(() -> { // background work }); getTaskRegistry().registerTask(task);}Custom Asset Stores
Section titled “Custom Asset Stores”To create a new asset type that gets loaded from JSON files in asset packs, build a HytaleAssetStore and register it with getAssetRegistry().
Asset Class
Section titled “Asset Class”Your asset class must implement JsonAssetWithMap:
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;import com.hypixel.hytale.assetstore.DefaultAssetMap;import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec;import com.hypixel.hytale.codec.Codec;import com.hypixel.hytale.codec.KeyedCodec;
public class SpawnTableAsset implements JsonAssetWithMap<String, DefaultAssetMap<String, SpawnTableAsset>> { public String[] entries; public float weight;
public static final AssetBuilderCodec<String, SpawnTableAsset> CODEC = AssetBuilderCodec.builder(SpawnTableAsset.class, SpawnTableAsset::new) .append( new KeyedCodec<>("Entries", Codec.STRING_ARRAY), (obj, entries) -> obj.entries = entries, obj -> obj.entries ) .add()
.append( new KeyedCodec<>("Weight", Codec.FLOAT), (obj, weight) -> obj.weight = weight, obj -> obj.weight ) .add()
.build();}Registering the Asset Store
Section titled “Registering the Asset Store”Use HytaleAssetStore.builder() to configure the store, then register it during setup():
import com.hypixel.hytale.assetstore.DefaultAssetMap;import com.hypixel.hytale.server.core.asset.HytaleAssetStore;
@Overrideprotected void setup() { getAssetRegistry().register( HytaleAssetStore.builder(SpawnTableAsset.class, new DefaultAssetMap<>()) .setPath("MyMod/SpawnTables") .setCodec(SpawnTableAsset.CODEC) .setKeyFunction(SpawnTableAsset::getId) .loadsAfter(Item.class, BlockType.class) .build() );}Assets are loaded from Server/MyMod/SpawnTables/ in each asset pack. The store is automatically unregistered when the plugin shuts down.
Builder Methods
Section titled “Builder Methods”| Method | Description |
|---|---|
setPath(String) | Directory path under Server/ where JSON files are loaded from |
setCodec(AssetCodec) | Codec used to deserialize asset JSON files |
setKeyFunction(Function) | Extracts the key (usually string ID) from a decoded asset |
setExtension(String) | File extension to match (default: ".json") |
loadsAfter(Class...) | Declare asset types that must load before this store |
loadsBefore(Class...) | Declare asset types that must load after this store |
preLoadAssets(List) | Pre-add default assets before directory loading |
setReplaceOnRemove(Function) | Provide a replacement asset when one is deleted |
unmodifiable() | Make the asset map read-only after loading |
setPacketGenerator(AssetPacketGenerator) | Generate network packets for client sync |
Automatic Cleanup
Section titled “Automatic Cleanup”When your plugin shuts down, all registries share a common shutdownTasks list in PluginBase. The cleanup iterates this list in reverse order, ensuring that content registered last is unregistered first. This respects dependency ordering — if system B depends on component A, and A was registered before B, then B is unregistered first.
You do not need to manually unregister:
- Event listeners
- Commands
- ECS components, resources, and systems
- Tasks (automatically cancelled)
- Codec registrations
- Asset stores
ComponentRegistry Cleanup
Section titled “ComponentRegistry Cleanup”For ECS content specifically, ComponentRegistry has additional cleanup behavior. When a component is unregistered, any system whose query requires that component is also automatically unregistered. This prevents systems from running against missing data.
Serializable Registrations and Codecs
Section titled “Serializable Registrations and Codecs”When registering ECS components or resources that need persistence (saving/loading with worlds), provide a string ID and a BuilderCodec:
import com.hypixel.hytale.codec.builder.BuilderCodec;import com.hypixel.hytale.component.ComponentType;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
@Overrideprotected void setup() { ComponentType<EntityStore, MyData> dataType = getEntityStoreRegistry().registerComponent( MyData.class, "My_data", MyData.CODEC);}The string ID is used as the serialization key in stored documents. It must be unique within the component registry. The codec defines how the component is serialized to and deserialized from BSON.
For a detailed guide on building codecs, see the Serialization section.
Best Practices
Section titled “Best Practices”- Register in
setup()— perform all registrations in thesetup()method, never instart()or event handlers - Store type references — keep references to
ComponentTypeandResourceTypefor runtime access - Let cleanup happen automatically — do not manually unregister unless you have a specific runtime reason
- Use appropriate event priorities — choose priorities based on whether you need to modify or observe events
- Provide codecs for persistent data — any component or resource that should survive server restarts needs a codec and string ID
Related
Section titled “Related”- Registry System Overview - Architecture and load order
- Existing Registries - Complete catalog of all registries
- Serialization - Building codecs for serializable content
- Plugin Lifecycle - Setup and shutdown phases
- ECS Overview - Component and system registration