diff --git a/app/ecs/components/vulnerable.js b/app/ecs/components/vulnerable.js index 9c0eacd..4e4d0e6 100644 --- a/app/ecs/components/vulnerable.js +++ b/app/ecs/components/vulnerable.js @@ -1,4 +1,24 @@ import Component from '@/ecs/component.js'; export default class Vulnerable extends Component { + mergeDiff(original, update) { + const merged = {}; + if (update.damage) { + merged.damage = { + ...original.damage, + ...update.damage, + } + } + return merged; + } + instanceFromSchema() { + const Component = this; + return class VulnerableInstance extends super.instanceFromSchema() { + damages = {}; + id = 0; + damage(specification) { + Component.markChange(this.entity, 'damage', {[this.id++]: specification}); + } + }; + } } diff --git a/app/react/components/dom/damage.jsx b/app/react/components/dom/damage.jsx new file mode 100644 index 0000000..83f431f --- /dev/null +++ b/app/react/components/dom/damage.jsx @@ -0,0 +1,72 @@ +import {useEffect, useState} from 'react'; + +import {useRadians} from '@/react/context/radians.js'; + +import styles from './damage.module.css'; + +export default function Damage({ + camera, + damage, + scale, +}) { + const [hue, setHue] = useState(0); + const [opacity, setOpacity] = useState(0.5); + const [growth, setGrowth] = useState(0.25); + const [offset, setOffset] = useState({x: 0, y: 0}); + const [randomness] = useState({ + hue: Math.random() * 10, + x: 1 * (Math.random() - 0.5), + y: 150 * (Math.random()), + rotation: Math.random() * (Math.PI / 8) - (Math.PI / 16), + }); + const radians = useRadians(); + useEffect(() => { + setHue(15 + (Math.cos(radians * 4) * (5 + randomness.hue))); + }, [radians, randomness.hue]); + useEffect(() => { + let handle; + let accumulated = 0; + let last = Date.now(); + function float() { + const elapsed = (Date.now() - last) / 1000; + accumulated += elapsed; + last = Date.now(); + if (accumulated < 0.25) { + setOpacity(0.5 + accumulated * 2) + } + if (accumulated < 0.5) { + setGrowth(0.25 + (accumulated * 1.5)) + setOffset({ + x: 80 * (accumulated * randomness.x), + y: -((80 + randomness.y) * (accumulated)), + }); + } + if (accumulated >= 1.5) { + damage.onClose(); + } + handle = requestAnimationFrame(float); + } + handle = requestAnimationFrame(float); + return () => { + cancelAnimationFrame(handle); + }; + }, [damage, randomness.x, randomness.y]); + + const {amount, position} = damage; + const left = position.x * scale - camera.x + offset.x; + const top = position.y * scale - camera.y + offset.y; + return ( +
+

{amount}

+
+ ); +} diff --git a/app/react/components/dom/damage.module.css b/app/react/components/dom/damage.module.css new file mode 100644 index 0000000..ae70dbb --- /dev/null +++ b/app/react/components/dom/damage.module.css @@ -0,0 +1,25 @@ +.damage { + color: red; + overflow-wrap: break-word; + position: fixed; + margin: 0; + margin-right: -66%; + text-shadow: + 0px -1px 0px black, + 1px 0px 0px black, + 0px 1px 0px black, + -1px 0px 0px black, + + 0px -2px 0px black, + 2px 0px 0px black, + 0px 2px 0px black, + -2px 0px 0px black + + ; + transform: translate(-50%, -50%); + user-select: none; + max-width: 66%; + p { + margin: 0; + } +} diff --git a/app/react/components/dom/damages.jsx b/app/react/components/dom/damages.jsx new file mode 100644 index 0000000..0df6e0f --- /dev/null +++ b/app/react/components/dom/damages.jsx @@ -0,0 +1,22 @@ +import styles from './damages.module.css'; + +import Damage from './damage.jsx'; + +export default function Damages({camera, damages, scale}) { + const elements = []; + for (const key in damages) { + elements.push( + + ); + } + if (0 === elements.length) { + return false; + } + return
{elements}
; +} + diff --git a/app/react/components/dom/damages.module.css b/app/react/components/dom/damages.module.css new file mode 100644 index 0000000..6a815d4 --- /dev/null +++ b/app/react/components/dom/damages.module.css @@ -0,0 +1,4 @@ +.damages { + font-family: Cookbook, Georgia, 'Times New Roman', Times, serif; + font-size: 40px; +} diff --git a/app/react/components/dom/entities.jsx b/app/react/components/dom/entities.jsx index 0919622..2aa351d 100644 --- a/app/react/components/dom/entities.jsx +++ b/app/react/components/dom/entities.jsx @@ -88,6 +88,21 @@ export default function Entities({ }; } } + const {damage} = update.Vulnerable || {}; + if (damage) { + const {damages} = updating[id].Vulnerable; + for (const key in damage) { + damages[key] = damage[key]; + damages[key].onClose = () => { + setEntities((entities) => ({ + ...entities, + [id]: ecs.rebuild(id), + })); + delete damages[key]; + }; + + } + } } setEntities((entities) => { for (const id in deleting) { diff --git a/app/react/components/dom/entity.jsx b/app/react/components/dom/entity.jsx index 2e453f1..01ae60c 100644 --- a/app/react/components/dom/entity.jsx +++ b/app/react/components/dom/entity.jsx @@ -1,6 +1,7 @@ import {memo} from 'react'; import Dialogues from './dialogues.jsx'; +import Damages from './damages.jsx'; function Entity({camera, entity, scale}) { return ( @@ -12,6 +13,13 @@ function Entity({camera, entity, scale}) { scale={scale} /> )} + {entity.Vulnerable && ( + + )} ) } diff --git a/public/assets/magic-swords/collision-start.js b/public/assets/magic-swords/collision-start.js index 7dee41a..004ea66 100644 --- a/public/assets/magic-swords/collision-start.js +++ b/public/assets/magic-swords/collision-start.js @@ -1,4 +1,4 @@ const playerEntity = ecs.lookupPlayerEntity(entity.Owned.owner); if (playerEntity !== other && other.Vulnerable) { - ecs.destroy(other.id); + other.Vulnerable.damage({amount: Math.round(60 + Math.random() * 10), position: other.Position.toJSON()}) }