# Entity Component System (ECS) The Hytale server uses an Entity Component System architecture for managing game entities. This provides high performance and flexibility for handling large numbers of entities with varied behaviors. ## ECS Concepts ### Overview The ECS pattern separates data (Components) from behavior (Systems): - **Entities**: Unique identifiers (IDs) that represent game objects - **Components**: Pure data containers attached to entities - **Systems**: Logic that operates on entities with specific component combinations - **Stores**: Data storage containers that hold components - **Archetypes**: Predefined combinations of components ### Key Classes | Class | Description | |---------------------|--------------------------------------| | `Component` | Base component interface | | `ComponentRegistry` | Central registry for component types | | `Store` | ECS data storage | | `Ref` | Entity reference (ID wrapper) | | `Holder` | Component holder/accessor | | `Archetype` | Entity archetype definition | | `SystemType` | System type definition | | `Query` | ECS query for finding entities | ## Components ### Defining a Component Components are pure data classes implementing `Component`: ```java public class HealthComponent implements Component { public static final ComponentRegistry.Entry ENTRY = EntityStore.REGISTRY.register("health", HealthComponent.class, HealthComponent::new); private float health; private float maxHealth; public HealthComponent() { this.health = 20.0f; this.maxHealth = 20.0f; } public float getHealth() { return health; } public void setHealth(float health) { this.health = Math.max(0, Math.min(health, maxHealth)); } public float getMaxHealth() { return maxHealth; } public void setMaxHealth(float maxHealth) { this.maxHealth = maxHealth; } public boolean isDead() { return health <= 0; } public void damage(float amount) { setHealth(health - amount); } public void heal(float amount) { setHealth(health + amount); } } ``` ### Built-in Components The server provides several built-in components in `EntityStore.REGISTRY`: | Component | Description | |--------------------------|-----------------------------------| | `TransformComponent` | Position, rotation, and scale | | `UUIDComponent` | Unique entity identifier | | `ModelComponent` | Visual model reference | | `MovementAudioComponent` | Movement sound effects | | `PositionDataComponent` | Position tracking data | | `HeadRotation` | Head rotation for living entities | ### Registering Components Register custom components in your plugin's `setup()`: ```java @Override protected void setup() { // Components are registered through the EntityStoreRegistry getEntityStoreRegistry().registerComponent(MyComponent.ENTRY); } ``` ## Entities ### Entity Hierarchy ``` Component └── Entity // Base entity └── LivingEntity // Has health and can take damage ├── Player // Player entity └── NPCEntity // NPC entity ``` ### Creating a Custom Entity ```java public class CustomEntity extends Entity { public static final Codec CODEC = BuilderCodec.of(CustomEntity::new) // Add codec fields .build(); private int customData; public CustomEntity(World world) { super(world); this.customData = 0; } public int getCustomData() { return customData; } public void setCustomData(int data) { this.customData = data; } @Override public void tick() { super.tick(); // Custom tick logic } } ``` ### Registering Entities ```java @Override protected void setup() { getEntityRegistry().register( "customEntity", CustomEntity.class, CustomEntity::new ); // With serialization codec getEntityRegistry().register( "customEntity", CustomEntity.class, CustomEntity::new, CustomEntity.CODEC ); } ``` ### Accessing Entity Components ```java Entity entity = world.getEntity(entityId); // Get a component TransformComponent transform = entity.get(TransformComponent.ENTRY); if (transform != null) { Vector3d position = transform.getPosition(); } // Check if entity has component if (entity.has(HealthComponent.ENTRY)) { // Entity has health } // Add a component entity.add(MyComponent.ENTRY, new MyComponent()); // Remove a component entity.remove(MyComponent.ENTRY); ``` ## Entity Store The `EntityStore` manages entity data within a world: ```java World world = Universe.get().getWorld("default"); EntityStore store = world.getEntityStore(); // Create entity Ref entityRef = store.create(); // Add components to entity store.add(entityRef, TransformComponent.ENTRY, new TransformComponent(position)); store.add(entityRef, HealthComponent.ENTRY, new HealthComponent()); // Get component from entity HealthComponent health = store.get(entityRef, HealthComponent.ENTRY); // Remove entity store.remove(entityRef); ``` ## Queries Queries find entities with specific component combinations: ```java // Query for entities with both Transform and Health components Query query = Query.builder() .with(TransformComponent.ENTRY) .with(HealthComponent.ENTRY) .build(); // Execute query EntityStore store = world.getEntityStore(); store.query(query, (ref, transform, health) -> { // Process each matching entity if (health.isDead()) { // Handle dead entity } }); ``` ### Query Builder ```java Query query = Query.builder() .with(ComponentA.ENTRY) // Must have ComponentA .with(ComponentB.ENTRY) // Must have ComponentB .without(ComponentC.ENTRY) // Must NOT have ComponentC .build(); ``` ## Archetypes Archetypes define common component combinations for entity types: ```java // Define an archetype for enemies Archetype enemyArchetype = Archetype.builder() .add(TransformComponent.ENTRY) .add(HealthComponent.ENTRY) .add(AIComponent.ENTRY) .add(CombatComponent.ENTRY) .build(); // Create entity with archetype Ref enemy = store.create(enemyArchetype); ``` ## Systems Systems contain the logic that operates on entities: ```java public class HealthRegenSystem implements System { private static final Query QUERY = Query.builder() .with(HealthComponent.ENTRY) .build(); @Override public void tick(World world) { EntityStore store = world.getEntityStore(); store.query(QUERY, (ref, health) -> { if (!health.isDead() && health.getHealth() < health.getMaxHealth()) { // Regenerate 0.1 health per tick health.heal(0.1f); } }); } } ``` ### Registering Systems ```java @Override protected void setup() { // Register system with the world module getEntityStoreRegistry().registerSystem(new HealthRegenSystem()); } ``` ## Working with Players ### Getting Players ```java // Get all players Collection players = Universe.get().getPlayers(); // Get player by name Player player = Universe.get().getPlayer("PlayerName"); // Get player by UUID Player player = Universe.get().getPlayer(uuid); // Get players in world World world = Universe.get().getWorld("default"); Collection worldPlayers = world.getPlayers(); ``` ### Player Properties ```java Player player = getPlayer(); // Position Vector3d position = player.getPosition(); player.setPosition(new Vector3d(x, y, z)); // Rotation float yaw = player.getYaw(); float pitch = player.getPitch(); // Health (if LivingEntity) player.setHealth(20.0f); float health = player.getHealth(); // Game mode GameMode mode = player.getGameMode(); player.setGameMode(GameMode.CREATIVE); // Inventory Inventory inventory = player.getInventory(); ``` ## Living Entities `LivingEntity` extends `Entity` with health and damage capabilities: ```java public class CustomMob extends LivingEntity { public CustomMob(World world) { super(world); setMaxHealth(50.0f); setHealth(50.0f); } @Override public void onDamage(DamageSource source, float amount) { super.onDamage(source, amount); // Custom damage handling } @Override public void onDeath() { super.onDeath(); // Drop loot, play effects, etc. } } ``` ## Entity Events Subscribe to entity-related events: ```java // Entity removed from world getEventRegistry().register(EntityRemoveEvent.class, event -> { Entity entity = event.getEntity(); // Handle entity removal }); // Living entity uses block getEventRegistry().register(LivingEntityUseBlockEvent.class, event -> { LivingEntity entity = event.getEntity(); // Handle block usage }); ``` ## Spawning Entities ```java // Get the world World world = Universe.get().getWorld("default"); // Spawn entity at position Vector3d position = new Vector3d(100, 64, 100); CustomEntity entity = new CustomEntity(world); entity.setPosition(position); world.addEntity(entity); // Spawn with specific properties CustomEntity entity = new CustomEntity(world); entity.setPosition(position); entity.setCustomData(42); world.addEntity(entity); ``` ## Best Practices 1. **Keep components data-only** - Components should not contain logic, only data 2. **Use systems for behavior** - Put game logic in systems, not components 3. **Prefer composition** - Use multiple small components instead of large monolithic ones 4. **Query efficiently** - Cache queries and reuse them 5. **Use archetypes** - Define archetypes for common entity types 6. **Handle null components** - Always check if a component exists before using it 7. **Clean up entities** - Remove entities when they're no longer needed 8. **Use appropriate entity types** - Extend `LivingEntity` for damageable entities