import * as THREE from 'three'; import type { Game } from './Game'; const ROOT_DURATION = 0.35; const ROOT_TICK = 1.0; const ROOT_COOLDOWN = 1.0; const FADE_TIME = 0.6; export class WebPatch { body: THREE.Group; private radius: number; private life: number; private rootTick = 0; private meshes: THREE.Mesh[] = []; constructor(position: THREE.Vector3, radius = 3, duration = 6) { this.radius = radius; this.life = duration; this.body = new THREE.Group(); this.body.position.set(position.x, 0.08, position.z); const mat = new THREE.MeshToonMaterial({ color: 0xdfe6ff, transparent: true, opacity: 0.55, depthWrite: false, }); const disc = new THREE.Mesh(new THREE.CircleGeometry(radius, 24), mat); disc.rotation.x = -Math.PI / 2; disc.position.y = 0.01; this.body.add(disc); this.meshes.push(disc); // Radiale Fäden const threadMat = new THREE.MeshToonMaterial({ color: 0xffffff, transparent: true, opacity: 0.7, depthWrite: false, }); for (let i = 0; i < 8; i++) { const a = (i / 8) * Math.PI * 2; const thread = new THREE.Mesh( new THREE.BoxGeometry(radius * 2, 0.03, 0.05), threadMat ); thread.rotation.y = -a; thread.position.y = 0.02; this.body.add(thread); this.meshes.push(thread); } // Konzentrische Ringe for (let r = 1; r <= 3; r++) { const ring = new THREE.Mesh( new THREE.RingGeometry(r * radius / 4 - 0.04, r * radius / 4 + 0.04, 24), threadMat ); ring.rotation.x = -Math.PI / 2; ring.position.y = 0.025; this.body.add(ring); this.meshes.push(ring); } } update(dt: number, game: Game): boolean { this.life -= dt; if (this.life <= 0) return false; const fade = Math.min(1, this.life / FADE_TIME); for (const mesh of this.meshes) { (mesh.material as THREE.MeshToonMaterial).opacity = (mesh === this.meshes[0] ? 0.55 : 0.7) * fade; } 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) { // Gepulste Fessel: kurzer Root, dann Lücke zum Rauslaufen. // rootCooldown verhindert Endlos-Fessel durch überlappende Netze. this.rootTick -= dt; if (this.rootTick <= 0 && game.player.rootCooldown <= 0) { game.player.rootTime = ROOT_DURATION; game.player.rootCooldown = ROOT_COOLDOWN; this.rootTick = ROOT_TICK; } } else { this.rootTick = 0; } } return true; } dispose() { this.body.removeFromParent(); for (const mesh of this.meshes) { mesh.geometry.dispose(); (mesh.material as THREE.Material).dispose(); } } }