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

6.4 KiB

title, type, weight
title type weight
Spawning Entities docs 3

Create and spawn entities into the world by registering entity classes and using the World API.

Registering Entities

Before spawning entities, register them with the EntityRegistry in your plugin's start() method:

@Override
public void start() {
    // Register entity with key, class, constructor, and codec
    getEntityRegistry().registerEntity(
        "my_custom_entity",
        CustomEntity.class,
        world -> new CustomEntity(world),
        CustomEntity.CODEC
    );
}

Registration Parameters

Parameter Type Description
key String Unique identifier for the entity type
clazz Class<T> The entity class
constructor Function<World, T> Factory function that creates entity instances
codec DirectDecodeCodec<T> Serialization codec for persistence

Entity Base Classes

Hytale provides two main entity base classes:

Entity

Base class for all entities. Implements Component<EntityStore>.

public class SimpleEntity extends Entity {
    public static final DirectDecodeCodec<SimpleEntity> CODEC =
        Entity.CODEC.extend(SimpleEntity.class, SimpleEntity::new);

    public SimpleEntity() {
        super();
    }

    public SimpleEntity(World world) {
        super(world);
    }
}

LivingEntity

Extended base class for entities with health, inventory, and stats.

public class CustomMob extends LivingEntity {
    public static final DirectDecodeCodec<CustomMob> CODEC =
        LivingEntity.CODEC.extend(CustomMob.class, CustomMob::new);

    public CustomMob() {
        super();
    }

    public CustomMob(World world) {
        super(world);
    }

    @Override
    protected Inventory createDefaultInventory() {
        return new Inventory(36);  // 36 slots
    }
}

Spawning Entities

Use the World API to spawn registered entities:

// Get the world
World world = player.getWorld();

// Create entity instance
CustomMob mob = new CustomMob(world);

// Define spawn position and rotation
Vector3d position = new Vector3d(100, 64, 200);
Vector3f rotation = new Vector3f(0, 90, 0);  // Facing east

// Spawn the entity (deprecated but functional)
world.spawnEntity(mob, position, rotation);

addEntity Method

For more control over spawn reasons, use addEntity:

// AddReason options: SPAWN, LOAD, TRANSFER
world.addEntity(mob, position, rotation, AddReason.SPAWN);

{{< callout type="warning" >}} Deprecation Notice: Both spawnEntity and addEntity methods on World are deprecated. The modern approach uses the component-based EntityStore system directly. However, these methods still work and are the simplest way to spawn entities. {{< /callout >}}

Entity Lifecycle

Loading into World

When an entity is added to a world:

// Called automatically when entity is added
entity.loadIntoWorld(world);

// Entity receives network ID
int networkId = entity.getNetworkId();

// Reference is created for component access
Ref<EntityStore> ref = entity.getReference();

Removing Entities

// Remove an entity from the world
boolean removed = entity.remove();

// Check if entity was already removed
if (entity.wasRemoved()) {
    // Entity no longer valid
}

Entity Events

EntityRemoveEvent

Listen for entity removal:

getEventRegistry().register(EntityRemoveEvent.class, event -> {
    Entity entity = event.getEntity();
    getLogger().at(Level.INFO).log("Entity removed: " + entity.getClass().getSimpleName());
});

LivingEntityInventoryChangeEvent

Track inventory changes on living entities:

getEventRegistry().register(LivingEntityInventoryChangeEvent.class, event -> {
    LivingEntity entity = event.getEntity();
    // Handle inventory change
});

{{< callout type="info" >}} No Spawn Event: Hytale does not have an EntitySpawnEvent. To track spawns, consider implementing custom logic in your entity constructor or using the component system's add callbacks. {{< /callout >}}

Complete Example

public class MyPlugin extends JavaPlugin {

    @Override
    public void start() {
        // Register custom entity
        getEntityRegistry().registerEntity(
            "boss_monster",
            BossMonster.class,
            world -> new BossMonster(world),
            BossMonster.CODEC
        );

        // Listen for removal
        getEventRegistry().register(EntityRemoveEvent.class, this::onEntityRemove);
    }

    private void onEntityRemove(EntityRemoveEvent event) {
        if (event.getEntity() instanceof BossMonster) {
            getLogger().at(Level.INFO).log("Boss monster was defeated!");
        }
    }

    public void spawnBoss(Player player) {
        World world = player.getWorld();

        // Create boss at player's location
        BossMonster boss = new BossMonster(world);

        // Get player position and add offset
        Vector3d spawnPos = player.getTransformComponent().getPosition();
        spawnPos = new Vector3d(spawnPos.x + 5, spawnPos.y, spawnPos.z);

        // Face the player
        Vector3f rotation = new Vector3f(0, 180, 0);

        // Spawn
        world.spawnEntity(boss, spawnPos, rotation);
    }
}

public class BossMonster extends LivingEntity {
    public static final DirectDecodeCodec<BossMonster> CODEC =
        LivingEntity.CODEC.extend(BossMonster.class, BossMonster::new);

    public BossMonster() {
        super();
    }

    public BossMonster(World world) {
        super(world);
    }

    @Override
    protected Inventory createDefaultInventory() {
        return new Inventory(9);  // Small inventory
    }

    @Override
    public boolean isCollidable() {
        return true;
    }
}

Thread Safety

{{< callout type="error" >}} Important: Entity spawning should be done on the world's ticking thread. If calling from an async context, use the world's executor: {{< /callout >}}

// Safe spawning from async context
world.execute(() -> {
    world.spawnEntity(entity, position, rotation);
});

Spawn Validation

The world validates spawn positions:

// Position requirements:
// - X and Z must be within +/- 33554432
// - Y must be >= -32

// Invalid positions will throw IllegalArgumentException
try {
    world.spawnEntity(entity, invalidPosition, rotation);
} catch (IllegalArgumentException e) {
    getLogger().warning("Invalid spawn position: " + e.getMessage());
}