Agent Skills: Creating Custom Hytale Assets

Create and manage custom assets for Hytale including models, textures, sounds, particles, and asset packs. Use when asked to "add custom assets", "create textures", "make models", "add sounds", "configure particles", or "build an asset pack".

UncategorizedID: mnkyarts/hytale-skills/hytale-custom-assets

Install this agent skill to your local

pnpm dlx add-skill https://github.com/MnkyArts/hytale-skills/tree/HEAD/skills/hytale-custom-assets

Skill Files

Browse the full folder contents for hytale-custom-assets.

Download Skill

Loading file tree…

skills/hytale-custom-assets/SKILL.md

Skill Metadata

Name
hytale-custom-assets
Description
Create and manage custom assets for Hytale including models, textures, sounds, particles, and asset packs. Use when asked to "add custom assets", "create textures", "make models", "add sounds", "configure particles", or "build an asset pack".

Creating Custom Hytale Assets

Complete guide for creating and managing custom assets including models, textures, sounds, and particles.

When to use this skill

Use this skill when:

  • Creating asset packs for plugins
  • Adding custom textures and models
  • Configuring sounds and music
  • Creating particle effects
  • Setting up animations
  • Managing asset inheritance and tags

Asset System Architecture

Hytale uses a hierarchical asset system:

AssetPack
├── Server/          # Server-side assets
│   └── Content/     # Game content definitions
│       ├── BlockTypes/
│       ├── Items/
│       ├── Entities/
│       └── ...
└── Client/          # Client-side assets
    ├── Models/
    ├── Textures/
    ├── Sounds/
    ├── Particles/
    └── ...

Asset Pack Structure

my-plugin/
└── assets/
    ├── pack.json                    # Pack metadata
    ├── Server/
    │   └── Content/
    │       ├── BlockTypes/
    │       │   └── custom_block.blocktype
    │       ├── Items/
    │       │   └── custom_item.item
    │       ├── Entities/
    │       │   └── custom_entity.entity
    │       ├── Recipes/
    │       │   └── custom_recipe.recipe
    │       └── Sounds/
    │           └── custom_soundset.soundset
    └── Client/
        ├── Models/
        │   └── custom_model.model
        ├── Textures/
        │   ├── blocks/
        │   │   └── custom_block.png
        │   ├── items/
        │   │   └── custom_item.png
        │   └── entities/
        │       └── custom_entity.png
        ├── Sounds/
        │   └── custom/
        │       └── sound.ogg
        ├── Particles/
        │   └── custom_particle.particle
        └── Animations/
            └── custom_animation.animation

Pack Metadata

File: pack.json

{
  "Name": "MyPlugin Assets",
  "Id": "MyPlugin",
  "Version": "1.0.0",
  "Description": "Custom assets for MyPlugin",
  "Author": "Your Name",
  "Priority": 100,
  "Dependencies": ["Hytale:Core"]
}

Pack Properties

| Property | Type | Description | |----------|------|-------------| | Name | String | Display name | | Id | String | Unique identifier | | Version | String | Semantic version | | Description | String | Pack description | | Author | String | Creator name | | Priority | Integer | Load order (higher = later) | | Dependencies | Array | Required packs |

Textures

Texture Formats

| Format | Use Case | |--------|----------| | PNG | Standard textures (recommended) | | TGA | Textures with alpha | | DDS | Compressed textures |

Block Textures

Location: Client/Textures/blocks/

custom_block.png          # All faces
custom_block_top.png      # Top face only
custom_block_side.png     # Side faces
custom_block_bottom.png   # Bottom face

Texture Size: 16x16, 32x32, 64x64 (power of 2)

Item Textures

Location: Client/Textures/items/

custom_item.png           # Inventory icon (32x32)
custom_item_held.png      # Held model texture

Entity Textures

Location: Client/Textures/entities/

custom_entity.png         # Main texture atlas
custom_entity_glow.png    # Emissive layer
custom_entity_normal.png  # Normal map

Models

Model Format

Hytale uses a custom JSON-based model format.

File: custom_model.model

{
  "Parent": "Hytale/Models/base_entity",
  "Textures": {
    "main": "MyPlugin/Textures/entities/custom_entity"
  },
  "Bones": [
    {
      "Name": "root",
      "Pivot": [0, 0, 0],
      "Children": [
        {
          "Name": "body",
          "Pivot": [0, 12, 0],
          "Cubes": [
            {
              "Origin": [-4, 0, -2],
              "Size": [8, 12, 4],
              "UV": [0, 0]
            }
          ],
          "Children": [
            {
              "Name": "head",
              "Pivot": [0, 24, 0],
              "Cubes": [
                {
                  "Origin": [-4, 0, -4],
                  "Size": [8, 8, 8],
                  "UV": [0, 16]
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "Animations": {
    "idle": "MyPlugin/Animations/custom_idle",
    "walk": "MyPlugin/Animations/custom_walk"
  }
}

Model Properties

| Property | Type | Description | |----------|------|-------------| | Parent | String | Parent model to inherit | | Textures | Object | Texture bindings | | Bones | Array | Bone hierarchy | | Animations | Object | Animation bindings | | Scale | Float | Overall scale | | TextureSize | Array | Texture dimensions [W, H] |

Bone Properties

| Property | Type | Description | |----------|------|-------------| | Name | String | Bone identifier | | Pivot | Array | Rotation pivot point | | Rotation | Array | Default rotation [X, Y, Z] | | Cubes | Array | Geometry cubes | | Children | Array | Child bones | | Mirror | Boolean | Mirror UV horizontally |

Cube Properties

| Property | Type | Description | |----------|------|-------------| | Origin | Array | Position offset | | Size | Array | Dimensions [W, H, D] | | UV | Array | Texture UV offset [U, V] | | Inflate | Float | Expand cube size | | Mirror | Boolean | Mirror this cube |

Animations

File: custom_animation.animation

{
  "Length": 1.0,
  "Loop": true,
  "Bones": {
    "body": {
      "Rotation": {
        "0.0": [0, 0, 0],
        "0.5": [5, 0, 0],
        "1.0": [0, 0, 0]
      }
    },
    "leftArm": {
      "Rotation": {
        "0.0": [0, 0, 0],
        "0.25": [-30, 0, 0],
        "0.5": [0, 0, 0],
        "0.75": [30, 0, 0],
        "1.0": [0, 0, 0]
      }
    },
    "rightArm": {
      "Rotation": {
        "0.0": [0, 0, 0],
        "0.25": [30, 0, 0],
        "0.5": [0, 0, 0],
        "0.75": [-30, 0, 0],
        "1.0": [0, 0, 0]
      }
    }
  }
}

Animation Properties

| Property | Type | Description | |----------|------|-------------| | Length | Float | Duration in seconds | | Loop | Boolean | Loop animation | | Bones | Object | Per-bone keyframes |

Keyframe Channels

| Channel | Type | Description | |---------|------|-------------| | Rotation | Array | Rotation [X, Y, Z] degrees | | Position | Array | Position offset [X, Y, Z] | | Scale | Array | Scale [X, Y, Z] |

Sounds

Sound Formats

| Format | Use Case | |--------|----------| | OGG | Music, ambient (recommended) | | WAV | Short sound effects |

Sound Event

File: custom_sound.soundevent

{
  "Sounds": [
    {
      "Path": "MyPlugin/Sounds/custom/sound1",
      "Weight": 1.0
    },
    {
      "Path": "MyPlugin/Sounds/custom/sound2",
      "Weight": 0.5
    }
  ],
  "Volume": {
    "Min": 0.8,
    "Max": 1.0
  },
  "Pitch": {
    "Min": 0.9,
    "Max": 1.1
  },
  "Category": "Effects",
  "Subtitle": {
    "en-US": "Custom sound plays"
  }
}

Sound Set

File: custom_soundset.soundset

{
  "Sounds": {
    "break": "MyPlugin/SoundEvents/custom_break",
    "place": "MyPlugin/SoundEvents/custom_place",
    "step": "MyPlugin/SoundEvents/custom_step",
    "hit": "MyPlugin/SoundEvents/custom_hit"
  }
}

Block Sound Set

File: custom_blocksound.blocksound

{
  "Break": {
    "Sound": "MyPlugin/SoundEvents/metal_break",
    "Volume": 1.0,
    "Pitch": 1.0
  },
  "Place": {
    "Sound": "MyPlugin/SoundEvents/metal_place",
    "Volume": 1.0
  },
  "Step": {
    "Sound": "MyPlugin/SoundEvents/metal_step",
    "Volume": 0.5
  },
  "Fall": {
    "Sound": "MyPlugin/SoundEvents/metal_fall"
  }
}

Sound Categories

| Category | Description | |----------|-------------| | Master | All sounds | | Music | Background music | | Effects | Sound effects | | Ambient | Environmental sounds | | Voice | Voice/dialogue | | UI | Interface sounds | | Weather | Weather sounds |

Particles

File: custom_particle.particle

{
  "Texture": "MyPlugin/Textures/particles/sparkle",
  "MaxParticles": 100,
  "EmissionRate": 10,
  "Lifetime": {
    "Min": 0.5,
    "Max": 1.5
  },
  "Size": {
    "Start": 0.2,
    "End": 0.0
  },
  "Color": {
    "Start": { "R": 1.0, "G": 0.8, "B": 0.2, "A": 1.0 },
    "End": { "R": 1.0, "G": 0.2, "B": 0.0, "A": 0.0 }
  },
  "Velocity": {
    "Min": [-0.5, 1.0, -0.5],
    "Max": [0.5, 2.0, 0.5]
  },
  "Gravity": -0.5,
  "Collision": false,
  "BlendMode": "Additive"
}

Particle Properties

| Property | Type | Description | |----------|------|-------------| | Texture | String | Particle texture | | MaxParticles | Integer | Maximum active particles | | EmissionRate | Float | Particles per second | | Lifetime | Range | Particle lifespan | | Size | Range/Gradient | Size over lifetime | | Color | Color/Gradient | Color over lifetime | | Velocity | Range | Initial velocity | | Gravity | Float | Gravity effect | | Collision | Boolean | Collide with blocks | | BlendMode | Enum | Alpha, Additive, Multiply |

Block Particle

File: custom_blockparticle.blockparticle

{
  "Break": {
    "Particle": "MyPlugin/Particles/shatter",
    "Count": 30
  },
  "Step": {
    "Particle": "MyPlugin/Particles/dust",
    "Count": 3
  },
  "Ambient": {
    "Particle": "MyPlugin/Particles/glow",
    "Rate": 1,
    "Radius": 0.5
  }
}

Trails

File: custom_trail.trail

{
  "Texture": "MyPlugin/Textures/trails/slash",
  "Width": 0.5,
  "Length": 10,
  "Lifetime": 0.3,
  "Color": {
    "Start": { "R": 1.0, "G": 1.0, "B": 1.0, "A": 1.0 },
    "End": { "R": 1.0, "G": 1.0, "B": 1.0, "A": 0.0 }
  },
  "BlendMode": "Additive",
  "AttachPoint": "weapon_tip"
}

Asset Inheritance

Assets can inherit from parents:

{
  "Parent": "Hytale:Stone",
  "DisplayName": { "en-US": "Enchanted Stone" },
  "LightLevel": 5
}

Inheritance Rules

  1. Child inherits all parent properties
  2. Child can override any property
  3. Nested objects merge (not replace)
  4. Arrays replace (not merge)

Asset Tags

Categorize assets with tags:

{
  "Tags": {
    "Category": ["Building", "Decorative"],
    "Material": ["Stone"],
    "Origin": ["Natural", "Cave"]
  }
}

Tag Queries

Used in spawning, loot tables, etc.:

{
  "Blocks": {
    "MatchAll": ["Material:Stone"],
    "MatchAny": ["Category:Natural", "Origin:Cave"],
    "Exclude": ["Category:Artificial"]
  }
}

Localization

File: lang/en-US.json

{
  "item.myPlugin.customItem.name": "Custom Item",
  "item.myPlugin.customItem.description": "A special custom item",
  "block.myPlugin.customBlock.name": "Custom Block",
  "entity.myPlugin.customEntity.name": "Custom Creature"
}

Localization Keys

| Pattern | Example | |---------|---------| | item.{plugin}.{id}.name | item.myPlugin.sword.name | | block.{plugin}.{id}.name | block.myPlugin.ore.name | | entity.{plugin}.{id}.name | entity.myPlugin.mob.name | | ui.{plugin}.{key} | ui.myPlugin.menuTitle |

Registering Assets in Plugin

@Override
protected void setup() {
    // Asset pack is auto-registered if manifest.json has:
    // "IncludesAssetPack": true
    
    // Listen for asset loading
    getEventRegistry().register(
        LoadedAssetsEvent.class,
        BlockType.class,
        this::onBlockTypesLoaded
    );
    
    getEventRegistry().register(
        LoadedAssetsEvent.class,
        Item.class,
        this::onItemsLoaded
    );
}

private void onBlockTypesLoaded(LoadedAssetsEvent<BlockType> event) {
    // Access loaded block types
    for (BlockType block : event.getLoadedAssets()) {
        getLogger().atInfo().log("Loaded block: %s", block.getId());
    }
}

Hot Reloading

Assets support hot-reload during development:

/reload assets

Listen for reload events:

getEventRegistry().register(
    AssetStoreMonitorEvent.class,
    this::onAssetReload
);

private void onAssetReload(AssetStoreMonitorEvent event) {
    if (event.getAssetStore().getAssetClass() == BlockType.class) {
        getLogger().atInfo().log("Block types reloaded");
        // Re-initialize dependent systems
    }
}

Custom Asset Store

Register entirely new asset types:

public class MyCustomAsset implements JsonAssetWithMap<String, DefaultAssetMap<String, MyCustomAsset>> {
    public static final AssetBuilderCodec<String, MyCustomAsset> CODEC = 
        AssetBuilderCodec.builder(MyCustomAsset.class, "MyCustomAsset")
            .appendRequired(Codec.STRING.fieldOf("Name"), MyCustomAsset::getName, (a, v) -> a.name = v)
            .appendOptional(Codec.INT.fieldOf("Value"), MyCustomAsset::getValue, (a, v) -> a.value = v, 0)
            .build();
    
    private String id;
    private String name;
    private int value;
    
    @Override
    public String getId() { return id; }
    public String getName() { return name; }
    public int getValue() { return value; }
}

// Register in plugin
@Override
protected void setup() {
    HytaleAssetStore<String, MyCustomAsset, DefaultAssetMap<String, MyCustomAsset>> store = 
        new HytaleAssetStore<>(
            MyCustomAsset.class,
            "MyCustomAssets",      // Directory name
            ".mycustomasset",      // File extension
            MyCustomAsset.CODEC,
            DefaultAssetMap::new
        );
    
    getAssetRegistry().register(store);
}

Troubleshooting

Textures Not Loading

  1. Check file path matches reference
  2. Verify PNG format and dimensions
  3. Ensure pack.json is valid
  4. Check console for loading errors

Models Not Displaying

  1. Verify model JSON syntax
  2. Check texture references exist
  3. Ensure bone hierarchy is valid
  4. Test with simpler model first

Sounds Not Playing

  1. Check OGG/WAV format compatibility
  2. Verify sound event references
  3. Check volume and category settings
  4. Ensure sound files exist at path

Asset Inheritance Not Working

  1. Verify Parent path is correct
  2. Check parent asset loads first
  3. Ensure pack dependencies are set

See references/asset-formats.md for detailed format specs. See references/texture-specs.md for texture requirements.