Files
Documentation/content/world/entities/entity-components.en.md
2026-01-20 20:33:59 +01:00

25 KiB

title, type, weight
title type weight
Entity Components docs 2

Hytale uses an Entity Component System (ECS) where entities are composed of modular components. Each component adds specific functionality or data to an entity.

Component Categories

{{< cards cols="3" >}} {{< card link="#transform--position" title="Transform & Position" subtitle="Location, rotation, collision" >}} {{< card link="#display--visual" title="Display & Visual" subtitle="Models, animations, effects" >}} {{< card link="#behavior-markers" title="Behavior Markers" subtitle="Interaction flags" >}} {{< card link="#physics" title="Physics" subtitle="Position data, collision" >}} {{< card link="#audio" title="Audio" subtitle="Movement sounds" >}} {{< card link="#custom-components" title="Custom Components" subtitle="Plugin-defined components" >}} {{< /cards >}}


Component Access Pattern

Before diving into specific components, understand how to access components in Hytale's ECS:

// Get component type from the component class
ComponentType<EntityStore, TransformComponent> transformType =
    TransformComponent.getComponentType();

// Get Ref<EntityStore> from PlayerRef or other source
Ref<EntityStore> ref = playerRef.getReference();
if (ref == null || !ref.isValid()) {
    return;
}

// Get Store from Ref, then access component via Store
Store<EntityStore> store = ref.getStore();
TransformComponent transform = store.getComponent(ref, transformType);

// Check if entity has a component using Archetype
Archetype<EntityStore> archetype = store.getArchetype(ref);
boolean hasComponent = archetype.contains(transformType);

{{< callout type="warning" >}} Important: Ref<EntityStore> does NOT have getComponent() or hasComponent() methods directly. You must get the Store first via ref.getStore(), then call store.getComponent(ref, componentType). {{< /callout >}}

{{< callout type="info" >}} All entity components implement Component<EntityStore> and provide a static getComponentType() method to retrieve the ComponentType for component access. {{< /callout >}}


Transform & Position

TransformComponent

Core component for entity position, rotation, and chunk reference.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
position Vector3d World position (x, y, z)
rotation Vector3f Rotation angles (x, y, z)
chunkRef Ref<ChunkStore> Reference to containing chunk
{{< /tab >}}
{{< tab >}}
  • getPosition() - Returns world position as Vector3d
  • setPosition(Vector3d) - Sets world position (uses assign())
  • getRotation() - Returns rotation angles
  • setRotation(Vector3f) - Sets rotation angles (uses assign())
  • teleportPosition(Vector3d) - Teleports entity to position
  • getTransform() - Returns Transform object combining position/rotation
  • getChunkRef() - Returns chunk reference (Ref<ChunkStore>)
  • getChunk() - Deprecated - Returns WorldChunk directly

{{< callout type="warning" >}} getChunk() is deprecated and marked for removal. Use getChunkRef() instead. {{< /callout >}} {{< /tab >}} {{< tab >}}

// Get store from ref
Store<EntityStore> store = ref.getStore();

ComponentType<EntityStore, TransformComponent> transformType =
    TransformComponent.getComponentType();
TransformComponent transform = store.getComponent(ref, transformType);

if (transform != null) {
    // Get current position
    Vector3d pos = transform.getPosition();
    getLogger().at(Level.INFO).log("Entity at: " + pos.x + ", " + pos.y + ", " + pos.z);

    // Get rotation
    Vector3f rot = transform.getRotation();

    // Get chunk reference (recommended)
    Ref<ChunkStore> chunkRef = transform.getChunkRef();
}

{{< /tab >}} {{< /tabs >}}


BoundingBox

Defines the collision bounds of an entity using a Box object.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
boundingBox Box The collision box with min/max corners
detailBoxes Map<String, DetailBox[]> Named detail boxes for precise collision
{{< /tab >}}
{{< tab >}}
  • getBoundingBox() - Returns the Box object
  • setBoundingBox(Box) - Sets the bounding box
  • getDetailBoxes() - Returns map of detail boxes (can be null) {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, BoundingBox> boundsType = BoundingBox.getComponentType();
BoundingBox bounds = store.getComponent(ref, boundsType);

// Access the Box object
Box box = bounds.getBoundingBox();

// Box provides min/max as Vector3d
Vector3d min = box.getMin();
Vector3d max = box.getMax();

// Box utility methods
double width = box.width();    // max.x - min.x
double height = box.height();  // max.y - min.y
double depth = box.depth();    // max.z - min.z

// Center point
double centerX = box.middleX();
double centerY = box.middleY();
double centerZ = box.middleZ();

// Check intersection with another box
Box otherBox = otherBounds.getBoundingBox();
if (box.isIntersecting(otherBox)) {
    handleCollision();
}

// Modify box (mutates in place)
box.offset(1.0, 0, 0);  // Move box by offset
box.scale(2.0f);         // Scale box

// Access detail boxes for precise collision
Map<String, DetailBox[]> details = bounds.getDetailBoxes();
if (details != null) {
    for (Map.Entry<String, DetailBox[]> entry : details.entrySet()) {
        // Process named detail boxes
    }
}

{{< /tab >}} {{< /tabs >}}

The Box Class

The Box class (com.hypixel.hytale.math.shape.Box) is a fundamental shape class:

Method Description
width() Returns width (X axis)
height() Returns height (Y axis)
depth() Returns depth (Z axis)
middleX/Y/Z() Returns center coordinates
getVolume() Returns total volume
isIntersecting(Box) Checks intersection with another box
containsPosition(x,y,z) Checks if point is inside
offset(x,y,z) Moves box by offset (mutates)
scale(float) Scales box (mutates)
union(Box) Expands to contain another box
clone() Creates a copy

CollisionResultComponent

Stores collision detection results from the physics system.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
collisionResult CollisionResult Full collision computation result
collisionStartPosition Vector3d Position where collision started
collisionPositionOffset Vector3d Offset from collision
pendingCollisionCheck boolean Whether collision check is pending
{{< /tab >}}
{{< tab >}}
  • getCollisionResult() - Returns the CollisionResult object
  • isPendingCollisionCheck() - Returns true if collision needs recomputation {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, CollisionResultComponent> collisionType =
    CollisionResultComponent.getComponentType();
CollisionResultComponent collision = store.getComponent(ref,collisionType);

// Get collision result object
CollisionResult result = collision.getCollisionResult();

// Check for block collisions
int blockCollisionCount = result.getBlockCollisionCount();
if (blockCollisionCount > 0) {
    BlockCollisionData firstCollision = result.getFirstBlockCollision();
    // Process collision
}

// Check if sliding (on ground)
if (result.isSliding) {
    // Entity is sliding on a surface
    double slideStart = result.slideStart;
    double slideEnd = result.slideEnd;
}

// Check character collisions (if enabled)
int charCollisionCount = result.getCharacterCollisionCount();
CharacterCollisionData charCollision = result.getFirstCharacterCollision();

// Access trigger blocks (collision triggers)
CollisionDataArray<BlockCollisionData> triggers = result.getTriggerBlocks();

{{< /tab >}} {{< /tabs >}}

{{< callout type="info" >}} The CollisionResult class is complex and handles block collisions, character collisions, slides, and trigger blocks. It's primarily used internally by the collision system. {{< /callout >}}


PositionDataComponent

Tracks block type IDs at entity's position.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
insideBlockTypeId int Block type ID entity is inside
standingOnBlockTypeId int Block type ID entity is standing on
{{< /tab >}}
{{< tab >}}
  • getInsideBlockTypeId() - Returns block type ID entity is inside
  • setInsideBlockTypeId(int) - Sets inside block type ID
  • getStandingOnBlockTypeId() - Returns block type ID beneath entity
  • setStandingOnBlockTypeId(int) - Sets standing-on block type ID {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, PositionDataComponent> posDataType =
    PositionDataComponent.getComponentType();
PositionDataComponent posData = store.getComponent(ref,posDataType);

// Check what block entity is standing on
int standingOn = posData.getStandingOnBlockTypeId();

// Check what block entity is inside
int insideBlock = posData.getInsideBlockTypeId();

// Use block type registry to get actual BlockType
BlockType blockType = BlockType.getAssetMap().getAsset(standingOn);
if (blockType != null) {
    getLogger().at(Level.INFO).log("Standing on: " + blockType.getId());
}

{{< /tab >}} {{< /tabs >}}


Display & Visual

DisplayNameComponent

Controls the display name shown above an entity.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
displayName Message The displayed name as a Message object
{{< /tab >}}
{{< tab >}}
  • getDisplayName() - Returns the Message display name {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, DisplayNameComponent> nameType =
    DisplayNameComponent.getComponentType();
DisplayNameComponent nameComp = store.getComponent(ref,nameType);

// Get current display name
Message name = nameComp.getDisplayName();

// Message is the formatted text object used throughout Hytale
// Use Message.raw() or Message formatting methods to create new names

{{< /tab >}} {{< /tabs >}}

{{< callout type="info" >}} Message Type: Display names use Hytale's Message class, not plain strings. The Message class supports formatted text, colors, and localization. {{< /callout >}}


ModelComponent

Links an entity to its visual 3D model.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
model Model Reference to the model
isNetworkOutdated boolean Network sync flag
{{< /tab >}}
{{< tab >}}
  • getModel() - Returns the Model
  • setModel(Model) - Sets the model {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, ModelComponent> modelType = ModelComponent.getComponentType();
ModelComponent model = store.getComponent(ref,modelType);

// Get current model
Model currentModel = model.getModel();

// Set new model
model.setModel(newModel);

{{< /tab >}} {{< /tabs >}}


EntityScaleComponent

Controls the scale/size of an entity.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
scale float Uniform scale factor (default: 1.0)
isNetworkOutdated boolean Network sync flag
{{< /tab >}}
{{< tab >}}
  • getScale() - Returns the scale factor (float)
  • setScale(float) - Sets the uniform scale factor {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, EntityScaleComponent> scaleType =
    EntityScaleComponent.getComponentType();
EntityScaleComponent scale = store.getComponent(ref,scaleType);

// Get current scale
float currentScale = scale.getScale();  // Default: 1.0

// Make entity twice as large
scale.setScale(2.0f);

// Make entity half size
scale.setScale(0.5f);

// Giant boss spawn effect - track growth in tick handler
private final Map<Ref<EntityStore>, Float> growingBosses = new ConcurrentHashMap<>();

public void startGrowBoss(Ref<EntityStore> bossRef) {
    EntityScaleComponent scale = store.getComponent(bossRef, scaleType);
    scale.setScale(0.1f);  // Start small
    growingBosses.put(bossRef, 0.1f);
}

// Call this from your tick handler (every 5 ticks)
public void onTick() {
    growingBosses.entrySet().removeIf(entry -> {
        Ref<EntityStore> bossRef = entry.getKey();
        float currentScale = entry.getValue();

        if (currentScale >= 1.0f) {
            return true;  // Remove from map
        }

        EntityScaleComponent scale = store.getComponent(bossRef, scaleType);
        if (scale != null) {
            float newScale = currentScale + 0.1f;
            scale.setScale(newScale);
            entry.setValue(newScale);
        }
        return false;  // Keep in map
    });
}

{{< /tab >}} {{< /tabs >}}

{{< callout type="warning" >}} Uniform Scale Only: Hytale's EntityScaleComponent uses a single float for uniform scaling on all axes, not per-axis Vector3f scaling. {{< /callout >}}


ActiveAnimationComponent

Manages currently playing animations per animation slot.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
activeAnimations String[] Animation names per slot
isNetworkOutdated boolean Network sync flag
{{< /tab >}}
{{< tab >}}
  • getActiveAnimations() - Returns array of animation names indexed by AnimationSlot
  • setPlayingAnimation(AnimationSlot, String) - Sets animation for a specific slot {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, ActiveAnimationComponent> animType =
    ActiveAnimationComponent.getComponentType();
ActiveAnimationComponent anim = store.getComponent(ref,animType);

// Get all active animations
String[] animations = anim.getActiveAnimations();

// Set animation for specific slot
anim.setPlayingAnimation(AnimationSlot.Action, "attack_swing");
anim.setPlayingAnimation(AnimationSlot.Movement, "walk");

// Clear animation on a slot
anim.setPlayingAnimation(AnimationSlot.Emote, null);

{{< /tab >}} {{< /tabs >}}

AnimationSlot Enum

Hytale uses animation slots to layer animations:

Slot Value Purpose
Movement 0 Locomotion (walk, run, jump)
Status 1 Status effects
Action 2 Actions (attack, use item)
Face 3 Facial expressions
Emote 4 Social emotes

DynamicLight

Emits dynamic light from an entity using a ColorLight structure.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
colorLight ColorLight Light data (radius, RGB)
isNetworkOutdated boolean Network sync flag
{{< /tab >}}
{{< tab >}}
  • getColorLight() - Returns the ColorLight object
  • setColorLight(ColorLight) - Sets the light configuration {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, DynamicLight> lightType = DynamicLight.getComponentType();
DynamicLight light = store.getComponent(ref,lightType);

// Get current light
ColorLight currentLight = light.getColorLight();

// Create new light configuration
// ColorLight has public fields: radius, red, green, blue (all bytes)
ColorLight torchLight = new ColorLight();
torchLight.radius = 8;      // Light radius
torchLight.red = (byte)255;   // Warm orange color
torchLight.green = (byte)200;
torchLight.blue = (byte)100;

light.setColorLight(torchLight);

// Or use constructor
ColorLight blueLight = new ColorLight(
    (byte)12,   // radius
    (byte)50,   // red
    (byte)100,  // green
    (byte)255   // blue
);
light.setColorLight(blueLight);

{{< /tab >}} {{< /tabs >}}

ColorLight Structure

The ColorLight class (com.hypixel.hytale.protocol.ColorLight) has public fields:

Field Type Description
radius byte Light radius
red byte Red component (0-255)
green byte Green component (0-255)
blue byte Blue component (0-255)

HeadRotation

Controls head rotation independently from body.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
rotation Vector3f Head rotation (x, y, z)
{{< /tab >}}
{{< tab >}}
  • getRotation() - Returns head rotation as Vector3f
  • setRotation(Vector3f) - Sets head rotation (uses assign())
  • getDirection() - Returns look direction as Vector3d
  • getAxisDirection() - Returns axis-aligned direction as Vector3i
  • teleportRotation(Vector3f) - Teleports head rotation {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, HeadRotation> headType = HeadRotation.getComponentType();
HeadRotation head = store.getComponent(ref,headType);

// Get current head rotation
Vector3f rot = head.getRotation();

// Set head rotation (pitch, yaw, roll)
head.setRotation(new Vector3f(-30, 45, 0));  // Look up-right

// Get look direction as normalized vector
Vector3d direction = head.getDirection();

// Get axis direction (discrete: -1, 0, or 1 per axis)
Vector3i axisDir = head.getAxisDirection();

// Teleport rotation (instant, no interpolation)
head.teleportRotation(new Vector3f(0, 180, 0));  // Face backward

{{< /tab >}} {{< /tabs >}}


Behavior Markers

Singleton components that act as flags modifying entity behavior. They use the singleton pattern with an INSTANCE field.

Interactable

{{< badge "Singleton" >}}

Marks an entity as interactable by players.

ComponentType<EntityStore, Interactable> type = Interactable.getComponentType();

// Check if entity has component using archetype
Store<EntityStore> store = ref.getStore();
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
    // Entity is interactable
}

// Component is singleton - use INSTANCE
Interactable component = Interactable.INSTANCE;

Invulnerable

{{< badge "Singleton" >}}

Makes an entity immune to damage.

ComponentType<EntityStore, Invulnerable> type = Invulnerable.getComponentType();

// Check invulnerability in damage handler
getEventRegistry().register(EntityDamageEvent.class, event -> {
    Ref<EntityStore> targetRef = event.getTarget();
    if (targetRef != null && targetRef.isValid()) {
        Store<EntityStore> store = targetRef.getStore();
        Archetype<EntityStore> archetype = store.getArchetype(targetRef);
        if (archetype.contains(type)) {
            event.setCancelled(true);
        }
    }
});

Intangible

{{< badge "Singleton" >}}

Makes an entity pass through other entities and blocks (no collision).

ComponentType<EntityStore, Intangible> type = Intangible.getComponentType();

// Ghost entity - no collision
Store<EntityStore> store = ref.getStore();
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
    // Entity passes through obstacles
}

Audio

MovementAudioComponent

Handles movement-related sounds with a predicate system.

Package: com.hypixel.hytale.server.core.modules.entity.component

{{< tabs items="Fields,Methods,Example" >}} {{< tab >}}

Field Type Description
shouldHearPredicate ShouldHearPredicate Predicate for who hears sounds
lastInsideBlockTypeId int Last block entity was inside
nextMoveInRepeat float Timer for movement sound repeat
{{< /tab >}}
{{< tab >}}
  • getShouldHearPredicate(Ref<EntityStore>) - Returns predicate for sound hearing
  • getLastInsideBlockTypeId() - Returns last inside block ID
  • setLastInsideBlockTypeId(int) - Sets last inside block ID
  • canMoveInRepeat() - Returns true if repeat timer is active
  • tickMoveInRepeat(float) - Advances repeat timer, returns true if elapsed
  • setNextMoveInRepeat(float) - Sets next repeat interval {{< /tab >}} {{< tab >}}
ComponentType<EntityStore, MovementAudioComponent> audioType =
    MovementAudioComponent.getComponentType();
MovementAudioComponent movementAudio = store.getComponent(ref,audioType);

// Get predicate for who should hear movement sounds
MovementAudioComponent.ShouldHearPredicate predicate =
    movementAudio.getShouldHearPredicate(entityRef);

// The predicate returns true for entities that should hear the sound
// (excludes the owner by default)
boolean shouldHear = predicate.test(otherEntityRef);

// Check move-in repeat timer
if (movementAudio.canMoveInRepeat()) {
    float dt = 1.0f / 20.0f;  // Delta time
    if (movementAudio.tickMoveInRepeat(dt)) {
        // Timer elapsed, play repeat sound
    }
}

// Disable repeat sounds
movementAudio.setNextMoveInRepeat(MovementAudioComponent.NO_REPEAT);

{{< /tab >}} {{< /tabs >}}


Custom Components

Creating Custom Components

Create custom components by implementing Component<EntityStore>:

public class CustomDataComponent implements Component<EntityStore> {
    // Static component type reference (set during registration)
    private static ComponentType<EntityStore, CustomDataComponent> TYPE;

    // Component data
    private int level = 1;
    private String faction = "neutral";

    public static ComponentType<EntityStore, CustomDataComponent> getComponentType() {
        return TYPE;
    }

    public int getLevel() { return level; }
    public void setLevel(int level) { this.level = level; }

    public String getFaction() { return faction; }
    public void setFaction(String faction) { this.faction = faction; }

    @Override
    @Nonnull
    public Component<EntityStore> clone() {
        CustomDataComponent copy = new CustomDataComponent();
        copy.level = this.level;
        copy.faction = this.faction;
        return copy;
    }
}

Registering Components

Register components during plugin start:

@Override
public void start() {
    // Register with EntityStoreRegistry
    ComponentType<EntityStore, CustomDataComponent> type =
        getEntityStoreRegistry().register(
            "custom_data",
            CustomDataComponent.class,
            CustomDataComponent::new
        );

    // Store type reference for static access
    CustomDataComponent.TYPE = type;
}

Using Custom Components

// Get component type
ComponentType<EntityStore, CustomDataComponent> type =
    CustomDataComponent.getComponentType();

// Get store from ref
Store<EntityStore> store = ref.getStore();

// Check if entity has component
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
    CustomDataComponent data = store.getComponent(ref, type);

    if (data.getLevel() > 10) {
        // High level entity logic
    }
}

Best Practices

{{< callout type="info" >}} Component Guidelines:

  • Use getComponentType() static method to get the ComponentType for access
  • Always check if a component exists using hasComponent() before accessing
  • Register custom components in start(), after all plugins have completed setup
  • Implement clone() for components that need copying
  • Use singleton pattern (static INSTANCE) for marker components {{< /callout >}}

{{< callout type="warning" >}} Network Sync: Many components have an isNetworkOutdated flag. This is managed automatically by the network system - avoid setting it manually unless you understand the networking layer. {{< /callout >}}

{{< callout type="error" >}} Thread Safety: Component access should be done on the world's ticking thread. Use world.isInThread() to verify, or use the task system for cross-thread operations. {{< /callout >}}


Component Type Reference

Component Package Type Purpose
TransformComponent entity.component Data Position, rotation, chunk
BoundingBox entity.component Data Collision bounds
CollisionResultComponent entity.component Data Collision results
PositionDataComponent entity.component Data Block context
DisplayNameComponent entity.component Data Visible name
ModelComponent entity.component Data 3D model
EntityScaleComponent entity.component Data Size scaling
ActiveAnimationComponent entity.component Data Animations
DynamicLight entity.component Data Light emission
HeadRotation entity.component Data Head direction
MovementAudioComponent entity.component Data Movement sounds
Interactable entity.component Singleton Can be interacted
Invulnerable entity.component Singleton Cannot be damaged
Intangible entity.component Singleton No collision