ZacxDev

hytale-mod

by @ZacxDev in Tools
1
0
# Install this skill:
npx skills add ZacxDev/claude-hytale-mod --skill "hytale-mod"

Install specific skill from multi-skill repository

# Description

Hytale server plugin and modding development. Use for Java plugins, commands, events, Custom UI, asset packs, items, weapons, and animations.

# SKILL.md


name: hytale-mod
description: Hytale server plugin and modding development. Use for Java plugins, commands, events, Custom UI, asset packs, items, weapons, and animations.
allowed-tools: [Read, Grep, Glob, Bash, Edit, Write, Task]
argument-hint: "[task description]"


Hytale Modding Skill

Prime context for Hytale server plugin development and modding (Early Access, Dec 2025).

Environment Requirements

  • Java 25 (required by Hytale server)
  • Gradle Groovy DSL only β€” Kotlin DSL (.gradle.kts) fails with JDK 25
  • Maven coordinates: com.hypixel.hytale:Server:2026.01.28-87d03be09

Project Structure

plugin-name/
β”œβ”€β”€ build.gradle              # Groovy DSL, shadowJar
β”œβ”€β”€ src/main/
β”‚   β”œβ”€β”€ java/                 # Plugin code
β”‚   └── resources/
β”‚       β”œβ”€β”€ manifest.json     # Plugin manifest
β”‚       β”œβ”€β”€ Server/           # Data assets (items, blocks)
β”‚       └── Common/           # Visual assets (icons, UI, textures)

Gradle Configuration

plugins {
    id 'java'
    id 'com.github.johnrengelman.shadow' version '8.1.1'
}

java {
    sourceCompatibility = JavaVersion.VERSION_25
    targetCompatibility = JavaVersion.VERSION_25
}

repositories {
    mavenCentral()
    maven {
        name = "hytale-release"
        url = uri("https://maven.hytale.com/release")
    }
    maven {
        name = "hytale-pre-release"
        url = uri("https://maven.hytale.com/pre-release")
    }
}

dependencies {
    compileOnly("com.hypixel.hytale:Server:2026.01.28-87d03be09")
}

Plugin Lifecycle

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);
        getLogger().atInfo().log("Plugin loaded!");
    }

    @Override
    protected void setup() {
        // Register commands, events, components
        getLogger().atInfo().log("Plugin setup!");
    }

    @Override
    protected void start() {
        // Start tasks, load config
        getLogger().atInfo().log("Plugin started!");
    }

    @Override
    protected void shutdown() {
        // Save data, clean up resources
        getLogger().atInfo().log("Plugin shutdown!");
    }
}

Critical Gotchas

Logging (Flogger, not SLF4J)

getLogger().atInfo().log("message");   // CORRECT
getLogger().info("message");           // WRONG - method doesn't exist

Event Registration

Event Type Method Example
IBaseEvent<Void> register(Class, Consumer) PlayerConnectEvent
IAsyncEvent<K> registerGlobal(Class, Consumer) PlayerChatEvent

Using register() with keyed events fails at compile time.

Command Permissions

Commands auto-generate permissions. Make public with:

@Override
protected boolean canGeneratePermission() {
    return false;
}

PNG Textures

All PNGs MUST be 8-bit/channel RGBA. 16-bit crashes the client.

magick input.png -depth 8 -type TrueColorAlpha PNG32:output.png
file texture.png  # Should show "8-bit/color RGBA"

Asset Pack Paths

  • Item definitions: Server/Item/Items/ (NOT Common/Assets/Items/)
  • Item icons: Common/Icons/ItemsGenerated/
  • Item models: Common/Items/ItemName/ItemName.blockymodel
  • Item textures: Common/Items/ItemName/ItemName_Texture.png
  • UI files: Common/UI/Custom/
  • Mods folder: server/Server/mods/ (NOT server/mods/)

Custom Items (CRITICAL)

Items REQUIRE an Id field matching the filename:

{
  "Id": "My_Item",
  "TranslationProperties": { "Name": "My Item" },
  "Icon": "Icons/ItemsGenerated/My_Item.png",
  "Model": "Items/My_Item/My_Item.blockymodel",
  "Texture": "Items/My_Item/My_Item_Texture.png"
}
  • Even though server logs warn "Unused key(s): Id", items without Id are silently ignored
  • Model/Texture paths must reference bundled assets β€” you cannot use vanilla asset paths
  • Items are namespaced: /spawnitem pluginname:ItemId
  • Commands are namespaced: /pluginname:commandname

Command Pattern

import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
import java.util.concurrent.CompletableFuture;

public class MyCommand extends AbstractCommand {
    private final OptionalArg<String> nameArg;

    public MyCommand() {
        super("mycommand", "Description of my command");
        nameArg = withOptionalArg("name", "A name argument", ArgTypes.STRING);
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext ctx) {
        String name = ctx.get(nameArg);
        ctx.sendMessage(Message.raw("Hello, " + (name != null ? name : "world") + "!"));
        return CompletableFuture.completedFuture(null);
    }
}

// Register in setup():
// getCommandRegistry().registerCommand(new MyCommand());

Event Pattern

// Unkeyed event (PlayerConnectEvent implements IBaseEvent<Void>)
getEventRegistry().register(PlayerConnectEvent.class, event -> {
    PlayerRef player = event.getPlayerRef();
    player.sendMessage(Message.raw("Welcome, " + player.getUsername() + "!"));
});

// Keyed event (PlayerChatEvent implements IAsyncEvent<String>)
// MUST use registerGlobal - register() won't compile
getEventRegistry().registerGlobal(PlayerChatEvent.class, event -> {
    String content = event.getContent();
    PlayerRef sender = event.getSender();
});

Custom UI Pattern

InteractiveCustomUIPage (with input)

import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
import com.hypixel.hytale.server.core.ui.builder.*;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.protocol.packets.interface_.*;

public class MyPage extends InteractiveCustomUIPage<MyPage.EventData> {

    public MyPage(PlayerRef playerRef) {
        super(playerRef, CustomPageLifetime.CanDismiss, EventData.CODEC);
    }

    @Override
    public void build(Ref<EntityStore> ref, UICommandBuilder ui,
                      UIEventBuilder events, Store<EntityStore> store) {
        ui.append("MyPage.ui");

        // Button click binding (static key - must start uppercase)
        events.addEventBinding(
            CustomUIEventBindingType.Activating,
            "#SaveButton",
            com.hypixel.hytale.server.core.ui.builder.EventData.of("Action", "save")
        );

        // Text field binding (reference key - @ prefix reads element value)
        events.addEventBinding(
            CustomUIEventBindingType.ValueChanged,
            "#NameInput",
            com.hypixel.hytale.server.core.ui.builder.EventData.of("@NameInput", "#NameInput.Value"),
            false  // don't send on initial load
        );
    }

    @Override
    public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store,
                                EventData data) {
        if ("save".equals(data.action)) {
            close();
        } else if (data.nameInput != null) {
            // Handle text input change
        }
    }

    // Typed event data with BuilderCodec for deserialization
    public static class EventData {
        public static final BuilderCodec<EventData> CODEC =
            BuilderCodec.builder(EventData.class, EventData::new)
                .addField(new KeyedCodec<>("Action", Codec.STRING),
                    (d, v) -> d.action = v, d -> d.action)
                .addField(new KeyedCodec<>("NameInput", Codec.STRING),
                    (d, v) -> d.nameInput = v, d -> d.nameInput)
                .build();

        public String action;
        public String nameInput;
    }
}

.ui File Format

$Common = "Common.ui";
@MyTex = PatchStyle(TexturePath: "MyBackground.png");

Group {
  LayoutMode: Center;

  Group #Panel {
    Background: @MyTex;
    Anchor: (Width: 400, Height: 300);
    LayoutMode: Top;

    Label #Title {
      Style: (FontSize: 24, Alignment: Center);
      Anchor: (Top: 20, Height: 40);
      Text: "Hello World";
    }

    TextField #NameInput {
      Style: $Common.@DefaultInputFieldStyle;
      Background: $Common.@InputBoxBackground;
      Anchor: (Top: 10, Width: 300, Height: 50);
      Padding: (Full: 10);
    }
  }
}

Syntax:
- $Variable = "file.ui" β€” import another UI file
- @Variable = PatchStyle(...) β€” define reusable texture
- #ElementId β€” unique identifier for Java access
- Element types: Group, Label, TextField
- LayoutMode: Center, Top

Item Definition (bundled in plugin)

Complete Working Example

Directory structure:

my-plugin/src/main/resources/
β”œβ”€β”€ manifest.json
β”œβ”€β”€ Server/Item/Items/
β”‚   └── My_Item.json
└── Common/
    β”œβ”€β”€ Icons/ItemsGenerated/
    β”‚   └── My_Item.png            # 64x64, 8-bit RGBA PNG
    └── Items/My_Item/
        β”œβ”€β”€ My_Item.blockymodel
        └── My_Item_Texture.png    # 8-bit RGBA PNG

manifest.json:

{
  "Group": "com.example",
  "Name": "MyPlugin",
  "Version": "1.0.0",
  "Main": "com.example.myplugin.MyPlugin",
  "IncludesAssetPack": true,
  "Dependencies": {
    "Hytale:EntityModule": "*",
    "Hytale:BlockModule": "*"
  }
}

Server/Item/Items/My_Item.json:

{
  "TranslationProperties": {
    "Name": "My Custom Item",
    "Description": "A custom item from my plugin"
  },
  "Id": "My_Item",
  "Icon": "Icons/ItemsGenerated/My_Item.png",
  "Model": "Items/My_Item/My_Item.blockymodel",
  "Texture": "Items/My_Item/My_Item_Texture.png",
  "Quality": "Common",
  "MaxStack": 64,
  "Categories": ["Items.Example"]
}

CRITICAL: The Id field MUST be present and match the filename. Items without Id are silently ignored.

Spawn the item: /spawnitem myplugin:My_Item

Weapon Definition

{
  "Id": "Custom_Sword",
  "Parent": "Template_Weapon_Sword",
  "TranslationProperties": { "Name": "Custom Sword" },
  "Model": "Resources/Custom_Sword/model.blockymodel",
  "Texture": "Resources/Custom_Sword/texture.png",
  "Icon": "Icons/ItemsGenerated/Custom_Sword.png",
  "PlayerAnimationsId": "Sword",
  "Quality": "Rare",
  "MaxStack": 1
}

Key weapon properties:
- PlayerAnimationsId β€” links to player animation set
- Parent β€” inherit from template (reduces duplication)
- Interactions β€” Primary/Secondary/Ability attack chains

Build & Deploy

# Build plugin JAR
gradle -p plugin-name shadowJar

# Copy to server mods folder (IMPORTANT: server/Server/mods/, NOT server/mods/)
cp plugin-name/build/libs/*.jar server/Server/mods/

# Start server
./server/start.sh

# Verify plugin loaded (check server logs for):
# [PluginManager] - com.example:PluginName from path PluginName-1.0.0.jar
# [PluginName|P] Plugin loaded!

# Verify item registered (check logs for warning, which means it's working):
# [AssetStore|Item] Unused key(s) in 'My_Item' file /Server/Item/Items/My_Item.json: Id

Context7 Library IDs

For fetching up-to-date documentation:

/websites/hytalemodding_dev_en                  # Community guides (1125 snippets)
/logan-mcduffie/hytale-toolkit                  # Decompiled source (45103 snippets)
/websites/britakee-studios_gitbook_io_hytale-modding-documentation  # Tutorials (457 snippets)

Key Packages

Package Purpose
com.hypixel.hytale.server.core.plugin JavaPlugin, JavaPluginInit
com.hypixel.hytale.server.core.command.system AbstractCommand, CommandContext
com.hypixel.hytale.event EventRegistry
com.hypixel.hytale.server.core.entity.entities.player.pages CustomUIPage, InteractiveCustomUIPage
com.hypixel.hytale.codec.builder BuilderCodec
com.hypixel.hytale.server.core.interaction Interaction system
com.hypixel.hytale.protocol.packets.interface_ CustomUIEventBindingType, CustomPageLifetime

Plugin Registries

Registry Access Method Purpose
logger getLogger() Plugin logging (Flogger)
eventRegistry getEventRegistry() Event handling
commandRegistry getCommandRegistry() Command registration
taskRegistry getTaskRegistry() Scheduled tasks
dataDirectory getDataDirectory() Persistent storage path
assetRegistry getAssetRegistry() Game assets

Decompiled Source Reference

This skill includes documentation extracted from the decompiled HytaleServer.jar (5,237 Java files, 894 packages).

Reference Files

File Description
reference/guides.md Community patterns (commands, ECS, UI, inventory)
reference/events.md Complete events list with registration patterns
reference/packets.md Client-to-server packet reference
reference/decompiled.md Navigation guide for decompiled source
reference/PACKAGES.md Key packages for plugin development
reference/API_REFERENCE.md Core plugin APIs

Quick Hierarchy Reference

Plugins: PluginBase β†’ JavaPlugin β†’ Your plugin

Commands:
- AbstractCommand β†’ Basic commands
- AbstractAsyncCommand β†’ Async commands
- AbstractPlayerCommand β†’ Player-only commands
- AbstractCommandCollection β†’ Subcommand groups

UI Pages:
- CustomUIPage β†’ Base page
- BasicCustomUIPage β†’ Read-only pages
- InteractiveCustomUIPage<T> β†’ Pages with input

Interactions:
- Interaction β†’ Base class
- SimpleInstantInteraction β†’ One-shot actions
- ChargingInteraction β†’ Hold to charge
- ChainingInteraction β†’ Combo chains

Built-in Plugin Examples

The decompiled source contains ~57 built-in plugins showing Hypixel's implementation patterns:

Plugin Purpose
CraftingPlugin Recipe and crafting system
WeatherPlugin Weather effects
PortalsPlugin Portal mechanics
ShopPlugin NPC shops
ObjectivePlugin Quest objectives
FarmingPlugin Farming mechanics

Reference

For extended patterns (ECS, animations, packet handling, interactions), see patterns.md.

For decompiled source navigation, see reference/decompiled.md.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.