# Plugin Development ## Plugin Structure A Hytale plugin consists of: 1. A main class extending `JavaPlugin` 2. A `manifest.json` file with plugin metadata 3. Optional asset packs for client-side resources ## The Plugin Manifest The `manifest.json` file must be placed at the root of your JAR: ```json { "Group": "com.example", "Name": "MyPlugin", "Version": "1.0.0", "Description": "A description of what this plugin does", "Authors": ["Author1", "Author2"], "Website": "https://example.com", "Main": "com.example.MyPlugin", "ServerVersion": ">=1.0.0", "Dependencies": { "com.other:OtherPlugin": ">=2.0.0" }, "OptionalDependencies": { "com.soft:SoftDep": ">=1.0.0" }, "LoadBefore": ["com.load:BeforeThis"], "DisabledByDefault": false, "IncludesAssetPack": true } ``` ### Manifest Fields | Field | Type | Required | Description | |------------------------|----------|----------|--------------------------------------------------| | `Group` | String | Yes | Maven-style group ID (e.g., `com.example`) | | `Name` | String | Yes | Plugin name (unique identifier within group) | | `Version` | String | Yes | Semantic version (e.g., `1.0.0`) | | `Description` | String | No | Brief description of the plugin | | `Authors` | String[] | No | List of author names | | `Website` | String | No | Plugin website or repository URL | | `Main` | String | Yes | Fully qualified main class name | | `ServerVersion` | String | No | Required server version range | | `Dependencies` | Object | No | Required plugin dependencies with version ranges | | `OptionalDependencies` | Object | No | Optional plugin dependencies | | `LoadBefore` | String[] | No | Plugins that should load after this one | | `DisabledByDefault` | Boolean | No | If true, plugin must be explicitly enabled | | `IncludesAssetPack` | Boolean | No | If true, plugin includes client assets | ### Plugin Identifier Plugins are identified by `Group:Name` format (e.g., `com.example:MyPlugin`). This identifier is used for: - Dependency resolution - Permission namespacing (`com.example.myplugin.*`) - Configuration keys ## The JavaPlugin Class ```java package com.example; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.java.JavaPluginInit; public class MyPlugin extends JavaPlugin { private Config config; public MyPlugin(JavaPluginInit init) { super(init); // Initialize config BEFORE setup() is called this.config = withConfig(MyConfig.CODEC); } @Override protected void setup() { // Register all components during setup registerCommands(); registerEvents(); registerEntities(); } @Override protected void start() { // Plugin is now active // Access other plugins, start services, etc. } @Override protected void shutdown() { // Clean up resources // Save data, close connections, etc. } private void registerCommands() { getCommandRegistry().registerCommand(new MyCommand()); } private void registerEvents() { getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect); } private void registerEntities() { getEntityRegistry().register("customEntity", CustomEntity.class, CustomEntity::new); } private void onPlayerConnect(PlayerConnectEvent event) { getLogger().info("Player connected: " + event.getPlayer().getName()); } } ``` ## Plugin Lifecycle States ``` NONE -> SETUP -> START -> ENABLED -> SHUTDOWN -> DISABLED ``` | State | Description | |------------|--------------------------------------------------| | `NONE` | Initial state before any lifecycle methods | | `SETUP` | `setup()` is executing; register components here | | `START` | `start()` is executing; plugin becoming active | | `ENABLED` | Plugin is fully operational and handling events | | `SHUTDOWN` | `shutdown()` is executing; cleanup in progress | | `DISABLED` | Plugin is fully disabled and unloaded | ## Configuration ### Defining a Configuration Class ```java public class MyConfig { public static final Codec CODEC = BuilderCodec.of(MyConfig::new) .with("enabled", Codec.BOOLEAN, c -> c.enabled, true) .with("maxPlayers", Codec.INTEGER, c -> c.maxPlayers, 100) .with("welcomeMessage", Codec.STRING, c -> c.welcomeMessage, "Welcome!") .build(); public final boolean enabled; public final int maxPlayers; public final String welcomeMessage; private MyConfig(boolean enabled, int maxPlayers, String welcomeMessage) { this.enabled = enabled; this.maxPlayers = maxPlayers; this.welcomeMessage = welcomeMessage; } } ``` ### Using Configuration ```java public class MyPlugin extends JavaPlugin { private Config config; public MyPlugin(JavaPluginInit init) { super(init); this.config = withConfig(MyConfig.CODEC); } @Override protected void setup() { MyConfig cfg = config.get(); if (cfg.enabled) { getLogger().info("Max players: " + cfg.maxPlayers); } } } ``` Configuration is stored in the server's config under your plugin identifier. ## Available Registries ### CommandRegistry Register custom commands: ```java getCommandRegistry().registerCommand(new MyCommand()); ``` See [Command System](04-command-system.md) for details. ### EventRegistry Register event listeners: ```java // Simple registration getEventRegistry().register(PlayerConnectEvent.class, this::onConnect); // With priority getEventRegistry().register(EventPriority.EARLY, PlayerConnectEvent.class, this::onConnect); // Global listener (all events of type) getEventRegistry().registerGlobal(EntityEvent.class, this::onAnyEntity); // Async event getEventRegistry().registerAsync(AsyncEvent.class, this::onAsync); ``` See [Event System](03-event-system.md) for details. ### EntityRegistry Register custom entity types: ```java getEntityRegistry().register("myEntity", MyEntity.class, MyEntity::new); // With serialization codec getEntityRegistry().register("myEntity", MyEntity.class, MyEntity::new, MyEntity.CODEC); ``` ### BlockStateRegistry Register custom block states: ```java getBlockStateRegistry().register("myBlock", MyBlockState.class); ``` ### TaskRegistry Schedule recurring tasks: ```java getTaskRegistry().register(new MyTask()); ``` ### AssetRegistry Register custom asset types: ```java getAssetRegistry().register(MyAsset.class, MyAsset.CODEC); ``` ### ClientFeatureRegistry Register client-side features: ```java getClientFeatureRegistry().register("myFeature", MyFeature.class); ``` ## Permissions Plugins automatically receive a base permission derived from their identifier: ``` {group}.{name} -> com.example.myplugin ``` Commands registered by your plugin will have permissions under: ``` {basePermission}.command.{commandName} ``` For example, if your plugin is `com.example:MyPlugin` and you register a command `spawn`: - Base permission: `com.example.myplugin` - Command permission: `com.example.myplugin.command.spawn` ## Logging Use the built-in logger: ```java getLogger().info("Information message"); getLogger().warn("Warning message"); getLogger().error("Error message"); getLogger().debug("Debug message"); ``` ## Dependencies ### Hard Dependencies Hard dependencies must be present for your plugin to load: ```json { "Dependencies": { "com.example:RequiredPlugin": ">=1.0.0" } } ``` ### Optional Dependencies Optional dependencies are loaded if present but not required: ```json { "OptionalDependencies": { "com.example:OptionalPlugin": ">=1.0.0" } } ``` Check if optional dependency is loaded: ```java @Override protected void setup() { if (getPluginManager().isPluginLoaded("com.example:OptionalPlugin")) { // Optional plugin is available } } ``` ### Load Order Use `LoadBefore` to ensure specific plugins load after yours: ```json { "LoadBefore": ["com.example:LoadAfterMe"] } ``` ## Asset Packs If your plugin includes client-side assets, set `IncludesAssetPack` to `true`: ```json { "IncludesAssetPack": true } ``` Place assets in your JAR under the `assets/` directory following Hytale's asset structure. ## Best Practices 1. **Register everything in `setup()`** - Commands, events, entities should all be registered during setup 2. **Use `start()` for initialization logic** - Access other plugins, connect to databases, etc. 3. **Clean up in `shutdown()`** - Close connections, save data, cancel tasks 4. **Use configuration** - Make your plugin configurable instead of hardcoding values 5. **Handle errors gracefully** - Log errors but don't crash the server 6. **Respect the lifecycle** - Don't access other plugins during setup, wait for start() 7. **Use meaningful permissions** - Follow the automatic permission naming convention