feat: add code

master
Mark Beck 2025-11-04 23:19:26 +01:00
parent 07545d30db
commit 289b635077
30 changed files with 1010 additions and 1 deletions

2
.gitignore vendored
View File

@ -153,3 +153,5 @@ fabric.properties
# Built Visual Studio Code Extensions
*.vsix
.mvn/
target/

136
README.md
View File

@ -1,2 +1,136 @@
# EntityComponentSystem
# Entity Component System
Nachdem das von ihnen entwickelte Spiel Racewars veröffentlicht wurde, bekommen sie einige beschwerden über schlechte Performance. Obwohl sie der Meinung sind, dass sich die Leute einfach einen neuen PC kaufen sollen, will ihr Arbeitgeber, dass sie das Spiel optimieren.
Nach einiger Recherche stellen sie fest, dass die derzeitige Implementierung der Wesen Klasse zu schlechter Performance führt.
Ihre Wesen sehen im Arbeitsspeicher etwa so aus:
```
┌─────────────────────────────────────────────┐
│ Wesen Objekt │
├─────────────────────────────────────────────┤
│ │
│ vtable pointer (8 bytes) │
│ │
├─────────────────────────────────────────────┤
│ │
│ geschwindigkeit : int (4 bytes) │
│ │
├─────────────────────────────────────────────┤
│ │
│ schaden : int (4 bytes) │
│ │
├─────────────────────────────────────────────┤
│ │
│ ruestung : int (4 bytes) │
│ │
├─────────────────────────────────────────────┤
│ │
│ lebenspunkte : double (8 bytes) │
│ │
└─────────────────────────────────────────────┘
```
Prozessoren laden Daten aus dem Arbeitsspeicher allerdings meist in Blöcken von 64 Byte. Werden nun zum Beipiel bei der Ausgabe der Einheiten nur die Lebenspunkte von jeder Einheit gebraucht, muss der CPU das gesamte Objekt aus dem Arbeitsspeicher laden, obwohl ein Großteil nicht gebraucht wird.
In vielen modernen Computerspielen wird diese ineffizienz umgangen, indem Spielobjekte nicht als einzelne Java-Objekte, sondern als eine Ansammlung von Komponenten gespeichert werden:
```
┌───────────────────────────────────────────────────────────────────────────┐
│ Entity Component System (ECS) Layout │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Entity Table │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ Entity 1 │ Entity 2 │ Entity 3 │ Entity 4 │ (int[], 4 bytes) │
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Geschwindigkeit Component │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ 25 │ 40 │ 30 │ 35 │ (int[], 4 bytes) │
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Schaden Component │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ 10 │ 15 │ 12 │ 18 │ (int[], 4 bytes) │
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Ruestung Component │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ 5 │ 8 │ 6 │ 3 │ (int[], 4 bytes) │
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Lebenspunkte Component (Structure of Arrays): │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ 100.0 │ 85.5 │ 92.3 │ 78.9 │ (double[], 8 bytes)│
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘
```
Werden jetzt wie im vorherigen Beispiel die Lebenspunkte aller Wesen gebraucht, liegen diese im Arbeitsspeicher nebeneinander.
Ein Entity-Component-System besteht aus vier Konzepten:
### Entities
Entities sind Spielobjekte, sie sind meist nicht mehr als eine fortlaufende ID.
### Components
Components sind die zum Spielobjekt gehörenden Daten. Wie viele Daten jede Art von Component enthält kann verschieden sein.
Zum Beispiel kann ein `Lebenspunkte` Component nur die derzeitigen Lebenspunkte enthalten, während ein `Kampfkraft` Component Schaden, Geschwindigkeit und Rüstung enthält.
### Registry
Die Registry ist eine Datenbank, die alle Entities ihren Components zuweist und folgende Operationen Erlaubt:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ registerComponentType(componentClass) │
│ └─ Registriert eine neue Art von Component zur Verwaltung │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ addComponent(entity, component) │
│ └─ Fügt der Entity einen neuen Component hinzu │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ removeComponent(entity, componentType) │
│ └─ Löscht den angegebenen Component von der Entity │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ hasComponent(entity, componentType) → boolean │
│ └─ Prüft, ob die Entity einen Component diesen Types hat │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ getComponent(entity, componentType) → component │
│ └─ Gibt den angegebenen Component für die Entity zurück │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ withComponents(componentType...) → entity[] │
│ └─ Gibt alle Entities zurück, die alle angegebenen Components haben │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ removeEntity(entity) │
│ └─ Löscht die Entity mit allen ihren Components │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### Sytems
Systems bilden die Anwendungslogik ab. Sie können die Registry nach Entities abfragen und neue Entities oder Components hinzufügen

98
pom.xml 100644
View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>racewars</artifactId>
<version>1.0-SNAPSHOT</version>
<name>racewars</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<!-- Optionally: parameterized tests support -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.6.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<mainClass>com.example.App</mainClass>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.example;
import com.example.controller.GameController;
/**
* Hello world!
*/
public class App {
public static void main(String[] args) {
GameController gc = new GameController();
gc.runGame();
}
}

View File

@ -0,0 +1,12 @@
package com.example.components;
import com.example.ecs.Component;
import com.example.ecs.Entity;
public record Attack(
Entity source,
Entity target,
double damage
) implements Component {
}

View File

@ -0,0 +1,8 @@
package com.example.components;
import com.example.ecs.Component;
import com.example.ecs.Entity;
public record Battle(Entity squad1, Entity squad2) implements Component {
}

View File

@ -0,0 +1,11 @@
package com.example.components;
import com.example.ecs.Component;
import com.example.ecs.Entity;
public record CombatLog (
Entity attacker,
Entity defender,
double initialDamage,
double resultingDamage
) implements Component {}

View File

@ -0,0 +1,25 @@
package com.example.components;
import com.example.ecs.Component;
public class Health implements Component {
private final double maxHp;
private double hp;
public Health(double maxHp, double hp) {
this.maxHp = maxHp;
this.hp = hp;
}
public double hp() {
return hp;
}
public double maxHp() {
return maxHp;
}
public void lowerHp(double amount) {
hp -= amount;
}
}

View File

@ -0,0 +1,11 @@
package com.example.components;
import com.example.ecs.Component;
import com.example.model.Element;
public record Hero(
String name,
double bonus,
Element element
) implements Component {
}

View File

@ -0,0 +1,7 @@
package com.example.components;
import com.example.ecs.Component;
public record HumanDefense(
double defense
) implements Component {}

View File

@ -0,0 +1,13 @@
package com.example.components;
import com.example.ecs.Component;
import com.example.ecs.Entity;
import com.example.model.Race;
public record Investment(
int amount,
Race race,
boolean witLeader,
Entity forSquad
) implements Component {
}

View File

@ -0,0 +1,28 @@
package com.example.components;
import java.util.List;
import com.example.ecs.Component;
import com.example.ecs.Entity;
public class Squad implements Component {
private String name;
private List<Entity> units;
public Squad(String name, List<Entity> units) {
this.name = name;
this.units = units;
}
public void removeUnit(Entity entity) {
units.remove(entity);
}
public String name() {
return name;
}
public List<Entity> units() {
return units;
}
}

View File

@ -0,0 +1,12 @@
package com.example.components;
import com.example.ecs.Component;
import com.example.model.Race;
public record Stats(
double dmg,
double speed,
double armor,
Race race
) implements Component {
}

View File

@ -0,0 +1,70 @@
package com.example.controller;
import com.example.components.Attack;
import com.example.components.Battle;
import com.example.components.CombatLog;
import com.example.components.Health;
import com.example.components.Hero;
import com.example.components.HumanDefense;
import com.example.components.Investment;
import com.example.components.Squad;
import com.example.components.Stats;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
import com.example.ecs.System;
import com.example.model.Race;
import com.example.systems.CleanupSystem;
import com.example.systems.CombatSystem;
import com.example.systems.DamageSystem;
import com.example.systems.RenderSystem;
import com.example.systems.SquadGenerationSystem;
public class GameController {
Registry registry = new Registry();
SquadGenerationSystem squadGenerationSystem;
System combatSystem;
System damageSystem;
System renderSystem;
CleanupSystem cleanupSystem;
public GameController() {
registry.registerComponentType(Attack.class);
registry.registerComponentType(Battle.class);
registry.registerComponentType(CombatLog.class);
registry.registerComponentType(Health.class);
registry.registerComponentType(Hero.class);
registry.registerComponentType(HumanDefense.class);
registry.registerComponentType(Investment.class);
registry.registerComponentType(Squad.class);
registry.registerComponentType(Stats.class);
squadGenerationSystem = new SquadGenerationSystem(registry);
combatSystem = new CombatSystem(registry);
damageSystem = new DamageSystem(registry);
renderSystem = new RenderSystem(registry);
cleanupSystem = new CleanupSystem(registry);
Entity squad1 = new Entity();
Entity squad2 = new Entity();
registry.addComponent(new Entity(), new Investment(2000, Race.Human, true, squad1));
registry.addComponent(new Entity(), new Investment(2000, Race.Orc, true, squad2));
squadGenerationSystem.generateSquad(squad1, "Squad1", 2000);
squadGenerationSystem.generateSquad(squad2, "Squad2", 2000);
registry.addComponent(new Entity(), new Battle(squad1, squad2));
}
public void runGame() {
while (!cleanupSystem.gameEnded()) {
combatSystem.run();
damageSystem.run();
renderSystem.run();
cleanupSystem.run();
}
}
}

View File

@ -0,0 +1,4 @@
package com.example.ecs;
public interface Component {
}

View File

@ -0,0 +1,37 @@
package com.example.ecs;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
class ComponentManager<T extends Component> {
private final Map<Integer, T> entityComponents;
protected ComponentManager() {
entityComponents = new HashMap<>();
}
protected void addComponent(Entity entity, T component) {
entityComponents.put(entity.getId(), component);
}
protected void removeComponent(Entity entity) {
entityComponents.remove(entity.id);
}
protected T getComponent(Entity entity) {
return entityComponents.get(Integer.valueOf(entity.getId()));
}
public boolean hasComponent(Entity entity) {
return entityComponents.containsKey(entity.getId());
}
protected Set<Entity> getEntities() {
return entityComponents.keySet().stream()
.map((Integer id) -> { return new Entity(id); })
.collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,38 @@
package com.example.ecs;
import java.util.Objects;
public class Entity {
static int count = 0;
int id;
public Entity() {
this.id = count++;
}
protected Entity(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
@Override
public boolean equals(Object o) {
if (o instanceof Entity e) {
return this.getId() == e.getId();
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Entity{" + "id=" + id + '}';
}
}

View File

@ -0,0 +1,60 @@
package com.example.ecs;
import java.util.HashMap;
import java.util.Set;
public class Registry {
HashMap<Class<? extends Component>, ComponentManager<?>> componentManagers = new HashMap<>();
public <T extends Component> void registerComponentType(Class<T> dataClass) {
componentManagers.put(dataClass, new ComponentManager<T>());
}
public <T extends Component> void addComponent(Entity entity, T component) {
Class<T> componentClass = (Class<T>) component.getClass();
getComponentManager(componentClass).addComponent(entity, component);
}
public <T extends Component> T getComponent(Entity entity, Class<T> componentClass) {
return getComponentManager(componentClass).getComponent(entity);
}
public <T extends Component> boolean hasComponent(Entity entity, Class<T> componentClass) {
try {
var component = getComponentManager(componentClass).getComponent(entity);
return component != null;
} catch(Exception e) {
return false;
}
}
public Entity[] getWithComponents(Class<? extends Component>... components) {
if(components.length == 0) {
throw new IllegalArgumentException();
}
Set<Entity> entitySet = getComponentManager(components[0]).getEntities();
for (int i = 1; i < components.length; i++) {
entitySet.retainAll(getComponentManager(components[i]).getEntities());
}
return entitySet.toArray(new Entity[0]);
}
public void remove(Entity entity) {
for (var manager : componentManagers.values()) {
manager.removeComponent(entity);
}
}
public void removeComponent(Entity entity, Class<? extends Component> component) {
getComponentManager(component).removeComponent(entity);
}
private <T extends Component> ComponentManager<T> getComponentManager(Class<T> componentClass) {
Object manager = componentManagers.get(componentClass);
if (manager == null) {
throw new IllegalArgumentException("No component manager for: " + componentClass);
}
return (ComponentManager<T>) manager;
}
}

View File

@ -0,0 +1,11 @@
package com.example.ecs;
public abstract class System {
protected final Registry registry;
public System(Registry registry) {
this.registry = registry;
}
public abstract void run();
}

View File

@ -0,0 +1,8 @@
package com.example.model;
public enum Element {
Fire,
Water,
Earth,
Air
}

View File

@ -0,0 +1,19 @@
package com.example.model;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
public interface Factory {
/**
* Erzeugt ein neues Wesen der Rasse.
* @return das Wesen.
*/
Entity createUnit(Registry registry);
/**
* Liefert den Anführer der Rasse.
* @return Anführer.
*/
Entity createHero(Registry registry);
}

View File

@ -0,0 +1,38 @@
package com.example.model;
import com.example.components.Health;
import com.example.components.Hero;
import com.example.components.HumanDefense;
import com.example.components.Stats;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
public class HumanFactory implements Factory {
@Override
public Entity createUnit(Registry registry) {
Stats stats = new Stats(40, 2, 0.4, Race.Human);
Health health = new Health(140, 140);
HumanDefense humanDefense = new HumanDefense(0.1);
Entity human = new Entity();
registry.addComponent(human, stats);
registry.addComponent(human, health);
registry.addComponent(human, humanDefense);
return human;
}
@Override
public Entity createHero(Registry registry) {
Stats stats = new Stats(40, 2, 0.4, Race.Human);
Health health = new Health(140 * 5, 140 * 5);
HumanDefense humanDefense = new HumanDefense(0.1);
Hero hero = new Hero("Archmage", 5, Element.Fire);
Entity human = new Entity();
registry.addComponent(human, stats);
registry.addComponent(human, health);
registry.addComponent(human, humanDefense);
registry.addComponent(human, hero);
return human;
}
}

View File

@ -0,0 +1,33 @@
package com.example.model;
import com.example.components.Health;
import com.example.components.Hero;
import com.example.components.Stats;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
public class OrcFactory implements Factory {
@Override
public Entity createUnit(Registry registry) {
Stats stats = new Stats(33, 1, 0.3, Race.Orc);
Health health = new Health(140, 140);
Entity orc = new Entity();
registry.addComponent(orc, stats);
registry.addComponent(orc, health);
return orc;
}
@Override
public Entity createHero(Registry registry) {
Stats stats = new Stats(33, 1, 0.3, Race.Orc);
Health health = new Health(140 * 1.2, 140 * 1.2);
Hero hero = new Hero("Farseer", 1.2, Element.Earth);
Entity orc = new Entity();
registry.addComponent(orc, stats);
registry.addComponent(orc, health);
registry.addComponent(orc, hero);
return orc;
}
}

View File

@ -0,0 +1,44 @@
package com.example.model;
public enum Race {
Human {
@Override
public Factory getFactory() {
return humanFactory;
}
@Override
public int getCost() {
return 110;
}
@Override
public int getLeaderCost() {
return 220;
}
},
Orc {
@Override
public Factory getFactory() {
return orcFactory;
}
@Override
public int getCost() {
return 150;
}
@Override
public int getLeaderCost() {
return 300;
}
}
;
static Factory humanFactory = new HumanFactory();
static Factory orcFactory = new OrcFactory();
public abstract Factory getFactory();
public abstract int getCost();
public abstract int getLeaderCost();
}

View File

@ -0,0 +1,58 @@
package com.example.systems;
import com.example.components.Attack;
import com.example.components.Battle;
import com.example.components.Health;
import com.example.ecs.Registry;
import com.example.ecs.System;
import com.example.components.Squad;
public class CleanupSystem extends System {
private boolean gameEnded = false;
public CleanupSystem(Registry registry) {
super(registry);
}
public void run() {
var attacks = registry.getWithComponents(Attack.class);
for (var attack : attacks) {
registry.remove(attack);
}
var units = registry.getWithComponents(Health.class);
for (var unit : units) {
var health = registry.getComponent(unit, Health.class);
if (health.hp() < 0) {
var squads = registry.getWithComponents(Squad.class);
for (var squadEntity : squads) {
var squad = registry.getComponent(squadEntity, Squad.class);
squad.removeUnit(unit);
}
registry.remove(unit);
}
}
var battles = registry.getWithComponents(Battle.class);
for (var battleEntity : battles) {
var battle = registry.getComponent(battleEntity, Battle.class);
var squad1 = registry.getComponent(battle.squad1(), Squad.class);
var squad2 = registry.getComponent(battle.squad2(), Squad.class);
if (squad1.units().isEmpty() || squad2.units().isEmpty()) {
registry.remove(battleEntity);
registry.remove(battle.squad1());
registry.remove(battle.squad2());
}
}
if (registry.getWithComponents(Battle.class).length == 0) {
gameEnded = true;
}
}
public boolean gameEnded() {
return gameEnded;
}
}

View File

@ -0,0 +1,49 @@
package com.example.systems;
import java.util.Random;
import com.example.components.Attack;
import com.example.components.Battle;
import com.example.components.Hero;
import com.example.components.Squad;
import com.example.components.Stats;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
import com.example.ecs.System;
public class CombatSystem extends System {
public CombatSystem(Registry registry) {
super(registry);
}
Random rng = new Random();
public void run() {
for (var battleEntity : registry.getWithComponents(Battle.class)) {
var battle = registry.getComponent(battleEntity, Battle.class);
var squad1 = registry.getComponent(battle.squad1(), Squad.class);
var squad2 = registry.getComponent(battle.squad2(), Squad.class);
attack(squad1, squad2);
attack(squad2, squad1);
}
}
private void attack(Squad attacker, Squad defender) {
for (var entity : attacker.units()) {
var unitData = registry.getComponent(entity, Stats.class);
boolean isHero = registry.hasComponent(entity, Hero.class);
var target = defender.units().get(rng.nextInt(defender.units().size()));
double damageDealt = unitData.dmg() * unitData.speed();
if (isHero) {
var heroData = registry.getComponent(entity, Hero.class);
damageDealt *= heroData.bonus();
}
registry.addComponent(new Entity(), new Attack(entity, target, damageDealt));
}
}
}

View File

@ -0,0 +1,33 @@
package com.example.systems;
import com.example.components.Attack;
import com.example.components.CombatLog;
import com.example.components.Health;
import com.example.components.HumanDefense;
import com.example.components.Stats;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
import com.example.ecs.System;
public class DamageSystem extends System {
public DamageSystem(Registry registry) {
super(registry);
}
public void run() {
for (Entity attackEntity : registry.getWithComponents(Attack.class)) {
Attack attack = registry.getComponent(attackEntity, Attack.class);
Stats targetStats = registry.getComponent(attack.target(), Stats.class);
Health targetHealth = registry.getComponent(attack.target(), Health.class);
double dmgDealt = attack.damage() * (1 - targetStats.armor());
if (registry.hasComponent(attack.target(), HumanDefense.class)) {
HumanDefense defense = registry.getComponent(attack.target(), HumanDefense.class);
dmgDealt -= dmgDealt * defense.defense();
}
targetHealth.lowerHp(dmgDealt);
registry.addComponent(attackEntity, new CombatLog(attack.source(), attack.target(), attack.damage(), dmgDealt));
}
}
}

View File

@ -0,0 +1,58 @@
package com.example.systems;
import com.example.components.CombatLog;
import com.example.components.Health;
import com.example.components.Hero;
import com.example.components.Squad;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
import com.example.ecs.System;
import com.example.components.Stats;
public class RenderSystem extends System {
public RenderSystem(Registry registry) {
super(registry);
}
public void run() {
renderCombat();
var squads = registry.getWithComponents(Squad.class);
for (var squad : squads) {
var squadData = registry.getComponent(squad, Squad.class);
renderSquad(squadData);
}
}
private String generateName(Entity entity) {
if (registry.hasComponent(entity, Hero.class)) {
Hero herodata = registry.getComponent(entity, Hero.class);
return herodata.name();
} else if (registry.hasComponent(entity, Stats.class)) {
Stats stats = registry.getComponent(entity, Stats.class);
return stats.race().toString();
} else {
return "Unknown";
}
}
private void renderCombat() {
for (var entity : registry.getWithComponents(CombatLog.class)) {
CombatLog log = registry.getComponent(entity, CombatLog.class);
IO.println(generateName(log.attacker()) + " ---------[" + log.resultingDamage() + "]-------->" + generateName(log.defender()));
}
IO.println();
}
private void renderSquad(Squad squad) {
IO.println("Squad " + squad.name());
for (var unit : squad.units()) {
var health = registry.getComponent(unit, Health.class);
IO.println(generateName(unit) + " [" + health.hp() + "/" + health.maxHp() + "]");
}
IO.println("-------------------------");
}
}

View File

@ -0,0 +1,56 @@
package com.example.systems;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import com.example.components.Investment;
import com.example.components.Squad;
import com.example.ecs.Entity;
import com.example.ecs.Registry;
public class SquadGenerationSystem {
private final Registry registry;
public SquadGenerationSystem(Registry registry) {
this.registry = registry;
}
public Entity generateSquad(Entity squadEntity, String name, int maxInvestment) {
var investmentEntities = registry.getWithComponents(Investment.class);
List<Entity> units = new ArrayList<>();
for (var investmentEntity : investmentEntities) {
var investment = registry.getComponent(investmentEntity, Investment.class);
if (investment.forSquad().equals(squadEntity)) {
units.addAll(createUnits(investment));
registry.remove(investmentEntity);
}
}
Squad squad = new Squad(name, units);
registry.addComponent(squadEntity, squad);
return squadEntity;
}
private List<Entity> createUnits(Investment investment) {
int moneyRemaining = investment.amount();
List<Entity> leader = new ArrayList<>();
if (investment.witLeader()) {
moneyRemaining -= investment.race().getLeaderCost();
leader.add(investment.race().getFactory().createHero(registry));
}
var amount = moneyRemaining / investment.race().getCost();
List<Entity> squad = Stream.generate(() ->
investment.race().getFactory().createUnit(registry))
.limit(amount)
.toList();
var finalSquad = new ArrayList<Entity>();
finalSquad.addAll(leader);
finalSquad.addAll(squad);
return finalSquad;
}
}

View File

@ -0,0 +1,19 @@
package com.example;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
/**
* Unit test for simple App.
*/
public class AppTest {
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue() {
assertTrue(true);
}
}