import {memo, useCallback, useRef} from 'react'; import {DamageTypes} from '@/ecs/components/vulnerable.js'; import useAnimationFrame from '@/react/hooks/use-animation-frame.js'; import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js'; import styles from './damages.module.css'; function damageHue(type) { let hue; switch(type) { case DamageTypes.PAIN: { hue = [0, 30]; break; } case DamageTypes.HEALING: { hue = [90, 120]; break; } case DamageTypes.MANA: { hue = [215, 220]; break; } } return hue; } function createDamageNode() { const damage = document.createElement('div'); damage.classList.add(styles.damage); damage.appendChild(document.createElement('p')); return damage; } function Damages({damages, scale}) { const animations = useRef({}); const pool = useRef([]); const damagesRef = useRef(); 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(animations.current); 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; } 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 (
); } export default memo(Damages);