silphius/app/react/components/dom/damages.jsx

175 lines
5.3 KiB
React
Raw Normal View History

2024-07-31 00:06:29 -05:00
import {memo, useCallback, useRef} from 'react';
import {DamageTypes} from '@/ecs/components/vulnerable.js';
2024-07-31 10:35:43 -05:00
import {useEcsTick} from '@/react/context/ecs.js';
2024-07-31 00:06:29 -05:00
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';
2024-07-26 18:05:24 -05:00
import styles from './damages.module.css';
2024-07-31 00:06:29 -05:00
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;
}
2024-07-26 18:05:24 -05:00
2024-07-31 10:35:43 -05:00
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;
2024-07-31 00:06:29 -05:00
// offset
2024-07-31 10:35:43 -05:00
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);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
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);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else {
offsetX = this.offsetX;
offsetY = this.offsetY;
}
this.element.style.setProperty('--offsetX', offsetX);
this.element.style.setProperty('--offsetY', offsetY);
2024-07-31 00:06:29 -05:00
// scale
2024-07-31 10:35:43 -05:00
let scale = 0.35;
if (this.elapsed <= 0.5) {
scale = easeOutQuad(this.elapsed, 0, 1, 0.5);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else if (this.elapsed > 0.5 && this.elapsed < 0.6) {
scale = linear(this.elapsed - 0.5, 1, 0.5, 0.1);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else if (this.elapsed > 0.6 && this.elapsed < 0.675) {
scale = easeInQuint(this.elapsed - 0.6, 1, 0.25, 0.075);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else if (this.elapsed > 0.675 && this.elapsed < 1.375) {
scale = 1;
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else if (this.elapsed > 1.375) {
scale = easeOutQuad(this.elapsed - 1.375, 1, -1, 0.125);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
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);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else if (this.elapsed > 0.375 && this.elapsed < 1.375) {
opacity = 1;
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
else if (this.elapsed > 1.375) {
opacity = linear(this.elapsed - 1.375, 1, -1, 0.125);
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
this.element.style.setProperty('--opacity', opacity);
2024-07-31 00:06:29 -05:00
// hue
2024-07-31 10:35:43 -05:00
this.element.style.setProperty(
'--hue',
easeInOutExpo(
Math.abs((this.elapsed % 0.3) - 0.15) / 0.15,
this.hueStart,
this.hueEnd,
1,
),
);
}
}
}
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;
2024-07-31 00:06:29 -05:00
}
2024-07-31 10:35:43 -05:00
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;
}
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 stepSize = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
for (const key of keys) {
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);
2024-07-31 00:06:29 -05:00
}
}
}
2024-07-31 10:35:43 -05:00
}, []);
2024-07-31 00:06:29 -05:00
useAnimationFrame(frame);
return (
<div
className={styles.damages}
ref={damagesRef}
/>
);
2024-07-26 18:05:24 -05:00
}
2024-07-31 00:06:29 -05:00
export default memo(Damages);