Update script to write to vendor/hytale-server
This commit is contained in:
351
docs/02-plugin-development.md
Normal file
351
docs/02-plugin-development.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# 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<MyConfig> 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<MyConfig> 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<MyConfig> 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
|
||||
Reference in New Issue
Block a user