NYC
skills/smithery/ai/hytale-custom-entities

hytale-custom-entities

SKILL.md

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.

Weekly Installs
1
Repository
smithery/ai
First Seen
2 days ago
Installed on
codex1