import {useCallback, useRef, useState} from 'react'; import {usePacket} from '@/react/context/client.js'; import {useEcsTick} from '@/react/context/ecs.js'; import {parseLetters} from '@/util/dialogue.js'; import Damages from './damages.jsx'; import Entity from './entity.jsx'; export default function Entities({ camera, scale, setChatMessages, setMonopolizers, }) { const [entities, setEntities] = useState({}); const [damages, setDamages] = useState({}); const {current: pendingDamage} = useRef({accumulated: [], handle: undefined}); usePacket('EcsChange', async () => { setEntities({}); }); const onEcsTick = useCallback((payload, ecs) => { const deleting = {}; const updating = {}; for (const id in payload) { if ('1' === id) { continue; } const update = payload[id]; if (false === update) { deleting[id] = true; continue; } updating[id] = ecs.get(id); const {dialogue} = update.Interlocutor || {}; if (dialogue) { const {dialogues} = updating[id].Interlocutor; for (const key in dialogue) { dialogues[key] = dialogue[key]; if (!dialogues[key].offset) { dialogues[key].offset = {x: 0, y: 0}; } if ('track' === dialogues[key].origin) { dialogues[key].origin = () => updating[id].Position; } if ('track' === dialogues[key].position) { dialogues[key].position = () => updating[id].Position; } dialogues[key].letters = parseLetters(dialogues[key].body); setChatMessages((chatMessages) => ({ [[id, key].join('-')]: dialogues[key].letters, ...chatMessages, })); const skipListeners = new Set(); dialogues[key].addSkipListener = (listener) => { skipListeners.add(listener); return () => { skipListeners.delete(listener); } } const monopolizer = { trigger: () => { for (const listener of skipListeners) { listener(); } }, }; if (dialogues[key].monopolizer) { setMonopolizers((monopolizers) => [...monopolizers, monopolizer]); } dialogues[key].onClose = () => { setEntities((entities) => ({ ...entities, [id]: ecs.rebuild(id), })); if (dialogues[key].monopolizer) { setMonopolizers((monopolizers) => { const index = monopolizers.indexOf(monopolizer); if (-1 === index) { return monopolizers; } monopolizers.splice(index, 1); return [...monopolizers]; }); } delete dialogues[key]; }; } } const {damage} = update.Vulnerable || {}; if (damage) { for (const key in damage) { damage[key].onClose = () => { setDamages((damages) => { const {[key]: _, ...rest} = damages; // eslint-disable-line no-unused-vars return rest; }) }; } pendingDamage.accumulated.push(damage); if (!pendingDamage.handle) { pendingDamage.handle = setTimeout(() => { const update = {}; for (const damage of pendingDamage.accumulated) { for (const key in damage) { update[key] = damage[key]; } } pendingDamage.accumulated.length = 0; setDamages((damages) => { return { ...damages, ...update, }; }); pendingDamage.handle = undefined; }, 50); } } } setEntities((entities) => { for (const id in deleting) { delete entities[id]; } return { ...entities, ...updating, }; }); }, [pendingDamage, setChatMessages, setMonopolizers]); useEcsTick(onEcsTick); const renderables = []; for (const id in entities) { renderables.push( ); } return (
{renderables}
); }