@benev/archimedes 0.1.0-3 → 0.1.0-5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -10
- package/package.json +4 -4
- package/s/ecs/index.ts +6 -5
- package/s/ecs/parts/apply-delta.ts +37 -0
- package/s/ecs/parts/change.ts +39 -0
- package/s/ecs/parts/entities.ts +98 -0
- package/s/ecs/parts/execute-systems.ts +21 -0
- package/s/ecs/parts/lifecycle.ts +29 -0
- package/s/ecs/parts/types.ts +28 -0
- package/s/ecs/test/setup-example.ts +50 -0
- package/s/ecs/test.ts +114 -61
- package/x/ecs/index.d.ts +6 -4
- package/x/ecs/index.js +6 -4
- package/x/ecs/index.js.map +1 -1
- package/x/ecs/parts/apply-delta.d.ts +3 -0
- package/x/ecs/parts/apply-delta.js +34 -0
- package/x/ecs/parts/apply-delta.js.map +1 -0
- package/x/ecs/parts/change.d.ts +15 -0
- package/x/ecs/parts/change.js +35 -0
- package/x/ecs/parts/change.js.map +1 -0
- package/x/ecs/parts/entities.d.ts +11 -0
- package/x/ecs/parts/entities.js +70 -0
- package/x/ecs/parts/entities.js.map +1 -0
- package/x/ecs/parts/execute-systems.d.ts +3 -0
- package/x/ecs/parts/execute-systems.js +14 -0
- package/x/ecs/parts/execute-systems.js.map +1 -0
- package/x/ecs/parts/lifecycle.d.ts +2 -0
- package/x/ecs/parts/lifecycle.js +20 -0
- package/x/ecs/parts/lifecycle.js.map +1 -0
- package/x/ecs/parts/types.d.ts +23 -0
- package/x/ecs/parts/types.js +9 -0
- package/x/ecs/parts/types.js.map +1 -0
- package/x/ecs/test/setup-example.d.ts +22 -0
- package/x/ecs/test/setup-example.js +33 -0
- package/x/ecs/test/setup-example.js.map +1 -0
- package/x/ecs/test.d.ts +6 -2
- package/x/ecs/test.js +111 -62
- package/x/ecs/test.js.map +1 -1
- package/s/ecs/parts/changers.ts +0 -20
- package/s/ecs/parts/world.ts +0 -65
- package/s/ecs/test/setup-example-world.ts +0 -42
- package/s/ecs/types.ts +0 -24
- package/s/ecs/utils/apply-change.ts +0 -32
- package/s/ecs/utils/is-match.ts +0 -7
- package/s/ecs/utils/optimizer.ts +0 -38
- package/x/ecs/parts/changers.d.ts +0 -5
- package/x/ecs/parts/changers.js +0 -15
- package/x/ecs/parts/changers.js.map +0 -1
- package/x/ecs/parts/world.d.ts +0 -11
- package/x/ecs/parts/world.js +0 -58
- package/x/ecs/parts/world.js.map +0 -1
- package/x/ecs/test/setup-example-world.d.ts +0 -10
- package/x/ecs/test/setup-example-world.js +0 -31
- package/x/ecs/test/setup-example-world.js.map +0 -1
- package/x/ecs/types.d.ts +0 -21
- package/x/ecs/types.js +0 -6
- package/x/ecs/types.js.map +0 -1
- package/x/ecs/utils/apply-change.d.ts +0 -2
- package/x/ecs/utils/apply-change.js +0 -30
- package/x/ecs/utils/apply-change.js.map +0 -1
- package/x/ecs/utils/is-match.d.ts +0 -2
- package/x/ecs/utils/is-match.js +0 -4
- package/x/ecs/utils/is-match.js.map +0 -1
- package/x/ecs/utils/optimizer.d.ts +0 -9
- package/x/ecs/utils/optimizer.js +0 -37
- package/x/ecs/utils/optimizer.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,19 +1,89 @@
|
|
|
1
1
|
|
|
2
|
-

|
|
3
3
|
|
|
4
|
-
#
|
|
4
|
+
# 🌀 archimedes, netlogic for multiplayer web games
|
|
5
5
|
|
|
6
|
-
> [***"do not disturb my circles!"***](https://en.wikipedia.org/wiki/noli_turbare_circulos_meos!)
|
|
6
|
+
> [***"do not disturb my circles!"***](https://en.wikipedia.org/wiki/noli_turbare_circulos_meos!)
|
|
7
7
|
> — *archimedes, c. 212 bc*
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install @benev/archimedes
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
- 🧩 [**#ecs,**](#ecs) entities, components, systems.
|
|
14
|
+
- 🔮 [**#sim,**](#sim) code like it's single-player, archimedes makes it multiplayer.
|
|
15
|
+
- 🌎 [**#net,**](#net) whole-world rollforward, everything is clientside predicted for insta-feels.
|
|
13
16
|
|
|
14
|
-
🌎 **whole-world rollforward**
|
|
15
|
-
clientside prediction applies to the whole simulation. all effects of player inputs feel instant.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
<br/><a id="ecs"></a>
|
|
20
|
+
|
|
21
|
+
## 🧩 ecs — entities, components, systems
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {Entities, asSystems, makeId, executeSystems} from "@benev/archimedes"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
1. ***define components.*** json-friendly data that entities could have.
|
|
28
|
+
```ts
|
|
29
|
+
export type MyComponents = {
|
|
30
|
+
health: number
|
|
31
|
+
bleed: number
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
1. ***create entities map.*** indexed for speedy-fast lookups.
|
|
35
|
+
```ts
|
|
36
|
+
export const entities = new Entities<MyComponents>()
|
|
37
|
+
```
|
|
38
|
+
1. ***define systems.*** select entities by components. formal changes.
|
|
39
|
+
```ts
|
|
40
|
+
const systems = asSystems<MyComponents>(
|
|
41
|
+
function bleeding(entities, change) {
|
|
42
|
+
for (const [id, components] of entities.select("health", "bleed")) {
|
|
43
|
+
if (components.bleed > 0) {
|
|
44
|
+
const health = components.health - components.bleed
|
|
45
|
+
change.merge(id, {health})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
function death(entities, change) {
|
|
51
|
+
for (const [id, components] of entities.select("health")) {
|
|
52
|
+
if (components.health <= 0)
|
|
53
|
+
change.delete(id)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
1. ***manually insert your first entity.***
|
|
59
|
+
```ts
|
|
60
|
+
const wizardId = makeId()
|
|
61
|
+
entities.set(wizardId, {health: 100, bleed: 2})
|
|
62
|
+
|
|
63
|
+
console.log(entities.get(wizardId)?.health)
|
|
64
|
+
// 100
|
|
65
|
+
```
|
|
66
|
+
1. ***execute systems to simulate each tick.***
|
|
67
|
+
```ts
|
|
68
|
+
executeSystems(entities, systems)
|
|
69
|
+
|
|
70
|
+
console.log(entities.get(wizardId)?.health)
|
|
71
|
+
// 98
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
<br/><a id="sim"></a>
|
|
77
|
+
|
|
78
|
+
## 🔮 sim — networkable simulation architecture
|
|
79
|
+
|
|
80
|
+
*coming soon*
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
<br/><a id="net"></a>
|
|
85
|
+
|
|
86
|
+
## 🌎 net — connect and run multiplayer games
|
|
87
|
+
|
|
88
|
+
*coming soon*
|
|
19
89
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benev/archimedes",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-5",
|
|
4
4
|
"description": "game ecs with auto networking",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,20 +10,20 @@
|
|
|
10
10
|
"s"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "
|
|
13
|
+
"build": "rm -rf x && mkdir x && tsc",
|
|
14
14
|
"dev": "octo 'node --watch x/test.js' 'tsc -w'",
|
|
15
15
|
"test": "node x/test.js",
|
|
16
16
|
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@e280/renraku": "^0.5.7",
|
|
20
|
-
"@e280/stz": "^0.2.
|
|
20
|
+
"@e280/stz": "^0.2.29",
|
|
21
21
|
"@msgpack/msgpack": "^3.1.3",
|
|
22
22
|
"sparrow-rtc": "^0.2.15"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@e280/octo": "^0.1.0",
|
|
26
|
-
"@e280/science": "^0.1.
|
|
26
|
+
"@e280/science": "^0.1.10",
|
|
27
27
|
"npm-run-all": "^4.1.5",
|
|
28
28
|
"typescript": "^6.0.2"
|
|
29
29
|
},
|
package/s/ecs/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
export * from "./parts/
|
|
3
|
-
export *
|
|
2
|
+
export * from "./parts/apply-delta.js"
|
|
3
|
+
export * from "./parts/change.js"
|
|
4
|
+
export * from "./parts/entities.js"
|
|
5
|
+
export * from "./parts/execute-systems.js"
|
|
6
|
+
export * from "./parts/lifecycle.js"
|
|
4
7
|
export * from "./parts/make-id.js"
|
|
5
|
-
export * from "./parts/
|
|
6
|
-
|
|
7
|
-
export * from "./types.js"
|
|
8
|
+
export * from "./parts/types.js"
|
|
8
9
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
import {Entities} from "./entities.js"
|
|
3
|
+
import {DeltaSet, Delta, Components, DeltaKind, DeltaMerge, DeltaDrop} from "./types.js"
|
|
4
|
+
|
|
5
|
+
export function applyDelta<C extends Components>(entities: Entities<C>, delta: Delta<C>) {
|
|
6
|
+
switch (delta[0]) {
|
|
7
|
+
case DeltaKind.Set: return applySet<C>(entities, <DeltaSet<C>>delta)
|
|
8
|
+
case DeltaKind.Merge: return applyMerge<C>(entities, <DeltaMerge<C>>delta)
|
|
9
|
+
case DeltaKind.Drop: return applyDrop<C>(entities, <DeltaDrop<C>>delta)
|
|
10
|
+
default: throw new Error(`unknown delta kind "${delta[0]}"`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function applySet<C extends Components>(entities: Entities<C>, [, id, components]: DeltaSet<C>) {
|
|
15
|
+
if (components) entities.set(id, components as Partial<C>)
|
|
16
|
+
else entities.delete(id)
|
|
17
|
+
return id
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function applyMerge<C extends Components>(entities: Entities<C>, [, id, patch]: DeltaMerge<C>) {
|
|
21
|
+
const components = entities.get(id)
|
|
22
|
+
if (!components)
|
|
23
|
+
return id
|
|
24
|
+
Object.assign(components, patch)
|
|
25
|
+
entities.set(id, components)
|
|
26
|
+
return id
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function applyDrop<C extends Components>(entities: Entities<C>, [, id, keys]: DeltaDrop<C>) {
|
|
30
|
+
const components = entities.get(id)
|
|
31
|
+
if (!components)
|
|
32
|
+
return id
|
|
33
|
+
for (const key of keys) delete components[key]
|
|
34
|
+
entities.set(id, components)
|
|
35
|
+
return id
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
import {makeId} from "./make-id.js"
|
|
3
|
+
import {Components, Id, DeltaKind, Delta} from "./types.js"
|
|
4
|
+
|
|
5
|
+
export class Change<C extends Components> {
|
|
6
|
+
constructor(private commit: (delta: Delta<C>) => void) {}
|
|
7
|
+
|
|
8
|
+
/** create a new entity with the given components */
|
|
9
|
+
create(components: Partial<C>) {
|
|
10
|
+
const id = makeId()
|
|
11
|
+
this.commit([DeltaKind.Set, id, components])
|
|
12
|
+
return id
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** overwrite a whole entity to the given components */
|
|
16
|
+
set(id: Id, components: Partial<C>) {
|
|
17
|
+
this.commit([DeltaKind.Set, id, components])
|
|
18
|
+
return id
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** remove the entity */
|
|
22
|
+
delete(id: Id) {
|
|
23
|
+
this.commit([DeltaKind.Set, id])
|
|
24
|
+
return id
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** update or add the given components onto the entity */
|
|
28
|
+
merge(id: Id, components: Partial<C>) {
|
|
29
|
+
this.commit([DeltaKind.Merge, id, components])
|
|
30
|
+
return id
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** delete specific components off the entity */
|
|
34
|
+
drop(id: Id, ...keys: (keyof C)[]) {
|
|
35
|
+
this.commit([DeltaKind.Drop, id, keys])
|
|
36
|
+
return id
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
import {GMap} from "@e280/stz"
|
|
3
|
+
import {Components, Id, Select} from "./types.js"
|
|
4
|
+
|
|
5
|
+
export class Entities<C extends Components> extends GMap<Id, Partial<C>> {
|
|
6
|
+
#index = new GMap<Set<keyof C>, GMap<Id, Partial<C>>>()
|
|
7
|
+
|
|
8
|
+
set(id: Id, components: Partial<C>) {
|
|
9
|
+
super.set(id, components)
|
|
10
|
+
for (const [set, entities] of this.#index) {
|
|
11
|
+
if (componentsSatisfySet(components, set))
|
|
12
|
+
entities.set(id, components)
|
|
13
|
+
else
|
|
14
|
+
entities.delete(id)
|
|
15
|
+
}
|
|
16
|
+
return this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
delete(id: Id) {
|
|
20
|
+
const didDelete = super.delete(id)
|
|
21
|
+
if (!didDelete)
|
|
22
|
+
return false
|
|
23
|
+
|
|
24
|
+
for (const entities of this.#index.values())
|
|
25
|
+
entities.delete(id)
|
|
26
|
+
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clear() {
|
|
31
|
+
super.clear()
|
|
32
|
+
for (const entities of this.#index.values())
|
|
33
|
+
entities.clear()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
*select<K extends keyof C>(...componentKeys: K[]): Iterable<[Id, Select<C, K>]> {
|
|
37
|
+
const cached = this.#getCache(componentKeys)
|
|
38
|
+
if (cached)
|
|
39
|
+
yield* cached
|
|
40
|
+
else
|
|
41
|
+
yield* this.#makeCache(componentKeys)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
readonly() {
|
|
45
|
+
return this as EntitiesReadonly<C>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#getCache<K extends keyof C>(componentKeys: K[]) {
|
|
49
|
+
for (const set of this.#index.keys()) {
|
|
50
|
+
if (setHasSameValuesAsArray(set, componentKeys))
|
|
51
|
+
return this.#index.require(set) as GMap<Id, Select<C, K>>
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
*#makeCache<K extends keyof C>(componentKeys: K[]) {
|
|
56
|
+
const set = new Set(componentKeys)
|
|
57
|
+
const entities = new GMap<Id, Partial<C>>()
|
|
58
|
+
this.#index.set(set, entities)
|
|
59
|
+
|
|
60
|
+
for (const entity of this) {
|
|
61
|
+
const [id, components] = entity
|
|
62
|
+
|
|
63
|
+
if (componentsSatisfyKeys(components, componentKeys)) {
|
|
64
|
+
entities.set(id, components)
|
|
65
|
+
yield entity as [Id, Select<C, K>]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type EntitiesReadonly<C extends Components> = Pick<Entities<Readonly<C>>, (
|
|
72
|
+
| "has"
|
|
73
|
+
| "get"
|
|
74
|
+
| "require"
|
|
75
|
+
| "keys"
|
|
76
|
+
| "values"
|
|
77
|
+
| "entries"
|
|
78
|
+
| "select"
|
|
79
|
+
| typeof Symbol.iterator
|
|
80
|
+
)>
|
|
81
|
+
|
|
82
|
+
function componentsSatisfySet(components: Components, set: Set<PropertyKey>) {
|
|
83
|
+
for (const key of set)
|
|
84
|
+
if (!(key in components))
|
|
85
|
+
return false
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function setHasSameValuesAsArray(set: Set<unknown>, keys: unknown[]) {
|
|
90
|
+
if (set.size !== keys.length)
|
|
91
|
+
return false
|
|
92
|
+
return keys.every(key => set.has(key))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function componentsSatisfyKeys(components: object, keys: PropertyKey[]) {
|
|
96
|
+
return keys.every(key => key in components)
|
|
97
|
+
}
|
|
98
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import {Change} from "./change.js"
|
|
3
|
+
import {Entities} from "./entities.js"
|
|
4
|
+
import {applyDelta} from "./apply-delta.js"
|
|
5
|
+
import {Delta, Components, System} from "./types.js"
|
|
6
|
+
|
|
7
|
+
export function executeSystems<C extends Components>(entities: Entities<C>, systems: System<C>[]) {
|
|
8
|
+
const entitiesReadonly = entities.readonly()
|
|
9
|
+
const deltas: Delta<C>[] = []
|
|
10
|
+
|
|
11
|
+
const change = new Change<C>(delta => {
|
|
12
|
+
applyDelta(entities, delta)
|
|
13
|
+
deltas.push(delta)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
for (const system of systems)
|
|
17
|
+
system(entitiesReadonly, change)
|
|
18
|
+
|
|
19
|
+
return deltas
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import {GMap} from "@e280/stz"
|
|
3
|
+
import {Components, Id, LifecycleCallbacks, LifecycleEnter, System} from "./types.js"
|
|
4
|
+
|
|
5
|
+
export function lifecycle<C extends Components, K extends keyof C>(
|
|
6
|
+
componentKeys: K[],
|
|
7
|
+
enter: LifecycleEnter<C, K>
|
|
8
|
+
): System<C> {
|
|
9
|
+
|
|
10
|
+
const alive = new GMap<Id, LifecycleCallbacks<C, K>>()
|
|
11
|
+
|
|
12
|
+
return (entities, commit) => {
|
|
13
|
+
|
|
14
|
+
// add fresh entities
|
|
15
|
+
for (const [id, components] of entities.select(...componentKeys)) {
|
|
16
|
+
const callbacks = alive.guarantee(id, () => enter(id, components, commit))
|
|
17
|
+
callbacks.tick(id, components)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// delete stale entities
|
|
21
|
+
const currentIds = new Set([...entities.select(...componentKeys)].map(([id]) => id))
|
|
22
|
+
for (const [id, callbacks] of alive) {
|
|
23
|
+
if (currentIds.has(id)) continue
|
|
24
|
+
alive.delete(id)
|
|
25
|
+
callbacks.exit(id)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import {Change} from "./change.js"
|
|
3
|
+
import {EntitiesReadonly} from "./entities.js"
|
|
4
|
+
|
|
5
|
+
export type Id = string
|
|
6
|
+
export type Components = Record<string, unknown>
|
|
7
|
+
export type AsComponents<C extends Components> = C
|
|
8
|
+
export type Select<C extends Components, K extends keyof C> = Pick<C, K> & Partial<C>
|
|
9
|
+
|
|
10
|
+
export enum DeltaKind {Set, Merge, Drop}
|
|
11
|
+
export type DeltaSet<C extends Components> = [kind: DeltaKind.Set, id: Id, components?: Partial<C>]
|
|
12
|
+
export type DeltaMerge<C extends Components> = [kind: DeltaKind.Merge, id: Id, patch: Partial<C>]
|
|
13
|
+
export type DeltaDrop<C extends Components> = [kind: DeltaKind.Drop, id: Id, keys: (keyof C)[]]
|
|
14
|
+
export type Delta<C extends Components> = DeltaSet<C> | DeltaMerge<C> | DeltaDrop<C>
|
|
15
|
+
|
|
16
|
+
export type System<C extends Components> = (entities: EntitiesReadonly<C>, change: Change<C>) => void
|
|
17
|
+
export const asSystem = <C extends Components>(system: System<C>) => system
|
|
18
|
+
export const asSystems = <C extends Components>(...systems: System<C>[]) => systems
|
|
19
|
+
|
|
20
|
+
export type LifecycleCallbacks<C extends Components, K extends keyof C> = {
|
|
21
|
+
tick: (id: Id, components: Select<C, K>) => void
|
|
22
|
+
exit: (id: Id) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type LifecycleEnter<C extends Components, K extends keyof C> = (
|
|
26
|
+
(id: Id, components: Select<C, K>, change: Change<C>) => LifecycleCallbacks<C, K>
|
|
27
|
+
)
|
|
28
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
import {Change} from "../parts/change.js"
|
|
3
|
+
import {asSystems} from "../parts/types.js"
|
|
4
|
+
import {Entities} from "../parts/entities.js"
|
|
5
|
+
import {applyDelta} from "../parts/apply-delta.js"
|
|
6
|
+
|
|
7
|
+
export function setupExample() {
|
|
8
|
+
type MyComponents = {
|
|
9
|
+
health: number
|
|
10
|
+
bleed: number
|
|
11
|
+
mana: number
|
|
12
|
+
manaRegen: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const systems = asSystems<MyComponents>(
|
|
16
|
+
function manaRegen(entities, change) {
|
|
17
|
+
for (const [id, components] of entities.select("mana", "manaRegen")) {
|
|
18
|
+
if (components.manaRegen !== 0) {
|
|
19
|
+
const mana = components.mana + components.manaRegen
|
|
20
|
+
change.merge(id, {mana})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
function bleeding(entities, change) {
|
|
26
|
+
for (const [id, components] of entities.select("health", "bleed")) {
|
|
27
|
+
if (components.bleed >= 0) {
|
|
28
|
+
const health = components.health - components.bleed
|
|
29
|
+
const bleed = components.bleed - 1
|
|
30
|
+
change.merge(id, {health, bleed})
|
|
31
|
+
}
|
|
32
|
+
if (components.bleed <= 0)
|
|
33
|
+
change.drop(id, "bleed")
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
function death(entities, change) {
|
|
38
|
+
for (const [id, components] of entities.select("health")) {
|
|
39
|
+
if (components.health <= 0)
|
|
40
|
+
change.delete(id)
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const entities = new Entities<MyComponents>()
|
|
46
|
+
const change = new Change<MyComponents>(delta => applyDelta(entities, delta))
|
|
47
|
+
|
|
48
|
+
return {systems, entities, change}
|
|
49
|
+
}
|
|
50
|
+
|