Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
72 changes: 72 additions & 0 deletions content/docs/af-ZA/guides/ecs/entity-component-system.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: Entity Component System
description: A basic introduction into ECS (Entity Component System)
---

<Callout type="warning">
This guide requires basic understanding of OOP concepts such as inheritance.

Please make sure you have a good understanding of these concepts before reading this guide.
</Callout>

<Callout type="info">
This guide is currently only on the theory of ECS and will not have code examples.

Code examples are intentionally omitted to avoid confusion about how Hytale’s ECS may be implemented.
</Callout>

# Introduction

An Entity Component System otherwise known as ECS is a design pattern in game development.

ECS is made up of Entities, which reference Components and Systems that operates on Entities.

It also focuses on composition over inheritance, eliminating rigid hierarchy, helps separate behavior and data and makes reusability easier.

# Composition Over Inheritance

Before getting into understanding what ECS is made of, lets understand what is the ECS philosophy.

We can describe composition as "has X" and inheritance as "is X".
When we inherit a class in Java, we inherit all of its attributes and methods.
While in composition we can choose for each object what it references and what systems will work on it.

# Entities, Components and Systems

## Entities

An Entity is just a unique identifier.

- It contains no data.
- It contains no logic

Think of entities as nouns that can be described using components

## Components

Components are just plain data containers.

- They store only data
- They contain no behavior or logic
- They describe traits or aspects of an entity

Examples of components:

- A 'Position' component that stores X, Y and Z coordinates
- A 'Velocity' component that stores the speed along each axis
- A 'Health' component that stores the current health of the entity

Think of components as adjectives that describe an entity

## Systems

Systems contains the logic for your game (or in this case, a mod).

- It Operates on all entities that have a specific set of components
- It Does not store entity data itself
- It Applies behavior by reading and modifying component data

For example:

- A "WinConditionSystem" may process every Entity that contains the components "Position" and "Velocity"
- If an entity reaches the position `(0, 0, 0)`, the system will trigger a "You Won!" message.
204 changes: 204 additions & 0 deletions content/docs/af-ZA/guides/ecs/example-ecs-plugin.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
---
title: Example ECS Plugin
description: In this guide you will learn how to create a simple poison system utilising all of the features you previously learned about Hytale's ECS system
authors:
- name: oskarscot
url: https://oskar.scot
---

## Practical Example: Poison System

Putting together everything you learned so far, here's a complete poison effect. When applied to any entity, it deals damage at a set interval until it expires and removes itself.

```java
package scot.oskar.hytaletemplate.components;

import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import javax.annotation.Nullable;

public class PoisonComponent implements Component<EntityStore> {

private float damagePerTick;
private float tickInterval;
private int remainingTicks;
private float elapsedTime;

public PoisonComponent() {
this(5f, 1.0f, 10);
}

public PoisonComponent(float damagePerTick, float tickInterval, int totalTicks) {
this.damagePerTick = damagePerTick;
this.tickInterval = tickInterval;
this.remainingTicks = totalTicks;
this.elapsedTime = 0f;
}

public PoisonComponent(PoisonComponent other) {
this.damagePerTick = other.damagePerTick;
this.tickInterval = other.tickInterval;
this.remainingTicks = other.remainingTicks;
this.elapsedTime = other.elapsedTime;
}

@Nullable
@Override
public Component<EntityStore> clone() {
return new PoisonComponent(this);
}

public float getDamagePerTick() {
return damagePerTick;
}

public float getTickInterval() {
return tickInterval;
}

public int getRemainingTicks() {
return remainingTicks;
}

public float getElapsedTime() {
return elapsedTime;
}

public void addElapsedTime(float dt) {
this.elapsedTime += dt;
}

public void resetElapsedTime() {
this.elapsedTime = 0f;
}

public void decrementRemainingTicks() {
this.remainingTicks--;
}

public boolean isExpired() {
return this.remainingTicks <= 0;
}
}
```

```java
package scot.oskar.hytaletemplate.systems;

import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.SystemGroup;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import scot.oskar.hytaletemplate.components.PoisonComponent;

public class PoisonSystem extends EntityTickingSystem<EntityStore> {

private final ComponentType<EntityStore, PoisonComponent> poisonComponentType;

public PoisonSystem(ComponentType<EntityStore, PoisonComponent> poisonComponentType) {
this.poisonComponentType = poisonComponentType;
}

@Override
public void tick(float dt, int index, @Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer) {

PoisonComponent poison = archetypeChunk.getComponent(index, poisonComponentType);
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);

poison.addElapsedTime(dt);

if (poison.getElapsedTime() >= poison.getTickInterval()) {
poison.resetElapsedTime();

Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.OUT_OF_WORLD, poison.getDamagePerTick());
DamageSystems.executeDamage(ref, commandBuffer, damage);

poison.decrementRemainingTicks();
}

if (poison.isExpired()) {
commandBuffer.removeComponent(ref, poisonComponentType);
}
}

@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return DamageModule.get().getGatherDamageGroup();
}

@Nonnull
@Override
public Query<EntityStore> getQuery() {
return Query.and(this.poisonComponentType);
}
}
```

```java
package scot.oskar.hytaletemplate.commands;

public class ExampleCommand extends AbstractPlayerCommand {

public ExampleCommand() {
super("test", "Super test command!");
}

@Override
protected void execute(@Nonnull CommandContext commandContext, @Nonnull Store<EntityStore> store,
@Nonnull Ref<EntityStore> ref, @Nonnull PlayerRef playerRef, @Nonnull World world) {
Player player = store.getComponent(ref, Player.getComponentType());
PoisonComponent poison = new PoisonComponent(3f, 0.5f, 8);
store.addComponent(ref, ExamplePlugin.get().getPoisonComponentType(), poison);
player.sendMessage(Message.raw("You have been poisoned!").color(Color.GREEN).bold(true));
}
}
```

```java
package scot.oskar.hytaletemplate;

public final class ExamplePlugin extends JavaPlugin {

private static ExamplePlugin instance;
private ComponentType<EntityStore, PoisonComponent> poisonComponent;

public ExamplePlugin(@Nonnull JavaPluginInit init) {
super(init);
instance = this;
}

@Override
protected void setup() {
this.getCommandRegistry().registerCommand(new ExampleCommand());
this.getEventRegistry().registerGlobal(PlayerReadyEvent.class, ExampleEvent::onPlayerReady);
this.getEventRegistry().registerGlobal(PlayerChatEvent.class, ChatFormatter::onPlayerChat);

this.poisonComponent = this.getEntityStoreRegistry()
.registerComponent(PoisonComponent.class, PoisonComponent::new);
this.getEntityStoreRegistry().registerSystem(new PoisonSystem(this.poisonComponent));
}

public ComponentType<EntityStore, PoisonComponent> getPoisonComponentType() {
return poisonComponent;
}

public static ExamplePlugin get() {
return instance;
}
}
```

The query uses only `poisonComponentType` which means the system will process any entity with a `PoisonComponent`, not just players. This makes it flexible for poisoning NPCs, mobs, or any living entity. The system places itself in the `GatherDamageGroup` so the damage it creates flows through the full damage pipeline including armor reduction and invulnerability checks.
Loading
Loading