import * as THREE from 'three'; import type { Game } from './Game'; const TICK = 0.5; const TICK_DAMAGE = 4; const SLOW_REFRESH = 0.15; export class InkPuddle { body: THREE.Group; private radius: number; private life: number; private tickTimer = 0; private puffs: THREE.Mesh[] = []; constructor(position: THREE.Vector3, radius = 3.5, duration = 6) { this.radius = radius; this.life = duration; this.body = new THREE.Group(); this.body.position.set(position.x, 0.1, position.z); const puffGeom = new THREE.SphereGeometry(1, 12, 10); const puffMat = new THREE.MeshToonMaterial({ color: 0x552277, transparent: true, opacity: 0.35, depthWrite: false, }); for (let i = 0; i < 6; i++) { const puff = new THREE.Mesh(puffGeom, puffMat); const a = (i / 6) * Math.PI * 2; puff.position.set( Math.cos(a) * this.radius * 0.4, 0.3 + Math.random() * 0.3, Math.sin(a) * this.radius * 0.4 ); puff.scale.setScalar(this.radius * (0.22 + Math.random() * 0.12)); this.body.add(puff); this.puffs.push(puff); } } update(dt: number, game: Game): boolean { this.life -= dt; if (this.life <= 0) return false; const fade = Math.min(1, this.life / 0.6); for (const puff of this.puffs) { (puff.material as THREE.MeshToonMaterial).opacity = 0.35 * fade; puff.rotation.y += dt * 0.3; } if (game.player.health > 0) { const dist = Math.hypot( game.player.body.position.x - this.body.position.x, game.player.body.position.z - this.body.position.z ); if (dist < this.radius) { game.player.slowTimer = SLOW_REFRESH; this.tickTimer -= dt; if (this.tickTimer <= 0) { this.tickTimer = TICK; game.player.damage(TICK_DAMAGE, this.body.position); game.triggerInkVignette(); } } } return true; } dispose() { this.body.removeFromParent(); for (const puff of this.puffs) { puff.geometry.dispose(); (puff.material as THREE.Material).dispose(); } } }