From 36607d3f12be16a1531c750dad3af377edcc60bc Mon Sep 17 00:00:00 2001 From: cha0s Date: Fri, 21 Jun 2024 04:50:17 -0500 Subject: [PATCH] flow: main entity, actions, active slot --- app/constants.js | 7 -- app/context/main-entity.js | 10 ++ app/ecs-components/controlled.js | 1 + app/ecs-components/wielder.js | 3 + app/ecs-systems/apply-control-item.js | 17 +++ app/engine/engine.js | 6 +- app/packets/action.js | 2 + app/react-components/ecs.jsx | 12 +- app/react-components/entities.jsx | 7 +- app/react-components/pixi.jsx | 22 ++-- app/react-components/ui.jsx | 146 ++++++++++++++++++++--- app/routes/_main-menu.play.$.$/route.jsx | 7 +- 12 files changed, 196 insertions(+), 44 deletions(-) create mode 100644 app/context/main-entity.js create mode 100644 app/ecs-components/wielder.js create mode 100644 app/ecs-systems/apply-control-item.js diff --git a/app/constants.js b/app/constants.js index e063bf2..aeed46e 100644 --- a/app/constants.js +++ b/app/constants.js @@ -10,10 +10,3 @@ export const RESOLUTION = { export const SERVER_LATENCY = 0; export const TPS = 60; - -export const ACTION_MAP = { - w: 'moveUp', - d: 'moveRight', - s: 'moveDown', - a: 'moveLeft', -}; diff --git a/app/context/main-entity.js b/app/context/main-entity.js new file mode 100644 index 0000000..5c7bdb4 --- /dev/null +++ b/app/context/main-entity.js @@ -0,0 +1,10 @@ +import {createContext, useContext} from 'react'; + +const context = createContext(); + +export default context; + +export function useMainEntity() { + return useContext(context); +} + diff --git a/app/ecs-components/controlled.js b/app/ecs-components/controlled.js index da57ca6..5f939e6 100644 --- a/app/ecs-components/controlled.js +++ b/app/ecs-components/controlled.js @@ -3,4 +3,5 @@ export default { moveRight: {type: 'float32'}, moveDown: {type: 'float32'}, moveLeft: {type: 'float32'}, + changeSlot: {type: 'int8'}, }; diff --git a/app/ecs-components/wielder.js b/app/ecs-components/wielder.js new file mode 100644 index 0000000..c0d56b7 --- /dev/null +++ b/app/ecs-components/wielder.js @@ -0,0 +1,3 @@ +export default { + activeSlot: {type: 'uint16'}, +}; diff --git a/app/ecs-systems/apply-control-item.js b/app/ecs-systems/apply-control-item.js new file mode 100644 index 0000000..40d1629 --- /dev/null +++ b/app/ecs-systems/apply-control-item.js @@ -0,0 +1,17 @@ +import {System} from '@/ecs/index.js'; + +export default class ApplyControlMovement extends System { + + tick() { + const {diff} = this.ecs; + for (const id in diff) { + if ('changeSlot' in (diff[id].Controlled ?? {})) { + if (diff[id].Controlled.changeSlot > 0) { + this.ecs.get(id).Wielder.activeSlot = diff[id].Controlled.changeSlot - 1; + } + } + } + } + +} + diff --git a/app/engine/engine.js b/app/engine/engine.js index 779a864..b1ca269 100644 --- a/app/engine/engine.js +++ b/app/engine/engine.js @@ -81,6 +81,7 @@ export default class Engine { }, }); const defaultSystems = [ + 'ApplyControlItem', 'ApplyControlMovement', 'ApplyMomentum', 'ClampPositions', @@ -104,7 +105,7 @@ export default class Engine { async createPlayer(id) { const player = { Camera: {}, - Controlled: {moveUp: 0, moveRight: 0, moveDown: 0, moveLeft: 0}, + Controlled: {}, Direction: {direction: 2}, Ecs: {path: join('homesteads', `${id}`)}, Momentum: {}, @@ -118,6 +119,9 @@ export default class Engine { source: '/assets/dude.json', speed: 0.115, }, + Wielder: { + activeSlot: 0, + }, }; const buffer = (new TextEncoder()).encode(JSON.stringify(player)); await this.server.writeData( diff --git a/app/packets/action.js b/app/packets/action.js index d83cf96..c6ff220 100644 --- a/app/packets/action.js +++ b/app/packets/action.js @@ -5,6 +5,8 @@ const WIRE_MAP = { 'moveRight': 1, 'moveDown': 2, 'moveLeft': 3, + 'use': 4, + 'changeSlot': 5, }; Object.entries(WIRE_MAP) .forEach(([k, v]) => { diff --git a/app/react-components/ecs.jsx b/app/react-components/ecs.jsx index 256fe60..966accd 100644 --- a/app/react-components/ecs.jsx +++ b/app/react-components/ecs.jsx @@ -2,6 +2,7 @@ import {Container} from '@pixi/react'; import {useState} from 'react'; import {RESOLUTION} from '@/constants.js'; +import {useMainEntity} from '@/context/main-entity.js'; import Ecs from '@/ecs/ecs.js'; import Components from '@/ecs-components/index.js'; import Systems from '@/ecs-systems/index.js'; @@ -14,7 +15,7 @@ import TileLayer from './tile-layer.jsx'; export default function EcsComponent() { const [ecs] = useState(new Ecs({Components, Systems})); const [entities, setEntities] = useState({}); - const [mainEntity, setMainEntity] = useState(); + const [mainEntity] = useMainEntity(); usePacket('Tick', (payload) => { if (0 === Object.keys(payload.ecs).length) { return; @@ -27,9 +28,6 @@ export default function EcsComponent() { } else { updatedEntities[id] = ecs.get(id); - if (updatedEntities[id].MainEntity) { - setMainEntity(ecs.get(id)); - } } } setEntities(updatedEntities); @@ -37,7 +35,7 @@ export default function EcsComponent() { if (!mainEntity) { return false; } - const {Camera} = mainEntity; + const {Camera, Position} = ecs.get(mainEntity); const {TileLayers} = ecs.get(1); const [cx, cy] = [ Math.round(Camera.x - RESOLUTION.x / 2), @@ -56,8 +54,8 @@ export default function EcsComponent() { y={-cy} /> diff --git a/app/react-components/entities.jsx b/app/react-components/entities.jsx index 23a5b51..7f9985a 100644 --- a/app/react-components/entities.jsx +++ b/app/react-components/entities.jsx @@ -27,13 +27,14 @@ export default function Entities({entities, x, y}) { continue; } renderables.push( - <> + - + ); } return ( diff --git a/app/react-components/pixi.jsx b/app/react-components/pixi.jsx index 26f9f6c..6c7e93c 100644 --- a/app/react-components/pixi.jsx +++ b/app/react-components/pixi.jsx @@ -3,29 +3,37 @@ import { } from '@pixi/react'; import {SCALE_MODES} from '@pixi/constants'; import {BaseTexture} from '@pixi/core'; +import {createElement, useContext} from 'react'; import {RESOLUTION} from '@/constants.js'; import ClientContext from '@/context/client.js'; +import MainEntityContext from '@/context/main-entity.js'; import Ecs from './ecs.jsx'; import styles from './pixi.module.css'; BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST; -const ContextBridge = ({ children, Context, render }) => { - return ( - - {(value) => - render({children}) +const ContextBridge = ({children, Contexts, render}) => { + const contexts = Contexts.map(useContext); + return render( + <> + { + Contexts.reduce( + (children, Context, i) => ([ + createElement(Context.Provider, {value: contexts[i], key: Context}, children) + ]), + children, + ) } - + ); }; export const Stage = ({children, ...props}) => { return ( {children}} > {children} diff --git a/app/react-components/ui.jsx b/app/react-components/ui.jsx index 2c55405..4587a12 100644 --- a/app/react-components/ui.jsx +++ b/app/react-components/ui.jsx @@ -1,8 +1,10 @@ import {useContext, useEffect, useState} from 'react'; import addKeyListener from '@/add-key-listener.js'; -import {ACTION_MAP, RESOLUTION} from '@/constants.js'; +import {RESOLUTION} from '@/constants.js'; import ClientContext from '@/context/client.js'; +import {useMainEntity} from '@/context/main-entity.js'; +import usePacket from '@/hooks/use-packet.js'; import Disconnected from './disconnected.jsx'; import Dom from './dom.jsx'; @@ -12,21 +14,18 @@ import styles from './ui.module.css'; const ratio = RESOLUTION.x / RESOLUTION.y; -const KEY_MAP = { - keyDown: 1, - keyUp: 0, -}; - export default function Ui({disconnected}) { // Key input. const client = useContext(ClientContext); + const [mainEntity, setMainEntity] = useMainEntity(); const [showDisconnected, setShowDisconnected] = useState(false); + const [activeSlot, setActiveSlot] = useState(0); useEffect(() => { let handle; if (disconnected) { handle = setTimeout(() => { setShowDisconnected(true); - }, 400); + }, 1000); } else { setShowDisconnected(false) @@ -37,17 +36,117 @@ export default function Ui({disconnected}) { }, [disconnected]); useEffect(() => { return addKeyListener(document.body, ({type, payload}) => { - if (type in KEY_MAP && payload in ACTION_MAP) { + const KEY_MAP = { + keyDown: 1, + keyUp: 0, + }; + let actionPayload; + switch (payload) { + case 'w': { + actionPayload = {type: 'moveUp', value: KEY_MAP[type]}; + break; + } + case 'a': { + actionPayload = {type: 'moveLeft', value: KEY_MAP[type]}; + break; + } + case 's': { + actionPayload = {type: 'moveDown', value: KEY_MAP[type]}; + break; + } + case 'd': { + actionPayload = {type: 'moveRight', value: KEY_MAP[type]}; + break; + } + case ' ': { + actionPayload = {type: 'use', value: KEY_MAP[type]}; + break; + } + case '1': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 1}; + } + break; + } + case '2': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 2}; + } + break; + } + case '3': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 3}; + } + break; + } + case '4': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 4}; + } + break; + } + case '5': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 5}; + } + break; + } + case '6': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 6}; + } + break; + } + case '7': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 7}; + } + break; + } + case '8': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 8}; + } + break; + } + case '9': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 9}; + } + break; + } + case '0': { + if ('keyDown' === type) { + actionPayload = {type: 'changeSlot', value: 10}; + } + break; + } + } + if (actionPayload) { client.send({ type: 'Action', - payload: { - type: ACTION_MAP[payload], - value: KEY_MAP[type], - }, + payload: actionPayload, }); } }); }); + usePacket('Tick', (payload) => { + if (0 === Object.keys(payload.ecs).length) { + return; + } + let localMainEntity = mainEntity; + for (const id in payload.ecs) { + if (payload.ecs[id]?.MainEntity) { + setMainEntity(localMainEntity = id); + } + if (localMainEntity === id) { + if (payload.ecs[id].Wielder && 'activeSlot' in payload.ecs[id].Wielder) { + setActiveSlot(payload.ecs[id].Wielder.activeSlot); + } + } + } + }, [mainEntity, setMainEntity]); return (
- - {})} /> - {showDisconnected && ( - - )} - + {mainEntity && ( + + { + client.send({ + type: 'Action', + payload: {type: 'changeSlot', value: i + 1}, + }); + }} + slots={Array(10).fill(0).map(() => {})} + /> + {showDisconnected && ( + + )} + + )}
); } diff --git a/app/routes/_main-menu.play.$.$/route.jsx b/app/routes/_main-menu.play.$.$/route.jsx index 68f8e0f..5369764 100644 --- a/app/routes/_main-menu.play.$.$/route.jsx +++ b/app/routes/_main-menu.play.$.$/route.jsx @@ -3,6 +3,8 @@ import {useEffect, useState} from 'react'; import {useOutletContext, useParams} from 'react-router-dom'; import ClientContext from '@/context/client.js'; +import MainEntityContext from '@/context/main-entity.js'; + import Ui from '@/react-components/ui.jsx'; import {juggleSession} from '@/session.server'; @@ -14,6 +16,7 @@ export async function loader({request}) { export default function PlaySpecific() { const Client = useOutletContext(); const [client, setClient] = useState(); + const mainEntityTuple = useState(); const [disconnected, setDisconnected] = useState(false); const params = useParams(); const [type, url] = params['*'].split('/'); @@ -84,7 +87,9 @@ export default function PlaySpecific() { }, [client, disconnected, url]); return ( - + + + ); }