import {simplex2D} from '@leodeslf/simplex-noise'; import {Texture} from '@pixi/core'; import {Container} from '@pixi/react'; import {Sprite} from '@pixi/sprite'; import {useEffect, useState} from 'react'; import {RESOLUTION} from '@/constants.js'; import {usePacket} from '@/context/client.js'; import {useEcs, useEcsTick} from '@/context/ecs.js'; import {useMainEntity} from '@/context/main-entity.js'; import {bresenham, TAU} from '@/util/math.js'; import Entities from './entities.jsx'; import TargetingGhost from './targeting-ghost.jsx'; import TargetingGrid from './targeting-grid.jsx'; import TileLayer from './tile-layer.jsx'; import Water from './water.jsx'; const NIGHTNESS = 0.1; function calculateDarkness(hour) { let darkness = 0; if (hour >= 21 || hour < 4) { darkness = 0.8; } if (hour >= 4 && hour < 7) { darkness = 0.8 * ((7 - hour) / 3); } if (hour >= 18 && hour < 21) { darkness = 0.8 * ((3 - (21 - hour)) / 3); } return Math.floor(darkness * 1000) / 1000; } function createLayerMask(layer) { const {area, hulls, tileSize} = layer; const canvas = window.document.createElement('canvas'); if (0 === hulls.length) { return Texture.from(canvas); } [canvas.width, canvas.height] = [area.x * tileSize.x, area.y * tileSize.y]; const ctx = canvas.getContext('2d'); ctx.fillStyle = 'rgba(0, 0, 0, 1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'rgba(255, 255, 255, 1)'; for (let i = 0; i < hulls.length; ++i) { const hull = [...hulls[i]]; hull.push(hull[0]); ctx.beginPath(); ctx.moveTo(hull[0].x, hull[0].y); for (let j = 0; j < hull.length - 1; j++) { const p0 = hull[j + 0]; const p1 = hull[j + 1]; const points = bresenham(p0, p1); const isReversed = ( (p0.x > p1.x && points[0].x < points[points.length - 1].x) || (p0.y > p1.y && points[0].y < points[points.length - 1].y) ); for ( let k = (isReversed ? points.length - 1 : 0); (isReversed ? k >= 0 : k < points.length); k += (isReversed ? -1 : 1) ) { const {x, y} = points[k]; const ANGLE_SCALE = 1000; const WALK_SCALE = 10; const MAGNITUDE = ((tileSize.x + tileSize.y) / 2) * simplex2D(x * 100, y * 100); const w = simplex2D(x * WALK_SCALE, y * WALK_SCALE); const r = TAU * simplex2D(x * ANGLE_SCALE, y * ANGLE_SCALE) ctx.lineTo( x + Math.cos(r) * w * MAGNITUDE, y + -Math.sin(r) * w * MAGNITUDE, ); } } ctx.closePath(); ctx.fill(); } return Texture.from(canvas); } export default function Ecs({applyFilters, scale}) { const [ecs] = useEcs(); const [entities, setEntities] = useState({}); const [mainEntity] = useMainEntity(); const [hour, setHour] = useState(10); const [night, setNight] = useState(); const [mask, setMask] = useState(); useEffect(() => { async function buildNightFilter() { const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix'); class NightFilter extends ColorMatrixFilter { setIntensity(intensity) { const double = NIGHTNESS * 2; const half = NIGHTNESS / 2; const redDown = 1 - (intensity * (1 + double)); const blueUp = 1 - (intensity * (1 - half)); const scale = intensity * NIGHTNESS; this.uniforms.m = [ redDown, -scale, 0, 0, 0, -scale, (1 - intensity), scale, 0, 0, 0, scale, blueUp, 0, 0, 0, 0, 0, 1, 0, ]; } } setNight(new NightFilter()); } buildNightFilter(); }, []); useEffect(() => { if (night) { night.setIntensity(calculateDarkness(hour)); } }, [hour, night]); usePacket('EcsChange', async () => { setEntities({}); }, [setEntities]); useEcsTick((payload) => { if (!ecs) { return; } const updatedEntities = {...entities}; for (const id in payload) { const update = payload[id]; if (false === update) { delete updatedEntities[id]; } else { if ('1' === id) { if (update.Time) { setHour(Math.round(ecs.get(1).Time.hour * 60) / 60); } if (update.TileLayers) { const layer1 = ecs.get(1).TileLayers.layer(1); if (layer1) { setMask(new Sprite(createLayerMask(layer1))); } } } updatedEntities[id] = ecs.get(id); if (update.Emitter?.emit) { updatedEntities[id].Emitter.emitting = { ...updatedEntities[id].Emitter.emitting, ...update.Emitter.emit, }; } } } setEntities(updatedEntities); }, [ecs, entities, mainEntity]); if (!ecs || !mainEntity) { return false; } const entity = ecs.get(mainEntity); if (!entity) { return false; } const {Direction, Position, Wielder} = entity; const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction) const {Camera} = entity; const {TileLayers, Water: WaterEcs} = ecs.get(1); const layer0 = TileLayers.layer(0); const layer1 = TileLayers.layer(1); const filters = []; if (applyFilters && night) { filters.push(night); } const [cx, cy] = [ Math.round((Camera.x * scale) - RESOLUTION.x / 2), Math.round((Camera.y * scale) - RESOLUTION.y / 2), ]; return ( {layer1 && ( )} {WaterEcs && ( )} {projected && ( )} {projected?.length > 0 && ( )} ) }