173 lines
6.0 KiB
JavaScript
173 lines
6.0 KiB
JavaScript
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 (
|
|
<div
|
|
className={styles.damages}
|
|
ref={damagesRef}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default memo(Damages);
|