267 lines
6.4 KiB
Markdown
267 lines
6.4 KiB
Markdown
---
|
|
title: Spawning Entities
|
|
type: docs
|
|
weight: 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:
|
|
|
|
```java
|
|
@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>`.
|
|
|
|
```java
|
|
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.
|
|
|
|
```java
|
|
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:
|
|
|
|
```java
|
|
// 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`:
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```java
|
|
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:
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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 >}}
|
|
|
|
```java
|
|
// Safe spawning from async context
|
|
world.execute(() -> {
|
|
world.spawnEntity(entity, position, rotation);
|
|
});
|
|
```
|
|
|
|
## Spawn Validation
|
|
|
|
The world validates spawn positions:
|
|
|
|
```java
|
|
// 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());
|
|
}
|
|
```
|