diff --git a/app/react/components/dom/damages.jsx b/app/react/components/dom/damages.jsx index 59bf870..518eca3 100644 --- a/app/react/components/dom/damages.jsx +++ b/app/react/components/dom/damages.jsx @@ -1,6 +1,7 @@ import {memo, useCallback, useRef} from 'react'; import {DamageTypes} from '@/ecs/components/vulnerable.js'; +import {useEcsTick} from '@/react/context/ecs.js'; import useAnimationFrame from '@/react/hooks/use-animation-frame.js'; import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js'; @@ -25,142 +26,143 @@ function damageHue(type) { return hue; } -function createDamageNode() { - const damage = document.createElement('div'); - damage.classList.add(styles.damage); - damage.appendChild(document.createElement('p')); - return damage; +class Damage { + elapsed = 0; + hue = [0, 30]; + offsetX = 0; + offsetY = 0; + step = 0; + constructor() { + const element = document.createElement('div'); + element.classList.add(styles.damage); + element.appendChild(document.createElement('p')); + this.element = element; + } + reset(key, scale, {amount, position, type}) { + this.elapsed = 0; + [this.hueStart, this.hueEnd] = damageHue(type); + this.offsetX = 20 * scale * 1 * (Math.random() - 0.5); + this.offsetY = -1 * (10 + Math.random() * 45 * scale); + this.step = 0; + const {element} = this; + element.style.setProperty('rotate', `${Math.random() * (Math.PI / 16) - (Math.PI / 32)}rad`); + element.style.setProperty('--magnitude', Math.max(1, Math.floor(Math.log10(Math.abs(amount))))); + element.style.setProperty('--positionX', `${position.x * scale}px`); + element.style.setProperty('--positionY', `${position.y * scale}px`); + element.style.setProperty('zIndex', key); + const p = element.querySelector('p'); + p.style.scale = scale / 2; + p.innerText = Math.abs(amount); + } + tick(elapsed, stepSize) { + this.elapsed += elapsed; + this.step += elapsed; + if (this.step > stepSize) { + this.step = this.step % stepSize; + // offset + let offsetX = 0, offsetY = 0; + if (this.elapsed <= 0.5) { + offsetX = easeOutQuad(this.elapsed, 0, this.offsetX, 0.5); + offsetY = easeOutQuad(this.elapsed, 0, this.offsetY, 0.5); + } + else if (this.elapsed > 1.375) { + offsetX = easeOutQuad(this.elapsed - 1.375, this.offsetX, -this.offsetX, 0.125); + offsetY = easeOutQuad(this.elapsed - 1.375, this.offsetY, -this.offsetY, 0.125); + } + else { + offsetX = this.offsetX; + offsetY = this.offsetY; + } + this.element.style.setProperty('--offsetX', offsetX); + this.element.style.setProperty('--offsetY', offsetY); + // scale + let scale = 0.35; + if (this.elapsed <= 0.5) { + scale = easeOutQuad(this.elapsed, 0, 1, 0.5); + } + else if (this.elapsed > 0.5 && this.elapsed < 0.6) { + scale = linear(this.elapsed - 0.5, 1, 0.5, 0.1); + } + else if (this.elapsed > 0.6 && this.elapsed < 0.675) { + scale = easeInQuint(this.elapsed - 0.6, 1, 0.25, 0.075); + } + else if (this.elapsed > 0.675 && this.elapsed < 1.375) { + scale = 1; + } + else if (this.elapsed > 1.375) { + scale = easeOutQuad(this.elapsed - 1.375, 1, -1, 0.125); + } + this.element.style.setProperty('--scale', scale); + // opacity + let opacity = 0.75; + if (this.elapsed <= 0.375) { + opacity = linear(this.elapsed, 0.75, 0.25, 0.375); + } + else if (this.elapsed > 0.375 && this.elapsed < 1.375) { + opacity = 1; + } + else if (this.elapsed > 1.375) { + opacity = linear(this.elapsed - 1.375, 1, -1, 0.125); + } + this.element.style.setProperty('--opacity', opacity); + // hue + this.element.style.setProperty( + '--hue', + easeInOutExpo( + Math.abs((this.elapsed % 0.3) - 0.15) / 0.15, + this.hueStart, + this.hueEnd, + 1, + ), + ); + } + } } -function Damages({damages, scale}) { - const animations = useRef({}); - const pool = useRef([]); +function Damages({scale}) { + const damages = useRef({}); const damagesRef = useRef(); + const pool = useRef([]); + const onEcsTick = useCallback((payload) => { + for (const id in payload) { + const update = payload[id]; + if (false === update || !update.Vulnerable?.damage) { + continue; + } + const {damage: damageUpdate} = update.Vulnerable; + for (const key in damageUpdate) { + const damage = pool.current.length > 0 ? pool.current.pop() : new Damage(); + damage.reset(key, scale, damageUpdate[key]); + damages.current[key] = damage; + damagesRef.current.appendChild(damage.element); + } + } + }, [scale]); + useEcsTick(onEcsTick); const frame = useCallback((elapsed) => { if (!damagesRef.current) { return; } - if (0 === pool.current.length) { - for (let i = 0; i < 512; ++i) { - const damage = createDamageNode(); - damagesRef.current.appendChild(damage); - pool.current.push(damage); + const keys = Object.keys(damages.current); + if (0 === keys.length && 0 === pool.current.length) { + for (let i = 0; i < 500; ++i) { + pool.current.push(new Damage()); } } - const keys = Object.keys(animations.current); + const stepSize = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed; for (const key of keys) { - const animation = animations.current[key]; - if (!damages[key]) { - if (animation.element) { - if (pool.current.length < 512) { - pool.current.push(animation.element); - } - animation.element = undefined; + const damage = damages.current[key]; + damage.tick(elapsed, stepSize); + if (damage.elapsed > 1.5) { + damagesRef.current.removeChild(damage.element); + delete damages.current[key]; + if (pool.current.length < 1000) { + pool.current.push(damage); } - delete animations.current[key]; - continue; - } - if (!animation.element) { - const {amount, position} = damages[key]; - let damage; - if (pool.current.length > 0) { - damage = pool.current.pop(); - } - else { - damage = createDamageNode(); - damagesRef.current.appendChild(damage); - } - const p = damage.querySelector('p'); - p.style.scale = scale / 2; - p.innerText = Math.abs(amount); - damage.style.setProperty('--randomnessX', animation['--randomnessX']); - damage.style.setProperty('--randomnessY', animation['--randomnessY']); - damage.style.setProperty('rotate', animation['rotate']); - damage.style.setProperty('rotate', animation['rotate']); - damage.style.setProperty('--magnitude', Math.max(1, Math.floor(Math.log10(Math.abs(amount))))); - damage.style.setProperty('--positionX', `${position.x * scale}px`); - damage.style.setProperty('--positionY', `${position.y * scale}px`); - damage.style.setProperty('zIndex', key); - animation.element = damage; - } - animation.elapsed += elapsed; - animation.step += elapsed; - const offsetX = 20 * scale * animation['--randomnessX']; - const offsetY = -1 * (10 + animation['--randomnessY'] * 45 * scale); - // offset - if (animation.elapsed <= 0.5) { - animation['--offsetX'] = easeOutQuad(animation.elapsed, 0, offsetX, 0.5); - animation['--offsetY'] = easeOutQuad(animation.elapsed, 0, offsetY, 0.5); - } - else if (animation.elapsed > 1.375) { - animation['--offsetX'] = offsetX - easeOutQuad(animation.elapsed - 1.375, 0, offsetX, 0.125); - animation['--offsetY'] = offsetY - easeOutQuad(animation.elapsed - 1.375, 0, offsetY, 0.125); - } - // scale - if (animation.elapsed <= 0.5) { - animation['--scale'] = easeOutQuad(animation.elapsed, 0, 1, 0.5); - } - else if (animation.elapsed > 0.5 && animation.elapsed < 0.6) { - animation['--scale'] = linear(animation.elapsed - 0.5, 1, 0.5, 0.1); - } - else if (animation.elapsed > 0.6 && animation.elapsed < 0.675) { - animation['--scale'] = 1.5 - easeInQuint(animation.elapsed - 0.6, 1, 0.5, 0.075); - } - else if (animation.elapsed > 0.675 && animation.elapsed < 1.375) { - animation['--scale'] = 1; - } - else if (animation.elapsed > 1.375) { - animation['--scale'] = 1 - easeOutQuad(animation.elapsed - 1.375, 0, 1, 0.125); - } - // fade - if (animation.elapsed <= 0.375) { - animation['--opacity'] = linear(animation.elapsed, 0.75, 0.25, 0.375); - } - else if (animation.elapsed > 0.375 && animation.elapsed < 1.375) { - animation['--opacity'] = 1; - } - else if (animation.elapsed > 1.375) { - animation['--opacity'] = 1 - linear(animation.elapsed - 1.375, 0, 1, 0.125); - } - // hue - const h = Math.abs((animation.elapsed % 0.3) - 0.15) / 0.15; - animation['--hue'] = easeInOutExpo(h, animation.hue[0], animation.hue[1], 1); - const step = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed; - if (animation.step > step) { - animation.step = animation.step % step; - animation.element.style.setProperty('--hue', animation['--hue']); - animation.element.style.setProperty('--opacity', animation['--opacity']); - animation.element.style.setProperty('--offsetX', animation['--offsetX']); - animation.element.style.setProperty('--offsetY', animation['--offsetY']); - animation.element.style.setProperty('--scale', animation['--scale']); - } - if (animation.elapsed > 1.5) { - if (pool.current.length < 512) { - pool.current.push(animation.element); - } - animation.element.style.setProperty('--opacity', 0); - animation.element = undefined; - damages[key].onClose(); } } - }, [damages, scale]); + }, []); useAnimationFrame(frame); - for (const key in damages) { - if (!animations.current[key]) { - animations.current[key] = { - elapsed: 0, - hue: damageHue(damages[key].type), - rotate: `${Math.random() * (Math.PI / 16) - (Math.PI / 32)}rad`, - step: 0, - '--scale': 0.35, - '--opacity': 0.75, - '--offsetX': 0, - '--offsetY': 0, - '--randomnessX': 1 * (Math.random() - 0.5), - '--randomnessY': Math.random(), - }; - } - } return (
'; } -@property --randomnessX { - initial-value: 0; - inherits: false; - syntax: ''; -} -@property --randomnessY { - initial-value: 0; - inherits: false; - syntax: ''; -} .damage { --hue: 0; --opacity: 0.75; - --randomnessX: 0; - --randomnessY: 0; --scale: 0.35; --background: hsl(var(--hue) 100% 12.5%); --foreground: hsl(var(--hue) 100% 50%); diff --git a/app/react/components/dom/entities.jsx b/app/react/components/dom/entities.jsx index 057dea7..ca05ee4 100644 --- a/app/react/components/dom/entities.jsx +++ b/app/react/components/dom/entities.jsx @@ -1,4 +1,4 @@ -import {useCallback, useRef, useState} from 'react'; +import {useCallback, useState} from 'react'; import {usePacket} from '@/react/context/client.js'; import {useEcsTick} from '@/react/context/ecs.js'; @@ -14,8 +14,6 @@ export default function Entities({ setMonopolizers, }) { const [entities, setEntities] = useState({}); - const [damages, setDamages] = useState({}); - const {current: pendingDamage} = useRef({accumulated: [], handle: undefined}); usePacket('EcsChange', async () => { setEntities({}); }); @@ -87,36 +85,6 @@ export default function Entities({ }; } } - 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) { @@ -127,7 +95,7 @@ export default function Entities({ ...updating, }; }); - }, [pendingDamage, setChatMessages, setMonopolizers]); + }, [setChatMessages, setMonopolizers]); useEcsTick(onEcsTick); const renderables = []; for (const id in entities) { @@ -151,7 +119,6 @@ export default function Entities({ > {renderables}