feat: entity devtools
This commit is contained in:
parent
21f8864b1b
commit
3df5266ba3
172
app/react/components/devtools/entities.jsx
Normal file
172
app/react/components/devtools/entities.jsx
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import {Map} from 'immutable';
|
||||||
|
import {useCallback, useEffect, useState} from 'react';
|
||||||
|
|
||||||
|
import {useClient} from '@/react/context/client.js';
|
||||||
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
|
|
||||||
|
import styles from './entities.module.css';
|
||||||
|
|
||||||
|
const entityJsonPaths = Object.keys(import.meta.glob('%/**/*.entity.json'));
|
||||||
|
|
||||||
|
function entityLabel(entity) {
|
||||||
|
let label = `${entity.id}`;
|
||||||
|
const {Player, Position} = entity;
|
||||||
|
if (1 === entity.id) {
|
||||||
|
label = 'Master';
|
||||||
|
}
|
||||||
|
if (Player) {
|
||||||
|
label += `: Player (${Player.id})`;
|
||||||
|
}
|
||||||
|
if (Position) {
|
||||||
|
label += `: [${Position.x.toFixed(2)}, ${Position.y.toFixed(2)}]`
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Entities({eventsChannel}) {
|
||||||
|
const client = useClient();
|
||||||
|
const ecsRef = useEcs();
|
||||||
|
const [activeEntity, setActiveEntity] = useState(1);
|
||||||
|
const [creatingEntityPath, setCreatingEntityPath] = useState(entityJsonPaths[0]);
|
||||||
|
const [entities, setEntities] = useState(Map());
|
||||||
|
const onEcsTick = useCallback((payload, ecs) => {
|
||||||
|
setEntities((entities) => {
|
||||||
|
return entities.withMutations((entities) => {
|
||||||
|
if (0 === entities.size) {
|
||||||
|
for (const id in ecs.$$entities) {
|
||||||
|
const entity = ecs.get(id);
|
||||||
|
entities.set(id, entityLabel(entity));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const id in payload) {
|
||||||
|
const update = payload[id];
|
||||||
|
if (false === update) {
|
||||||
|
entities.delete(id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const entity = ecs.get(id);
|
||||||
|
entities.set(id, entityLabel(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
useEcsTick(onEcsTick);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ecsRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const master = ecsRef.current.get(1);
|
||||||
|
if (!master) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {TileLayers} = master;
|
||||||
|
const {size, tileSize} = TileLayers.layer(0);
|
||||||
|
function onClick({x, y}, {shiftKey}) {
|
||||||
|
const at = {
|
||||||
|
x: shiftKey ? Math.floor(x / tileSize.x) * tileSize.x + (tileSize.x / 2) : x,
|
||||||
|
y: shiftKey ? Math.floor(y / tileSize.y) * tileSize.y + (tileSize.y / 2) : y,
|
||||||
|
};
|
||||||
|
if (at.x < 0 || at.y < 0 || at.x >= size.x || at.y >= size.y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entity = ecsRef.current.get(activeEntity);
|
||||||
|
if (!entity.Position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.send({
|
||||||
|
type: 'AdminAction',
|
||||||
|
payload: {
|
||||||
|
type: 'moveEntity',
|
||||||
|
value: {
|
||||||
|
id: activeEntity,
|
||||||
|
at,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
eventsChannel.addListener('click', onClick);
|
||||||
|
return () => {
|
||||||
|
eventsChannel.removeListener('click', onClick);
|
||||||
|
};
|
||||||
|
}, [activeEntity, client, ecsRef, eventsChannel]);
|
||||||
|
const options = [];
|
||||||
|
for (const [id, label] of entities.entries()) {
|
||||||
|
options.push(
|
||||||
|
<option
|
||||||
|
key={id}
|
||||||
|
value={id}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</option>);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.entities}>
|
||||||
|
<form>
|
||||||
|
<div
|
||||||
|
className={styles.activeEntity}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={(event) => {
|
||||||
|
client.send({
|
||||||
|
type: 'AdminAction',
|
||||||
|
payload: {
|
||||||
|
type: 'destroyEntity',
|
||||||
|
value: {
|
||||||
|
id: activeEntity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<select
|
||||||
|
onChange={(event) => {
|
||||||
|
setActiveEntity(event.target.value);
|
||||||
|
}}
|
||||||
|
value={activeEntity}
|
||||||
|
>
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.createEntity}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={(event) => {
|
||||||
|
client.send({
|
||||||
|
type: 'AdminAction',
|
||||||
|
payload: {
|
||||||
|
type: 'createEntity',
|
||||||
|
value: {
|
||||||
|
path: creatingEntityPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
<select
|
||||||
|
onChange={(event) => {
|
||||||
|
setCreatingEntityPath(event.target.value);
|
||||||
|
}}
|
||||||
|
value={creatingEntityPath}
|
||||||
|
>
|
||||||
|
{entityJsonPaths.map((path) => (
|
||||||
|
<option key={path} value={path}>{path}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entities.displayName = 'Entities';
|
||||||
|
|
||||||
|
export default Entities;
|
7
app/react/components/devtools/entities.module.css
Normal file
7
app/react/components/devtools/entities.module.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.activeEntity {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createEntity {
|
||||||
|
display: flex;
|
||||||
|
}
|
|
@ -195,6 +195,16 @@ export default class Engine {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'createEntity': {
|
||||||
|
const {path} = payload.value;
|
||||||
|
ecs.create({$$extends: path});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'destroyEntity': {
|
||||||
|
const {id} = payload.value;
|
||||||
|
ecs.destroy(parseInt(id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'paint': {
|
case 'paint': {
|
||||||
const {TileLayers} = ecs.get(1);
|
const {TileLayers} = ecs.get(1);
|
||||||
const {brush, layer: paintLayer, stamp} = payload.value;
|
const {brush, layer: paintLayer, stamp} = payload.value;
|
||||||
|
@ -213,6 +223,15 @@ export default class Engine {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'moveEntity': {
|
||||||
|
const {at: {x, y}, id} = payload.value;
|
||||||
|
const entity = ecs.get(id);
|
||||||
|
if (entity.Position) {
|
||||||
|
entity.Position.x = x;
|
||||||
|
entity.Position.y = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'moveUp':
|
case 'moveUp':
|
||||||
case 'moveRight':
|
case 'moveRight':
|
||||||
case 'moveDown':
|
case 'moveDown':
|
||||||
|
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -26,6 +26,7 @@
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
|
"immutable": "^5.0.2",
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
"kefir": "^3.8.8",
|
"kefir": "^3.8.8",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
"remark-mdx": "^3.0.1",
|
"remark-mdx": "^3.0.1",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"simplex-noise": "^4.0.1",
|
"simplex-noise": "^4.0.1",
|
||||||
"sylvite": "^1.0.5",
|
"sylvite": "^1.0.6",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit-parents": "^6.0.1",
|
"unist-util-visit-parents": "^6.0.1",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
|
@ -11571,6 +11572,11 @@
|
||||||
"node": ">=16.x"
|
"node": ">=16.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immutable": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw=="
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
|
"immutable": "^5.0.2",
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
"kefir": "^3.8.8",
|
"kefir": "^3.8.8",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user