Loading...
Loading...
Create and structure Hytale server plugins with proper lifecycle, manifest, dependencies, and registries. Use when asked to "create a Hytale plugin", "make a Hytale mod", "start a new Hytale plugin", "setup plugin structure", or "write plugin boilerplate".
npx skill4agent add mnkyarts/hytale-skills hytale-plugin-basics| 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) |
NONE -> SETUP -> START -> ENABLED -> SHUTDOWN -> DISABLEDmy-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.gradle{
"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
}| 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) |
JavaPluginpackage 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!");
}
}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 |
@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);
}
}@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());
}@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());
}@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);
}withConfig()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/{PluginGroup}.{PluginName}/config.json// 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();@Override
protected void start() {
// Get optional dependency
PluginBase teleportPlugin = getPluginManager()
.getPlugin(PluginIdentifier.of("Hytale", "TeleportPlugin"))
.orElse(null);
if (teleportPlugin != null) {
// Use teleport plugin features
}
}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/**'
}
}@Override
protected void setup() {
try {
getCommandRegistry().registerCommand(new MyCommand());
} catch (Exception e) {
getLogger().atSevere().withCause(e).log("Failed to register command");
}
}// 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");.info().warn().error().atLevel().log("message")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);
}
}manifest.jsonMaincompileOnlyDependenciesLoadBeforesetup()# Interactive mode (prompts for all values)
./scripts/create-plugin.sh
# With arguments
./scripts/create-plugin.sh <PluginName> [Group] [Version] [Author] [Description]
# Example
./scripts/create-plugin.sh MyAwesomePlugin com.mycompany 1.0.0 "John Doe" "A cool plugin":: 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 # 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| 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 |
references/plugin-lifecycle.mdreferences/registry-patterns.md