Spaces:
Running
Running
| import * as THREE from "three"; | |
| import { Projectile } from "../../Projectile.js"; | |
| import { findNearestWithinRange } from "../common/targeting.js"; | |
| const VISUAL_TOP_INCREMENT = 0.08; | |
| const VISUAL_TOP_CAP = 0.4; | |
| export default { | |
| key: "slow", | |
| buildHead(tower) { | |
| const headGeo = new THREE.SphereGeometry( | |
| 0.55, | |
| 24, | |
| 16, | |
| 0, | |
| Math.PI * 2, | |
| Math.PI / 2, | |
| Math.PI / 2 | |
| ); | |
| const headMat = new THREE.MeshStandardMaterial({ | |
| color: 0xffb6c1, | |
| metalness: 0.15, | |
| roughness: 0.35, | |
| emissive: 0x4a0a2a, | |
| emissiveIntensity: 0.4, | |
| side: THREE.DoubleSide, | |
| }); | |
| const head = new THREE.Mesh(headGeo, headMat); | |
| head.castShadow = true; | |
| head.position.set(0, 0.8, 0); | |
| tower.baseMesh.add(head); | |
| tower.headMesh = head; | |
| tower.head = head; | |
| tower.headTopY = tower.mesh.position.y + head.position.y + 0.4; | |
| }, | |
| tryFire(tower, dt, enemies, projectiles, projectileSpeed) { | |
| tower.fireCooldown -= dt; | |
| if (tower.fireCooldown > 0) return; | |
| const target = findNearestWithinRange(tower, enemies); | |
| if (!target) return; | |
| const dir = new THREE.Vector3().subVectors( | |
| target.mesh.position, | |
| tower.position | |
| ); | |
| const yaw = Math.atan2(dir.x, dir.z); | |
| tower.mesh.rotation.y = yaw; | |
| tower.fireCooldown = 1 / tower.rate; | |
| const spawnY = | |
| typeof tower.headTopY === "number" ? tower.headTopY - 0.1 : 0.9; | |
| const proj = new Projectile( | |
| tower.position.clone().add(new THREE.Vector3(0, spawnY, 0)), | |
| target, | |
| projectileSpeed, | |
| tower.scene, | |
| tower.projectileEffect || null | |
| ); | |
| proj.damage = tower.damage; | |
| projectiles.push(proj); | |
| tower.playShootSound(); | |
| }, | |
| applyVisualLevel(tower) { | |
| const lvl = tower.level; | |
| const head = tower.headMesh; | |
| if (!head) return; | |
| const baseMat = tower.baseMesh?.material; | |
| const headMat = head.material; | |
| if (tower.levelRing) { | |
| tower.scene.remove(tower.levelRing); | |
| tower.levelRing.geometry.dispose(); | |
| if (tower.levelRing.material?.dispose) tower.levelRing.material.dispose(); | |
| tower.levelRing = null; | |
| } | |
| // No height increment on level gain — only rings should reflect level | |
| if (lvl <= 1) { | |
| if (baseMat) { | |
| baseMat.color?.set?.(0xff69b4); | |
| baseMat.emissive?.set?.(0x000000); | |
| baseMat.emissiveIntensity = 0.0; | |
| } | |
| // Keep original head height/position; recompute top using current head position | |
| tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4; | |
| headMat.color?.set?.(0xffb6c1); | |
| headMat.emissive?.set?.(0x4a0a2a); | |
| headMat.emissiveIntensity = 0.2; | |
| } else { | |
| if (baseMat) { | |
| baseMat.color?.set?.(0xff5ea8); | |
| baseMat.emissive?.set?.(0x2a0a1a); | |
| baseMat.emissiveIntensity = 0.08; | |
| } | |
| // Keep original head height/position; recompute top using current head position | |
| tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4; | |
| headMat.color?.set?.(0xffc6d9); | |
| headMat.emissive?.set?.(0x9a135a); | |
| headMat.emissiveIntensity = 0.35; | |
| const ringGeom = new THREE.TorusGeometry(0.45, 0.035, 8, 24); | |
| const ringMat = new THREE.MeshStandardMaterial({ | |
| color: 0xff8fc2, | |
| emissive: 0xe01a6b, | |
| emissiveIntensity: 0.55, | |
| metalness: 0.3, | |
| roughness: 0.45, | |
| }); | |
| const ring = new THREE.Mesh(ringGeom, ringMat); | |
| ring.castShadow = false; | |
| ring.receiveShadow = false; | |
| const topY = tower.headTopY ?? head.position.y + 0.8; | |
| ring.position.set( | |
| tower.mesh.position.x, | |
| topY + 0.02, | |
| tower.mesh.position.z | |
| ); | |
| ring.rotation.x = Math.PI / 2; | |
| ring.name = "tower_level_ring"; | |
| tower.levelRing = ring; | |
| tower.scene.add(ring); | |
| } | |
| }, | |
| }; | |