hytale-plugin-basics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHytale Plugin Development Basics
Hytale服务器插件开发基础
Complete guide for creating Hytale server plugins following the official architecture patterns.
遵循官方架构模式创建Hytale服务器插件的完整指南。
When to use this skill
何时使用本技能
Use this skill when:
- Creating a new Hytale plugin from scratch
- Setting up plugin project structure
- Configuring plugin manifest (manifest.json)
- Understanding plugin lifecycle hooks
- Registering commands, events, or components
- Managing plugin dependencies
在以下场景使用本技能:
- 从零开始创建新的Hytale插件
- 搭建插件项目结构
- 配置插件清单(manifest.json)
- 理解插件生命周期钩子
- 注册命令、事件或组件
- 管理插件依赖项
Plugin Architecture Overview
插件架构概述
Hytale plugins are Java-based extensions that run on the server. They follow an ECS (Entity Component System) architecture with lifecycle management.
Hytale插件是运行在服务器上的Java扩展,遵循带有生命周期管理的ECS(实体组件系统)架构。
Plugin Types
插件类型
| Type | Location | Description |
|---|---|---|
| Core Plugins | Registered programmatically | Built-in server functionality |
| Builtin Plugins | | Shipped with server |
| External Plugins | | User-installed plugins |
| Early Plugins | | Bytecode transformers (advanced) |
| 类型 | 位置 | 描述 |
|---|---|---|
| 核心插件 | 以编程方式注册 | 服务器内置功能 |
| 内置插件 | | 随服务器一同发布 |
| 外部插件 | | 用户自行安装的插件 |
| 早期插件 | | 字节码转换器(高级功能) |
Plugin Lifecycle States
插件生命周期状态
NONE -> SETUP -> START -> ENABLED -> SHUTDOWN -> DISABLEDNONE -> SETUP -> START -> ENABLED -> SHUTDOWN -> DISABLEDProject Structure
项目结构
my-plugin/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/myplugin/
│ │ ├── MyPlugin.java
│ │ ├── commands/
│ │ ├── events/
│ │ ├── components/
│ │ └── systems/
│ └── resources/
│ ├── manifest.json
│ └── assets/ # Optional asset pack
│ └── Server/
│ └── Content/
├── build.gradle
└── settings.gradlemy-plugin/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/myplugin/
│ │ ├── MyPlugin.java
│ │ ├── commands/
│ │ ├── events/
│ │ ├── components/
│ │ └── systems/
│ └── resources/
│ ├── manifest.json
│ └── assets/ # 可选资源包
│ └── Server/
│ └── Content/
├── build.gradle
└── settings.gradlePlugin Manifest (manifest.json)
插件清单(manifest.json)
Required file in JAR root defining plugin metadata:
json
{
"Group": "com.example",
"Name": "MyPlugin",
"Version": "1.0.0",
"Description": "My awesome Hytale plugin",
"Authors": [
{
"Name": "Author Name",
"Email": "author@example.com",
"Url": "https://example.com"
}
],
"Website": "https://example.com/myplugin",
"Main": "com.example.myplugin.MyPlugin",
"ServerVersion": ">=1.0.0",
"Dependencies": {
"Hytale:NPCPlugin": ">=1.0.0"
},
"OptionalDependencies": {
"Hytale:TeleportPlugin": "*"
},
"LoadBefore": {
"Hytale:SomeOtherPlugin": "*"
},
"DisabledByDefault": false,
"IncludesAssetPack": true
}必填文件,位于JAR包根目录,用于定义插件元数据:
json
{
"Group": "com.example",
"Name": "MyPlugin",
"Version": "1.0.0",
"Description": "My awesome Hytale plugin",
"Authors": [
{
"Name": "Author Name",
"Email": "author@example.com",
"Url": "https://example.com"
}
],
"Website": "https://example.com/myplugin",
"Main": "com.example.myplugin.MyPlugin",
"ServerVersion": ">=1.0.0",
"Dependencies": {
"Hytale:NPCPlugin": ">=1.0.0"
},
"OptionalDependencies": {
"Hytale:TeleportPlugin": "*"
},
"LoadBefore": {
"Hytale:SomeOtherPlugin": "*"
},
"DisabledByDefault": false,
"IncludesAssetPack": true
}Manifest Fields
清单字段说明
| Field | Required | Description |
|---|---|---|
| No | Organization/namespace identifier |
| Yes | Plugin name (1-64 chars) |
| No | Semantic version string |
| No | Human-readable description |
| No | Array of author info objects |
| Yes | Fully qualified main class name |
| No | Required server version constraint |
| No | Required plugins with version constraints |
| No | Optional plugins with version constraints |
| No | Plugins this should load before |
| No | Start disabled (default: false) |
| No | Has embedded assets (default: false) |
| 字段 | 是否必填 | 描述 |
|---|---|---|
| 否 | 组织/命名空间标识符 |
| 是 | 插件名称(1-64个字符) |
| 否 | 语义化版本字符串 |
| 否 | 人类可读的插件描述 |
| 否 | 作者信息对象数组 |
| 是 | 完整限定的主类名称 |
| 否 | 所需的服务器版本约束 |
| 否 | 带版本约束的必填插件 |
| 否 | 带版本约束的可选插件 |
| 否 | 本插件需要优先加载的插件 |
| 否 | 默认是否禁用(默认值:false) |
| 否 | 是否包含嵌入式资源包(默认值:false) |
Main Plugin Class
主插件类
Extend and implement lifecycle methods:
JavaPluginjava
package com.example.myplugin;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import javax.annotation.Nonnull;
public class MyPlugin extends JavaPlugin {
// Required constructor
public MyPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
@Override
protected void setup() {
// Called after config load
// Register: commands, events, components, systems, codecs
getLogger().atInfo().log("MyPlugin setup complete!");
}
@Override
protected void start() {
// Called after all plugins complete setup
// Safe to interact with other plugins
getLogger().atInfo().log("MyPlugin started!");
}
@Override
protected void shutdown() {
// Called before disable (in reverse load order)
// Cleanup resources
getLogger().atInfo().log("MyPlugin shutting down!");
}
}继承并实现生命周期方法:
JavaPluginjava
package com.example.myplugin;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import javax.annotation.Nonnull;
public class MyPlugin extends JavaPlugin {
// 必填构造函数
public MyPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
@Override
protected void setup() {
// 配置加载完成后调用
// 注册:命令、事件、组件、系统、编解码器
getLogger().atInfo().log("MyPlugin setup complete!");
}
@Override
protected void start() {
// 所有插件完成setup后调用
// 可安全与其他插件交互
getLogger().atInfo().log("MyPlugin started!");
}
@Override
protected void shutdown() {
// 禁用前调用(按反向加载顺序)
// 清理资源
getLogger().atInfo().log("MyPlugin shutting down!");
}
}Available Registries
可用注册中心
Access through methods:
PluginBase| Registry | Method | Purpose |
|---|---|---|
| Commands | | Register slash commands |
| Events | | Register event listeners |
| Tasks | | Register async tasks |
| Block States | | Register block state types |
| Entities | | Register entity types |
| Client Features | | Register client features |
| Assets | | Register asset stores |
| Entity Components | | Register ECS components/systems |
| Chunk Components | | Register chunk components/systems |
| Codecs | | Register serializable types |
通过方法访问:
PluginBase| 注册中心 | 方法 | 用途 |
|---|---|---|
| 命令 | | 注册斜杠命令 |
| 事件 | | 注册事件监听器 |
| 任务 | | 注册异步任务 |
| 方块状态 | | 注册方块状态类型 |
| 实体 | | 注册实体类型 |
| 客户端功能 | | 注册客户端功能 |
| 资源 | | 注册资源存储 |
| 实体组件 | | 注册ECS组件/系统 |
| 区块组件 | | 注册区块组件/系统 |
| 编解码器 | | 注册可序列化类型 |
Command Registration
命令注册
java
@Override
protected void setup() {
getCommandRegistry().registerCommand(new MyCommand());
}
// Command class
public class MyCommand extends Command {
public MyCommand() {
super("mycommand", "My command description");
// Add arguments
addArg(EntityArg.player("target"));
addArg(IntArg.number("amount", 1, 100));
}
@Override
public void execute(CommandContext ctx) {
Player target = ctx.get("target");
int amount = ctx.get("amount");
ctx.sendMessage("Executed on " + target.getName() + " with " + amount);
}
}java
@Override
protected void setup() {
getCommandRegistry().registerCommand(new MyCommand());
}
// 命令类
public class MyCommand extends Command {
public MyCommand() {
super("mycommand", "My command description");
// 添加参数
addArg(EntityArg.player("target"));
addArg(IntArg.number("amount", 1, 100));
}
@Override
public void execute(CommandContext ctx) {
Player target = ctx.get("target");
int amount = ctx.get("amount");
ctx.sendMessage("Executed on " + target.getName() + " with " + amount);
}
}Event Registration
事件注册
java
@Override
protected void setup() {
// Global listener (all events of this type)
getEventRegistry().registerGlobal(PlayerConnectEvent.class, this::onPlayerConnect);
// Keyed listener (specific world/key)
getEventRegistry().register(AddPlayerToWorldEvent.class, "world_name", this::onPlayerAddToWorld);
// Priority listener
getEventRegistry().registerGlobal(EventPriority.FIRST, SomeEvent.class, this::onSomeEvent);
}
private void onPlayerConnect(PlayerConnectEvent event) {
getLogger().atInfo().log("Player connected: %s", event.getPlayer().getName());
}java
@Override
protected void setup() {
// 全局监听器(监听该类型的所有事件)
getEventRegistry().registerGlobal(PlayerConnectEvent.class, this::onPlayerConnect);
// 键控监听器(特定世界/键)
getEventRegistry().register(AddPlayerToWorldEvent.class, "world_name", this::onPlayerAddToWorld);
// 优先级监听器
getEventRegistry().registerGlobal(EventPriority.FIRST, SomeEvent.class, this::onSomeEvent);
}
private void onPlayerConnect(PlayerConnectEvent event) {
getLogger().atInfo().log("Player connected: %s", event.getPlayer().getName());
}Component Registration (ECS)
组件注册(ECS)
java
@Override
protected void setup() {
// Register a component type
ComponentType<EntityStore, MyComponent> myComponentType =
getEntityStoreRegistry().registerComponent(
MyComponent.class,
MyComponent::new
);
// Register with serialization codec
ComponentType<EntityStore, MyComponent> myComponentType =
getEntityStoreRegistry().registerComponent(
MyComponent.class,
"myComponentName",
MyComponent.CODEC
);
// Register a system
getEntityStoreRegistry().registerSystem(new MySystem());
}java
@Override
protected void setup() {
// 注册组件类型
ComponentType<EntityStore, MyComponent> myComponentType =
getEntityStoreRegistry().registerComponent(
MyComponent.class,
MyComponent::new
);
// 带序列化编解码器的注册
ComponentType<EntityStore, MyComponent> myComponentType =
getEntityStoreRegistry().registerComponent(
MyComponent.class,
"myComponentName",
MyComponent.CODEC
);
// 注册系统
getEntityStoreRegistry().registerSystem(new MySystem());
}Codec Registration
编解码器注册
java
@Override
protected void setup() {
// Register custom interaction type
getCodecRegistry(Interaction.CODEC)
.register("MyInteraction", MyInteraction.class, MyInteraction.CODEC);
// Register custom action type
getCodecRegistry(Action.CODEC)
.register("MyAction", MyAction.class, MyAction.CODEC);
}java
@Override
protected void setup() {
// 注册自定义交互类型
getCodecRegistry(Interaction.CODEC)
.register("MyInteraction", MyInteraction.class, MyInteraction.CODEC);
// 注册自定义动作类型
getCodecRegistry(Action.CODEC)
.register("MyAction", MyAction.class, MyAction.CODEC);
}Plugin Configuration
插件配置
Use for typed configuration:
withConfig()java
public class MyPlugin extends JavaPlugin {
private final Config<MyConfig> config;
public MyPlugin(@Nonnull JavaPluginInit init) {
super(init);
this.config = withConfig(MyConfig.CODEC);
}
@Override
protected void setup() {
MyConfig cfg = config.get();
getLogger().atInfo().log("Config value: %s", cfg.someValue());
}
}
// Config class
public record MyConfig(
String someValue,
int maxPlayers,
boolean enableFeature
) {
public static final BuilderCodec<MyConfig> CODEC = BuilderCodec.builder(
Codec.STRING.required().fieldOf("SomeValue"),
Codec.INT.required().fieldOf("MaxPlayers"),
Codec.BOOL.optionalFieldOf("EnableFeature", true)
).constructor(MyConfig::new);
}Config files are stored in .
config/{PluginGroup}.{PluginName}/config.json使用实现类型化配置:
withConfig()java
public class MyPlugin extends JavaPlugin {
private final Config<MyConfig> config;
public MyPlugin(@Nonnull JavaPluginInit init) {
super(init);
this.config = withConfig(MyConfig.CODEC);
}
@Override
protected void setup() {
MyConfig cfg = config.get();
getLogger().atInfo().log("Config value: %s", cfg.someValue());
}
}
// 配置类
public record MyConfig(
String someValue,
int maxPlayers,
boolean enableFeature
) {
public static final BuilderCodec<MyConfig> CODEC = BuilderCodec.builder(
Codec.STRING.required().fieldOf("SomeValue"),
Codec.INT.required().fieldOf("MaxPlayers"),
Codec.BOOL.optionalFieldOf("EnableFeature", true)
).constructor(MyConfig::new);
}配置文件存储在路径下。
config/{PluginGroup}.{PluginName}/config.jsonAccessing Server Resources
依赖注入模式
java
// Get the server instance
HytaleServer server = HytaleServer.get();
// Get the universe (world container)
Universe universe = server.getUniverse();
// Get a specific world
World world = universe.getWorld("world_name");
// Get the event bus
IEventBus eventBus = server.getEventBus();
// Get player by name
Optional<Player> player = server.getPlayerByName("PlayerName");
// Get asset registry
AssetRegistry assetRegistry = server.getAssetRegistry();安全访问其他插件:
java
@Override
protected void start() {
// 获取可选依赖
PluginBase teleportPlugin = getPluginManager()
.getPlugin(PluginIdentifier.of("Hytale", "TeleportPlugin"))
.orElse(null);
if (teleportPlugin != null) {
// 使用 teleport 插件功能
}
}Dependency Injection Pattern
Gradle构建配置
Access other plugins safely:
java
@Override
protected void start() {
// Get optional dependency
PluginBase teleportPlugin = getPluginManager()
.getPlugin(PluginIdentifier.of("Hytale", "TeleportPlugin"))
.orElse(null);
if (teleportPlugin != null) {
// Use teleport plugin features
}
}groovy
plugins {
id 'java'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
// 若可用,添加Hytale仓库
}
dependencies {
compileOnly 'com.hypixel.hytale:hytale-server-api:1.0.0'
}
jar {
from('src/main/resources') {
include 'manifest.json'
include 'assets/**'
}
}Gradle Build Configuration
最佳实践
—
生命周期管理
groovy
plugins {
id 'java'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
// Add Hytale repository if available
}
dependencies {
compileOnly 'com.hypixel.hytale:hytale-server-api:1.0.0'
}
jar {
from('src/main/resources') {
include 'manifest.json'
include 'assets/**'
}
}- setup():注册所有组件,绝不与游戏状态交互
- start():可安全与世界、玩家、其他插件交互
- shutdown():清理资源、保存数据、取消任务
Best Practices
错误处理
Lifecycle Management
—
- setup(): Register all components, never interact with game state
- start(): Safe to interact with world, players, other plugins
- shutdown(): Clean up resources, save data, cancel tasks
java
@Override
protected void setup() {
try {
getCommandRegistry().registerCommand(new MyCommand());
} catch (Exception e) {
getLogger().atSevere().withCause(e).log("Failed to register command");
}
}Error Handling
日志记录
java
@Override
protected void setup() {
try {
getCommandRegistry().registerCommand(new MyCommand());
} catch (Exception e) {
getLogger().atSevere().withCause(e).log("Failed to register command");
}
}Hytale服务器使用流式日志API:
java
// 使用内置的流式API日志器
getLogger().atInfo().log("Information message");
getLogger().atWarning().log("Warning message");
getLogger().atSevere().log("Error message"); // 或 atSevere().withCause(exception).log("Error message")
getLogger().atFine().log("Debug message");
// 带字符串格式化
getLogger().atInfo().log("Player %s connected", playerName);
// 带异常信息
getLogger().atSevere().withCause(exception).log("Failed to process request");注意:日志器不直接使用、、方法。请始终使用流式模式:。
.info().warn().error().atLevel().log("message")Logging
资源清理
The Hytale server uses a fluent logging API:
java
// Use the built-in logger with fluent API
getLogger().atInfo().log("Information message");
getLogger().atWarning().log("Warning message");
getLogger().atSevere().log("Error message"); // or atSevere().withCause(exception).log("Error message")
getLogger().atFine().log("Debug message");
// With string formatting
getLogger().atInfo().log("Player %s connected", playerName);
// With exception
getLogger().atSevere().withCause(exception).log("Failed to process request");Note: The logger does NOT use , , methods directly. Always use the fluent pattern: .
.info().warn().error().atLevel().log("message")通过插件注册中心完成的所有注册会在插件禁用时自动清理。对于自定义资源:
java
private ScheduledFuture<?> task;
@Override
protected void start() {
task = scheduler.scheduleAtFixedRate(this::doWork, 0, 1, TimeUnit.SECONDS);
}
@Override
protected void shutdown() {
if (task != null) {
task.cancel(false);
}
}Resource Cleanup
故障排除
—
插件无法加载
All registrations through plugin registries are automatically cleaned up when the plugin is disabled. For custom resources:
java
private ScheduledFuture<?> task;
@Override
protected void start() {
task = scheduler.scheduleAtFixedRate(this::doWork, 0, 1, TimeUnit.SECONDS);
}
@Override
protected void shutdown() {
if (task != null) {
task.cancel(false);
}
}- 检查是否位于JAR包根目录
manifest.json - 验证类路径是否正确
Main - 检查依赖版本是否不匹配
- 在服务器日志中查找异常信息
Troubleshooting
类未找到
Plugin Not Loading
—
- Check is in JAR root
manifest.json - Verify class path is correct
Main - Check for dependency version mismatches
- Look for exceptions in server logs
- 确保依赖项标记为
compileOnly - 通过/
Dependencies检查插件加载顺序LoadBefore - 验证JAR包包含所有必需的类
Class Not Found
事件未触发
- Ensure dependencies are marked
compileOnly - Check plugin load order via /
DependenciesLoadBefore - Verify JAR contains all required classes
- 确认注册操作在中执行
setup() - 检查事件键是否匹配(针对键控事件)
- 验证事件优先级顺序
- 确保事件未被取消
Events Not Firing
脚手架脚本
- Confirm registration happens in
setup() - Check event key matches (for keyed events)
- Verify event priority order
- Ensure event isn't being cancelled
使用提供的脚本快速创建包含完整目录结构和模板代码的新插件项目。
Scaffolding Scripts
Linux/macOS
Use the provided scripts to quickly create a new plugin project with the complete directory structure and boilerplate code.
bash
undefinedLinux/macOS
交互模式(提示输入所有参数)
bash
undefined./scripts/create-plugin.sh
Interactive mode (prompts for all values)
带参数执行
./scripts/create-plugin.sh
./scripts/create-plugin.sh <PluginName> [Group] [Version] [Author] [Description]
With arguments
示例
./scripts/create-plugin.sh <PluginName> [Group] [Version] [Author] [Description]
./scripts/create-plugin.sh MyAwesomePlugin com.mycompany 1.0.0 "John Doe" "A cool plugin"
undefinedExample
Windows
./scripts/create-plugin.sh MyAwesomePlugin com.mycompany 1.0.0 "John Doe" "A cool plugin"
undefinedbatch
:: 交互模式(提示输入所有参数)
scripts\create-plugin.bat
:: 带参数执行
scripts\create-plugin.bat <PluginName> [Group] [Version] [Author] [Description]
:: 示例
scripts\create-plugin.bat MyAwesomePlugin com.mycompany 1.0.0 "John Doe" "A cool plugin"Windows
生成的结构
batch
:: Interactive mode (prompts for all values)
scripts\create-plugin.bat
:: With arguments
scripts\create-plugin.bat <PluginName> [Group] [Version] [Author] [Description]
:: Example
scripts\create-plugin.bat MyAwesomePlugin com.mycompany 1.0.0 "John Doe" "A cool plugin"脚本会创建以下项目结构:
my-plugin/
├── src/main/java/com/example/myplugin/
│ ├── MyPlugin.java # 包含生命周期方法的主插件类
│ ├── commands/
│ │ └── ExampleCommand.java # 示例命令实现
│ ├── events/
│ │ └── PlayerEventHandler.java # 示例事件处理器
│ ├── components/ # ECS组件目录
│ └── systems/ # ECS系统目录
├── src/main/resources/
│ ├── manifest.json # 包含元数据的插件清单
│ └── assets/Server/Content/ # 资源包目录(可选)
├── build.gradle # Gradle构建配置(Java 21)
├── settings.gradle # Gradle项目设置
└── .gitignore # Git忽略规则Generated Structure
脚本参数
The scripts create the following project structure:
my-plugin/
├── src/main/java/com/example/myplugin/
│ ├── MyPlugin.java # Main plugin class with lifecycle methods
│ ├── commands/
│ │ └── ExampleCommand.java # Example command implementation
│ ├── events/
│ │ └── PlayerEventHandler.java # Example event handler
│ ├── components/ # Directory for ECS components
│ └── systems/ # Directory for ECS systems
├── src/main/resources/
│ ├── manifest.json # Plugin manifest with metadata
│ └── assets/Server/Content/ # Asset pack directory (optional)
├── build.gradle # Gradle build configuration (Java 21)
├── settings.gradle # Gradle project settings
└── .gitignore # Git ignore rules| 参数 | 是否必填 | 默认值 | 描述 |
|---|---|---|---|
| PluginName | 是 | - | 插件名称(1-64个字母数字字符,以字母开头) |
| Group | 否 | | Maven组/Java包前缀 |
| Version | 否 | | 语义化版本字符串 |
| Author | 否 | | 插件作者名称 |
| Description | 否 | (空) | 人类可读的插件描述 |
如需详细的生命周期文档,请查看。
如需高级注册模式文档,请查看。
references/plugin-lifecycle.mdreferences/registry-patterns.mdScript Parameters
—
| Parameter | Required | Default | Description |
|---|---|---|---|
| PluginName | Yes | - | Name of the plugin (1-64 alphanumeric chars, starts with letter) |
| Group | No | | Maven group/Java package prefix |
| Version | No | | Semantic version string |
| Author | No | | Plugin author name |
| Description | No | (empty) | Human-readable plugin description |
See for detailed lifecycle documentation.
See for advanced registration patterns.
references/plugin-lifecycle.mdreferences/registry-patterns.md—