Agent Skills: Creating Custom Hytale Entities

Create custom entities and NPCs for Hytale with AI behaviors, components, spawning, and animations. Use when asked to "create a custom entity", "add an NPC", "make a mob", "design AI behavior", or "configure entity spawning".

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

Install this agent skill to your local

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

Skill Files

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

Download Skill

Loading file tree…

skills/hytale-custom-entities/SKILL.md

Skill Metadata

Name
hytale-custom-entities
Description
Create custom entities and NPCs for Hytale with AI behaviors, components, spawning, and animations. Use when asked to "create a custom entity", "add an NPC", "make a mob", "design AI behavior", or "configure entity spawning".

Creating Custom Hytale Entities

Complete guide for defining custom entities with AI, components, spawning, and animations.

When to use this skill

Use this skill when:

  • Creating new entity types (mobs, NPCs, creatures)
  • Designing AI behaviors with sensors and actions
  • Setting up entity spawning rules
  • Adding custom entity components
  • Configuring entity animations and models
  • Creating interactive NPCs

Entity Architecture Overview

Hytale uses an ECS (Entity Component System) architecture:

  • Entity: Container with unique ID
  • Components: Data attached to entities
  • Systems: Logic that processes components

Entity Hierarchy

Entity
├── LivingEntity (has health, inventory, stats)
│   ├── Player
│   └── NPCEntity (has AI role, pathfinding)
├── BlockEntity (block-attached entities)
├── ProjectileEntity
└── ItemEntity (dropped items)

Entity Asset Structure

my-plugin/
└── assets/
    └── Server/
        └── Content/
            ├── Entities/
            │   └── my_creature.entity
            ├── Roles/
            │   └── my_creature_role.role
            └── Spawns/
                └── my_creature_spawn.spawn

Basic Entity Definition

File: my_creature.entity

{
  "DisplayName": {
    "en-US": "Custom Creature"
  },
  "Model": "MyPlugin/Models/custom_creature",
  "Health": 20,
  "MovementSpeed": 1.0,
  "Role": "MyPlugin:CustomCreatureRole",
  "Tags": {
    "Type": ["Monster", "Hostile"]
  }
}

Entity Properties Reference

Core Properties

| Property | Type | Description | |----------|------|-------------| | DisplayName | LocalizedString | Entity name | | Model | String | Model asset reference | | Health | Float | Maximum health | | Role | String | AI role reference | | Team | String | Team/faction ID | | Tags | Object | Classification tags |

Physical Properties

| Property | Type | Default | Description | |----------|------|---------|-------------| | MovementSpeed | Float | 1.0 | Base move speed | | BoundingBox | Object | auto | Collision box | | Mass | Float | 1.0 | Physics mass | | Gravity | Float | 1.0 | Gravity multiplier | | CanSwim | Boolean | false | Can swim in water | | CanFly | Boolean | false | Can fly | | StepHeight | Float | 0.5 | Max step-up height |

Combat Properties

| Property | Type | Description | |----------|------|-------------| | AttackDamage | Float | Base attack damage | | AttackSpeed | Float | Attacks per second | | AttackRange | Float | Melee attack range | | Armor | Float | Damage reduction | | KnockbackResistance | Float | Knockback reduction |

Visual Properties

| Property | Type | Description | |----------|------|-------------| | Scale | Float | Model scale | | RenderDistance | Float | Max render distance | | ShadowSize | Float | Shadow radius | | GlowColor | Color | Outline glow color | | Particles | String | Ambient particles |

AI Role System

NPCs are controlled by Roles containing Instructions with:

  • Sensors: Conditions for activation
  • BodyMotion: Movement behavior
  • HeadMotion: Look behavior
  • Actions: Effects to execute

Basic Role Definition

File: my_creature_role.role

{
  "MotionController": "Walk",
  "DefaultInstruction": {
    "Sensor": {
      "Type": "Always"
    },
    "BodyMotion": {
      "Type": "Wander",
      "Speed": 0.5,
      "Radius": 10
    },
    "HeadMotion": {
      "Type": "Nothing"
    }
  },
  "Instructions": [
    {
      "Priority": 10,
      "Sensor": {
        "Type": "SensorPlayer",
        "Range": 15,
        "Condition": "Visible"
      },
      "BodyMotion": {
        "Type": "Pursue",
        "Target": "Player",
        "Speed": 1.0
      },
      "HeadMotion": {
        "Type": "Watch",
        "Target": "Player"
      },
      "Actions": [
        {
          "Type": "Attack",
          "Range": 2.0,
          "Damage": 5,
          "Cooldown": 1.0
        }
      ]
    }
  ]
}

Motion Controllers

| Controller | Description | |------------|-------------| | Walk | Ground-based movement | | Fly | Aerial movement | | Dive | Swimming movement | | Hover | Stationary flight |

Sensor Types

| Sensor | Description | Parameters | |--------|-------------|------------| | Always | Always true | - | | Never | Always false | - | | Random | Random chance | Chance | | SensorPlayer | Detect players | Range, Condition | | SensorEntity | Detect entities | Range, EntityType, Tags | | SensorDamage | When damaged | Threshold | | SensorHealth | Health check | Below, Above | | SensorTime | Time of day | DayTime, NightTime | | SensorNav | Navigation state | HasPath, AtDestination | | SensorDistance | Distance check | Target, Min, Max |

Body Motion Types

| Motion | Description | Parameters | |--------|-------------|------------| | Wander | Random wandering | Speed, Radius, IdleTime | | Pursue | Chase target | Target, Speed, StopDistance | | Flee | Run from target | Target, Speed, SafeDistance | | MoveTo | Go to position | Position, Speed | | MoveAway | Move away | Target, Distance | | Patrol | Follow path | Waypoints, Speed | | Circle | Circle target | Target, Radius, Speed | | Stay | Don't move | - | | TakeOff | Start flying | - | | Land | Stop flying | - | | Teleport | Instant move | Position |

Head Motion Types

| Motion | Description | Parameters | |--------|-------------|------------| | Watch | Look at target | Target | | Aim | Aim at target | Target, Offset | | Look | Look direction | Direction | | Nothing | Don't control head | - |

Action Types

| Action | Description | Parameters | |--------|-------------|------------| | Attack | Melee attack | Damage, Range, Cooldown | | RangedAttack | Projectile attack | Projectile, Speed, Cooldown | | ApplyEntityEffect | Apply effect | Effect, Duration, Target | | PlaySound | Play sound | Sound, Volume | | SpawnEntity | Spawn entity | Entity, Count | | SetStat | Modify stat | Stat, Value | | SetFlag | Set flag | Flag, Value | | Notify | Trigger event | Event, Data | | Wait | Delay | Duration |

Complex AI Example

Aggressive mob with multiple behaviors:

{
  "MotionController": "Walk",
  "CombatRange": 2.0,
  "AggroRange": 20,
  "LeashRange": 40,
  
  "DefaultInstruction": {
    "Sensor": { "Type": "Always" },
    "BodyMotion": {
      "Type": "Wander",
      "Speed": 0.4,
      "Radius": 15,
      "IdleTime": { "Min": 2, "Max": 5 }
    },
    "HeadMotion": { "Type": "Nothing" }
  },
  
  "Instructions": [
    {
      "Name": "ReturnToLeash",
      "Priority": 100,
      "Sensor": {
        "Type": "SensorDistance",
        "Target": "LeashPosition",
        "Min": 40
      },
      "BodyMotion": {
        "Type": "MoveTo",
        "Target": "LeashPosition",
        "Speed": 1.5
      }
    },
    {
      "Name": "AttackPlayer",
      "Priority": 50,
      "Sensor": {
        "Type": "And",
        "Sensors": [
          {
            "Type": "SensorPlayer",
            "Range": 2.5,
            "Condition": "Visible"
          },
          {
            "Type": "SensorCooldown",
            "Cooldown": "AttackCooldown",
            "Ready": true
          }
        ]
      },
      "BodyMotion": { "Type": "Stay" },
      "HeadMotion": {
        "Type": "Aim",
        "Target": "Player"
      },
      "Actions": [
        {
          "Type": "Attack",
          "Damage": 8,
          "Animation": "attack_swing"
        },
        {
          "Type": "SetCooldown",
          "Cooldown": "AttackCooldown",
          "Duration": 1.5
        }
      ]
    },
    {
      "Name": "ChasePlayer",
      "Priority": 40,
      "Sensor": {
        "Type": "SensorPlayer",
        "Range": 20,
        "Condition": "Visible"
      },
      "BodyMotion": {
        "Type": "Pursue",
        "Target": "Player",
        "Speed": 1.0,
        "StopDistance": 1.5
      },
      "HeadMotion": {
        "Type": "Watch",
        "Target": "Player"
      }
    },
    {
      "Name": "FleeWhenLow",
      "Priority": 60,
      "Sensor": {
        "Type": "And",
        "Sensors": [
          { "Type": "SensorHealth", "Below": 0.25 },
          { "Type": "SensorPlayer", "Range": 15 }
        ]
      },
      "BodyMotion": {
        "Type": "Flee",
        "Target": "Player",
        "Speed": 1.3,
        "SafeDistance": 25
      }
    }
  ]
}

Entity Spawning

Define where and when entities spawn:

Spawn Point Configuration

File: my_creature_spawn.spawn

{
  "Entity": "MyPlugin:CustomCreature",
  "SpawnWeight": 10,
  "GroupSize": { "Min": 1, "Max": 3 },
  "SpawnConditions": {
    "Biomes": ["Plains", "Forest"],
    "TimeOfDay": {
      "Start": 0.75,
      "End": 0.25
    },
    "LightLevel": { "Max": 7 },
    "MoonPhase": ["Full", "Waning"],
    "Weather": ["Clear", "Cloudy"],
    "Surface": true
  },
  "SpawnCooldown": 300,
  "MaxNearby": 4,
  "NearbyCheckRadius": 32
}

Spawn Beacon (Block-based spawning)

{
  "Type": "Beacon",
  "Entity": "MyPlugin:CustomCreature",
  "SpawnRadius": 10,
  "SpawnInterval": 100,
  "MaxSpawns": 5,
  "DespawnDistance": 64,
  "RequiredBlock": "MyPlugin:SpawnerBlock"
}

Custom Entity Components

Create custom data components:

Component Definition

public class MyEntityData implements Component<EntityStore> {
    public static final BuilderCodec<MyEntityData> CODEC = BuilderCodec.builder(
        Codec.INT.required().fieldOf("Level"),
        Codec.STRING.optionalFieldOf("CustomName", ""),
        Codec.BOOL.optionalFieldOf("IsEnraged", false)
    ).constructor(MyEntityData::new);
    
    private int level;
    private String customName;
    private boolean isEnraged;
    
    public MyEntityData() {
        this(1, "", false);
    }
    
    public MyEntityData(int level, String customName, boolean isEnraged) {
        this.level = level;
        this.customName = customName;
        this.isEnraged = isEnraged;
    }
    
    // Getters and setters
    public int getLevel() { return level; }
    public void setLevel(int level) { this.level = level; }
    public String getCustomName() { return customName; }
    public void setCustomName(String name) { this.customName = name; }
    public boolean isEnraged() { return isEnraged; }
    public void setEnraged(boolean enraged) { this.isEnraged = enraged; }
}

Component Registration

@Override
protected void setup() {
    ComponentType<EntityStore, MyEntityData> myDataType = 
        getEntityStoreRegistry().registerComponent(
            MyEntityData.class,
            "myPluginEntityData",
            MyEntityData.CODEC
        );
}

Custom Entity Systems

Process entities with matching components:

Tick System

public class EnrageSystem extends TickSystem<EntityStore> {
    private ComponentAccess<EntityStore, MyEntityData> myData;
    private ComponentAccess<EntityStore, HealthComponent> health;
    
    @Override
    protected void register(Store<EntityStore> store) {
        myData = registerComponent(MyEntityData.class);
        health = registerComponent(HealthComponent.class);
    }
    
    @Override
    public void tick(
        int index,
        ArchetypeChunk<EntityStore> chunk,
        Store<EntityStore> store,
        CommandBuffer<EntityStore> buffer
    ) {
        MyEntityData data = myData.get(chunk, index);
        HealthComponent hp = health.getOptional(chunk, index);
        
        if (hp != null && hp.getPercent() < 0.25f && !data.isEnraged()) {
            data.setEnraged(true);
            // Apply enrage buff
        }
    }
}

Event System

public class MyDamageHandler extends EntityEventSystem<EntityStore, Damage> {
    private ComponentAccess<EntityStore, MyEntityData> myData;
    
    public MyDamageHandler() {
        super(Damage.class);
    }
    
    @Override
    protected void register(Store<EntityStore> store) {
        myData = registerComponent(MyEntityData.class);
    }
    
    @Override
    public void handle(
        int index,
        ArchetypeChunk<EntityStore> chunk,
        Store<EntityStore> store,
        CommandBuffer<EntityStore> buffer,
        Damage damage
    ) {
        MyEntityData data = myData.getOptional(chunk, index);
        if (data != null && data.isEnraged()) {
            // Reduce damage when enraged
            damage.setAmount(damage.getAmount() * 0.5f);
        }
    }
}

Entity Registration in Plugin

@Override
protected void setup() {
    // Register components
    ComponentType<EntityStore, MyEntityData> dataType = 
        getEntityStoreRegistry().registerComponent(
            MyEntityData.class,
            "myEntityData", 
            MyEntityData.CODEC
        );
    
    // Register systems
    getEntityStoreRegistry().registerSystem(new EnrageSystem());
    getEntityStoreRegistry().registerSystem(new MyDamageHandler());
    
    // Register custom sensors
    getCodecRegistry(Sensor.CODEC).register(
        "MySensor", MySensor.class, MySensor.CODEC
    );
    
    // Register custom actions
    getCodecRegistry(Action.CODEC).register(
        "MyAction", MyAction.class, MyAction.CODEC
    );
}

NPC Interactions

Create interactive NPCs:

{
  "DisplayName": { "en-US": "Village Merchant" },
  "Model": "MyPlugin/Models/merchant",
  "Role": "MyPlugin:MerchantRole",
  "IsInteractable": true,
  "Interactions": {
    "Use": "MyPlugin:OpenShop"
  },
  "DialogueTree": "MyPlugin:MerchantDialogue",
  "Schedule": {
    "06:00-18:00": "WorkAtShop",
    "18:00-22:00": "Wander",
    "22:00-06:00": "Sleep"
  }
}

Complete Example: Boss Entity

{
  "DisplayName": {
    "en-US": "Shadow Guardian"
  },
  "Description": {
    "en-US": "Ancient protector of the dark temple"
  },
  "Model": "MyPlugin/Models/shadow_guardian",
  "Scale": 2.0,
  "Health": 500,
  "Armor": 10,
  "AttackDamage": 20,
  "MovementSpeed": 0.8,
  "KnockbackResistance": 0.8,
  "Role": "MyPlugin:ShadowGuardianRole",
  "BossBar": {
    "Enabled": true,
    "Color": "Purple",
    "Style": "Notched"
  },
  "Loot": "MyPlugin:ShadowGuardianLoot",
  "DeathSound": "MyPlugin/Sounds/boss_death",
  "AmbientSound": {
    "Sound": "MyPlugin/Sounds/dark_ambient",
    "Interval": 5
  },
  "Particles": "MyPlugin/Particles/shadow_aura",
  "GlowColor": { "R": 0.5, "G": 0.0, "B": 0.8 },
  "Tags": {
    "Type": ["Boss", "Undead", "Hostile"]
  }
}

Troubleshooting

Entity Not Spawning

  1. Check spawn conditions match environment
  2. Verify spawn weight is > 0
  3. Check MaxNearby limit
  4. Ensure biome/time conditions are met

AI Not Working

  1. Verify Role reference is correct
  2. Check sensor conditions are achievable
  3. Ensure instruction priorities are ordered
  4. Debug with /npc debug command

Components Not Saving

  1. Ensure CODEC is defined correctly
  2. Register with unique string ID
  3. Check serialization in logs

See references/entity-components.md for built-in components. See references/ai-sensors.md for all sensor types. See references/ai-actions.md for all action types.