flow: request/response, entity editing and labels
This commit is contained in:
parent
9262e7b1a8
commit
04fd533811
|
@ -1,12 +1,16 @@
|
|||
.devtools {
|
||||
background-color: #444444;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.devtools form {
|
||||
&, & * {
|
||||
font-size: 100%;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
:first-child {
|
||||
|
@ -29,6 +33,24 @@
|
|||
> :global(.react-tabs__tab-panel) {
|
||||
overflow-y: auto;
|
||||
}
|
||||
:global(.react-tabs__tab) {
|
||||
button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 1em;
|
||||
cursor: pointer;
|
||||
left: 0.25em;
|
||||
padding: 0 0.125em;
|
||||
position: relative;
|
||||
visibility: hidden;
|
||||
&:hover {
|
||||
background-color: #ffffff33;
|
||||
}
|
||||
}
|
||||
&:hover button {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
:global(.react-tabs__tab--selected) {
|
||||
background-color: #00000022;
|
||||
color: #ffffff;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {Map} from 'immutable';
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||
|
||||
import {useClient} from '@/react/context/client.js';
|
||||
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||
|
@ -10,15 +11,15 @@ const entityJsonPaths = Object.keys(import.meta.glob('%/**/*.entity.json'));
|
|||
|
||||
function entityLabel(entity) {
|
||||
let label = `${entity.id}`;
|
||||
const {Player, Position} = entity;
|
||||
const {Label, Player} = entity;
|
||||
if (1 === entity.id) {
|
||||
label = 'Master';
|
||||
}
|
||||
if (Player) {
|
||||
label += `: Player (${Player.id})`;
|
||||
label = `Player (${Player.id})`;
|
||||
}
|
||||
if (Position) {
|
||||
label += `: [${Position.x.toFixed(2)}, ${Position.y.toFixed(2)}]`
|
||||
if (Label) {
|
||||
label = Label.label;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
@ -26,7 +27,7 @@ function entityLabel(entity) {
|
|||
function Entities({eventsChannel}) {
|
||||
const client = useClient();
|
||||
const ecsRef = useEcs();
|
||||
const [activeEntity, setActiveEntity] = useState(1);
|
||||
const [activeEntity, setActiveEntity] = useState('1');
|
||||
const [creatingEntityPath, setCreatingEntityPath] = useState(entityJsonPaths[0]);
|
||||
const [entities, setEntities] = useState(Map());
|
||||
const onEcsTick = useCallback((payload, ecs) => {
|
||||
|
@ -53,6 +54,8 @@ function Entities({eventsChannel}) {
|
|||
});
|
||||
}, []);
|
||||
useEcsTick(onEcsTick);
|
||||
const list = Array.from(entities.keys())
|
||||
.toSorted(new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}).compare);
|
||||
useEffect(() => {
|
||||
if (!ecsRef.current) {
|
||||
return;
|
||||
|
@ -92,7 +95,36 @@ function Entities({eventsChannel}) {
|
|||
};
|
||||
}, [activeEntity, client, ecsRef, eventsChannel]);
|
||||
const options = [];
|
||||
for (const [id, label] of entities.entries()) {
|
||||
const tabLabels = [];
|
||||
const tabPanels = [];
|
||||
for (const id of list) {
|
||||
const label = entities.get(id);
|
||||
tabLabels.push(
|
||||
<Tab key={id}>
|
||||
{label}
|
||||
<button
|
||||
onClick={(event) => {
|
||||
client.send({
|
||||
type: 'AdminAction',
|
||||
payload: {
|
||||
type: 'destroyEntity',
|
||||
value: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
});
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</Tab>
|
||||
);
|
||||
tabPanels.push(
|
||||
<TabPanel key={id}>
|
||||
{id}
|
||||
</TabPanel>
|
||||
);
|
||||
options.push(
|
||||
<option
|
||||
key={id}
|
||||
|
@ -104,40 +136,13 @@ function Entities({eventsChannel}) {
|
|||
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
|
||||
<div
|
||||
className={styles.createEntity}
|
||||
>
|
||||
<button
|
||||
onClick={(event) => {
|
||||
client.send({
|
||||
onClick={async (event) => {
|
||||
event.preventDefault();
|
||||
const {payload} = await client.request({
|
||||
type: 'AdminAction',
|
||||
payload: {
|
||||
type: 'createEntity',
|
||||
|
@ -146,7 +151,7 @@ function Entities({eventsChannel}) {
|
|||
},
|
||||
},
|
||||
});
|
||||
event.preventDefault();
|
||||
setActiveEntity(`${payload.id}`);
|
||||
}}
|
||||
>
|
||||
+
|
||||
|
@ -162,6 +167,21 @@ function Entities({eventsChannel}) {
|
|||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
className={styles.activeEntity}
|
||||
>
|
||||
<Tabs
|
||||
onSelect={(index) => {
|
||||
setActiveEntity(list[index]);
|
||||
}}
|
||||
selectedIndex={list.indexOf(activeEntity)}
|
||||
>
|
||||
<TabList>
|
||||
{tabLabels}
|
||||
</TabList>
|
||||
{tabPanels}
|
||||
</Tabs>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
|
7
app/silphius/ecs/components/label.js
Normal file
7
app/silphius/ecs/components/label.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Component from '@/silphius/ecs/component.js';
|
||||
|
||||
export default class Label extends Component {
|
||||
static properties = {
|
||||
label: {type: 'string'},
|
||||
};
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {CLIENT_LATENCY, CLIENT_INTERPOLATION, CLIENT_PREDICTION} from '@/lib/constants.js';
|
||||
import EventEmitter from '@/lib/event-emitter.js';
|
||||
import {withResolvers} from '@/lib/promise.js';
|
||||
import {decode, encode} from '@/silphius/net/packets/index.js';
|
||||
|
||||
import {Flow} from './constants.js';
|
||||
|
@ -97,6 +98,25 @@ export default class Client {
|
|||
removePacketListener(type, listener) {
|
||||
this.emitter.removeListener(type, listener);
|
||||
}
|
||||
async request(packet) {
|
||||
// needs crypto strength
|
||||
const id = Math.random();
|
||||
const request = {
|
||||
type: 'Request',
|
||||
payload: {id, request: packet},
|
||||
}
|
||||
this.send(request);
|
||||
const {promise, resolve} = withResolvers();
|
||||
const listenForId = (payload) => {
|
||||
if (id !== payload.id) {
|
||||
return;
|
||||
}
|
||||
this.emitter.removeListener(listenForId);
|
||||
resolve(payload.response);
|
||||
}
|
||||
this.emitter.addListener('Response', listenForId);
|
||||
return promise;
|
||||
}
|
||||
send(packet) {
|
||||
const transmitOrPredict = () => {
|
||||
if (CLIENT_PREDICTION) {
|
||||
|
|
3
app/silphius/net/packets/admin-action-response.js
Normal file
3
app/silphius/net/packets/admin-action-response.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Packet from '@/silphius/net/packet.js';
|
||||
|
||||
export default class AdminActionResponse extends Packet {}
|
3
app/silphius/net/packets/request.js
Normal file
3
app/silphius/net/packets/request.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Packet from '@/silphius/net/packet.js';
|
||||
|
||||
export default class Request extends Packet {}
|
3
app/silphius/net/packets/response.js
Normal file
3
app/silphius/net/packets/response.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Packet from '@/silphius/net/packet.js';
|
||||
|
||||
export default class Response extends Packet {}
|
|
@ -106,14 +106,21 @@ export default class Engine {
|
|||
if (!this.incomingActions.has(connection)) {
|
||||
this.incomingActions.set(connection, []);
|
||||
}
|
||||
this.incomingActions.get(connection).push(payload);
|
||||
this.incomingActions.get(connection).push({payload});
|
||||
});
|
||||
this.server.addPacketListener('AdminAction', (connection, payload) => {
|
||||
// check...
|
||||
if (!this.incomingActions.has(connection)) {
|
||||
this.incomingActions.set(connection, []);
|
||||
}
|
||||
this.incomingActions.get(connection).push(payload);
|
||||
this.incomingActions.get(connection).push({payload});
|
||||
});
|
||||
this.server.addPacketListener('Request', (connection, payload) => {
|
||||
if (!this.incomingActions.has(connection)) {
|
||||
this.incomingActions.set(connection, []);
|
||||
}
|
||||
const {request} = payload;
|
||||
this.incomingActions.get(connection).push({payload: request.payload, respondTo: payload.id});
|
||||
});
|
||||
this.server.addPacketListener('Heartbeat', (connection) => {
|
||||
const playerData = this.connectedPlayers.get(connection);
|
||||
|
@ -158,7 +165,7 @@ export default class Engine {
|
|||
Wielder,
|
||||
} = entity;
|
||||
const ecs = this.ecses[Ecs.path];
|
||||
for (const payload of payloads) {
|
||||
for (const {payload, respondTo} of payloads) {
|
||||
switch (payload.type) {
|
||||
case 'chat': {
|
||||
if (payload.value.startsWith('/')) {
|
||||
|
@ -197,7 +204,20 @@ export default class Engine {
|
|||
}
|
||||
case 'createEntity': {
|
||||
const {path} = payload.value;
|
||||
ecs.create({$$extends: path});
|
||||
const id = ecs.create({$$extends: path});
|
||||
if (respondTo) {
|
||||
this.server.send(
|
||||
connection,
|
||||
{
|
||||
type: 'Response',
|
||||
payload: {
|
||||
id: respondTo,
|
||||
response: {type: 'AdminActionResponse', payload: {id}},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'destroyEntity': {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"Label": {"label": "Shrub"},
|
||||
"Position": {},
|
||||
"Sprite": {
|
||||
"anchorY": 0.7,
|
||||
|
|
Loading…
Reference in New Issue
Block a user