Init
This commit is contained in:
266
content/world/entities/spawning-entities.en.md
Normal file
266
content/world/entities/spawning-entities.en.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
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());
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user