Spaces:
Running
Running
ok
Browse files
src/entities/towers/Tower.js
CHANGED
|
@@ -134,6 +134,7 @@ export class Tower {
|
|
| 134 |
this.ring = ring;
|
| 135 |
this.hoverOutline = outline;
|
| 136 |
this.levelRing = null;
|
|
|
|
| 137 |
|
| 138 |
if (this.isElectric) {
|
| 139 |
this.applyVisualLevel();
|
|
@@ -193,7 +194,17 @@ export class Tower {
|
|
| 193 |
}
|
| 194 |
|
| 195 |
applyVisualLevel() {
|
|
|
|
| 196 |
this.kind.applyVisualLevel(this);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
}
|
| 198 |
|
| 199 |
tryFire(dt, enemies, projectiles, projectileSpeed) {
|
|
@@ -280,6 +291,97 @@ export class Tower {
|
|
| 280 |
if (this.levelRing.material?.dispose) this.levelRing.material.dispose();
|
| 281 |
this.levelRing = null;
|
| 282 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
this.scene.remove(this.mesh);
|
| 284 |
}
|
| 285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
this.ring = ring;
|
| 135 |
this.hoverOutline = outline;
|
| 136 |
this.levelRing = null;
|
| 137 |
+
this.levelRings = [];
|
| 138 |
|
| 139 |
if (this.isElectric) {
|
| 140 |
this.applyVisualLevel();
|
|
|
|
| 194 |
}
|
| 195 |
|
| 196 |
applyVisualLevel() {
|
| 197 |
+
// Let the kind update its visuals first (head position, colors, base ring)
|
| 198 |
this.kind.applyVisualLevel(this);
|
| 199 |
+
|
| 200 |
+
// Clean up any legacy/orphan base rings left from earlier versions
|
| 201 |
+
this._cleanupOrphanLevelRings();
|
| 202 |
+
|
| 203 |
+
// Then ensure a consistent number of visible rings across all towers:
|
| 204 |
+
// Level 1 => 0 rings; Level 2 => 1 ring; Level 3 => 2 rings, etc.
|
| 205 |
+
// Most kinds add a single base ring for level >= 2. We add additional
|
| 206 |
+
// rings above that so the total equals (level - 1).
|
| 207 |
+
this._updateAdditionalLevelRings();
|
| 208 |
}
|
| 209 |
|
| 210 |
tryFire(dt, enemies, projectiles, projectileSpeed) {
|
|
|
|
| 291 |
if (this.levelRing.material?.dispose) this.levelRing.material.dispose();
|
| 292 |
this.levelRing = null;
|
| 293 |
}
|
| 294 |
+
if (this.levelRings && this.levelRings.length) {
|
| 295 |
+
for (const ring of this.levelRings) {
|
| 296 |
+
this.scene.remove(ring);
|
| 297 |
+
ring.geometry?.dispose?.();
|
| 298 |
+
ring.material?.dispose?.();
|
| 299 |
+
}
|
| 300 |
+
this.levelRings.length = 0;
|
| 301 |
+
}
|
| 302 |
this.scene.remove(this.mesh);
|
| 303 |
}
|
| 304 |
+
|
| 305 |
+
_updateAdditionalLevelRings() {
|
| 306 |
+
// Clean up any previously created extra rings
|
| 307 |
+
if (this.levelRings && this.levelRings.length) {
|
| 308 |
+
for (const ring of this.levelRings) {
|
| 309 |
+
this.scene.remove(ring);
|
| 310 |
+
ring.geometry?.dispose?.();
|
| 311 |
+
ring.material?.dispose?.();
|
| 312 |
+
}
|
| 313 |
+
this.levelRings.length = 0;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
// How many rings should be visible in total
|
| 317 |
+
const totalRings = Math.max(0, (this.level || 1) - 1);
|
| 318 |
+
if (totalRings <= 1) {
|
| 319 |
+
// Kind likely already created the first ring if level >= 2; nothing else to do
|
| 320 |
+
return;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
// Additional rings we should add above the kind's base ring
|
| 324 |
+
const extraRings = totalRings - 1;
|
| 325 |
+
|
| 326 |
+
// Placement parameters
|
| 327 |
+
const baseY = (this.headTopY ?? 0.8) + 0.02; // matches kind base ring offset
|
| 328 |
+
const spacing = 0.1; // increased vertical spacing between rings
|
| 329 |
+
|
| 330 |
+
// Choose a color consistent with the tower type
|
| 331 |
+
let color = 0x3aa6ff; // default blue
|
| 332 |
+
if (this.type === "slow") color = 0xff8fc2;
|
| 333 |
+
else if (this.type === "sniper") color = 0x00ffff;
|
| 334 |
+
// electric uses default blue
|
| 335 |
+
|
| 336 |
+
for (let i = 0; i < extraRings; i++) {
|
| 337 |
+
const geom = new THREE.TorusGeometry(0.45, 0.035, 8, 24);
|
| 338 |
+
const mat = new THREE.MeshStandardMaterial({
|
| 339 |
+
color,
|
| 340 |
+
emissive: 0xe01a6b,
|
| 341 |
+
emissiveIntensity: 0.55,
|
| 342 |
+
metalness: 0.3,
|
| 343 |
+
roughness: 0.45,
|
| 344 |
+
});
|
| 345 |
+
const ring = new THREE.Mesh(geom, mat);
|
| 346 |
+
ring.castShadow = false;
|
| 347 |
+
ring.receiveShadow = false;
|
| 348 |
+
ring.rotation.x = Math.PI / 2;
|
| 349 |
+
ring.name = "tower_level_ring_extra";
|
| 350 |
+
|
| 351 |
+
// Stack above the base ring with clear vertical separation
|
| 352 |
+
const y = baseY + (i + 1) * spacing;
|
| 353 |
+
ring.position.set(this.mesh.position.x, y, this.mesh.position.z);
|
| 354 |
+
|
| 355 |
+
this.scene.add(ring);
|
| 356 |
+
this.levelRings.push(ring);
|
| 357 |
+
}
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
_cleanupOrphanLevelRings() {
|
| 361 |
+
const scene = this.scene;
|
| 362 |
+
if (!scene || !scene.children) return;
|
| 363 |
+
|
| 364 |
+
const px = this.mesh?.position?.x ?? this.position?.x ?? 0;
|
| 365 |
+
const pz = this.mesh?.position?.z ?? this.position?.z ?? 0;
|
| 366 |
+
const threshold = 0.1; // only remove rings very near this tower
|
| 367 |
+
|
| 368 |
+
// Collect candidates first to avoid mutating while iterating
|
| 369 |
+
const toRemove = [];
|
| 370 |
+
for (const obj of scene.children) {
|
| 371 |
+
if (!obj || obj === this.levelRing) continue;
|
| 372 |
+
if (obj.name !== 'tower_level_ring') continue;
|
| 373 |
+
const ox = obj.position?.x ?? 0;
|
| 374 |
+
const oz = obj.position?.z ?? 0;
|
| 375 |
+
const dx = ox - px;
|
| 376 |
+
const dz = oz - pz;
|
| 377 |
+
if (dx * dx + dz * dz <= threshold * threshold) {
|
| 378 |
+
toRemove.push(obj);
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
for (const obj of toRemove) {
|
| 382 |
+
scene.remove(obj);
|
| 383 |
+
obj.geometry?.dispose?.();
|
| 384 |
+
obj.material?.dispose?.();
|
| 385 |
+
}
|
| 386 |
+
}
|
| 387 |
+
}
|
src/entities/towers/kinds/basic.js
CHANGED
|
@@ -75,8 +75,7 @@ export default {
|
|
| 75 |
tower.levelRing = null;
|
| 76 |
}
|
| 77 |
|
| 78 |
-
|
| 79 |
-
const visualExtra = Math.min(VISUAL_TOP_CAP, extraRaw);
|
| 80 |
|
| 81 |
if (lvl <= 1) {
|
| 82 |
if (baseMat) {
|
|
@@ -85,8 +84,7 @@ export default {
|
|
| 85 |
baseMat.emissiveIntensity = 0.0;
|
| 86 |
}
|
| 87 |
|
| 88 |
-
head
|
| 89 |
-
head.position.y = 0.65 + visualExtra;
|
| 90 |
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4;
|
| 91 |
|
| 92 |
headMat.color?.set?.(0x90caf9);
|
|
@@ -99,9 +97,8 @@ export default {
|
|
| 99 |
baseMat.emissiveIntensity = 0.08;
|
| 100 |
}
|
| 101 |
|
| 102 |
-
head
|
| 103 |
-
|
| 104 |
-
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.8;
|
| 105 |
|
| 106 |
headMat.color?.set?.(0xa5d6ff);
|
| 107 |
headMat.emissive?.set?.(0x9a135a);
|
|
@@ -132,4 +129,4 @@ export default {
|
|
| 132 |
tower.scene.add(ring);
|
| 133 |
}
|
| 134 |
},
|
| 135 |
-
};
|
|
|
|
| 75 |
tower.levelRing = null;
|
| 76 |
}
|
| 77 |
|
| 78 |
+
// No height increment on level gain — only rings should reflect level
|
|
|
|
| 79 |
|
| 80 |
if (lvl <= 1) {
|
| 81 |
if (baseMat) {
|
|
|
|
| 84 |
baseMat.emissiveIntensity = 0.0;
|
| 85 |
}
|
| 86 |
|
| 87 |
+
// Keep original head height/position; recompute top using current head position
|
|
|
|
| 88 |
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4;
|
| 89 |
|
| 90 |
headMat.color?.set?.(0x90caf9);
|
|
|
|
| 97 |
baseMat.emissiveIntensity = 0.08;
|
| 98 |
}
|
| 99 |
|
| 100 |
+
// Keep original head height/position; recompute top using current head position
|
| 101 |
+
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4;
|
|
|
|
| 102 |
|
| 103 |
headMat.color?.set?.(0xa5d6ff);
|
| 104 |
headMat.emissive?.set?.(0x9a135a);
|
|
|
|
| 129 |
tower.scene.add(ring);
|
| 130 |
}
|
| 131 |
},
|
| 132 |
+
};
|
src/entities/towers/kinds/electric.js
CHANGED
|
@@ -60,6 +60,8 @@ export default {
|
|
| 60 |
tower.headMesh = head;
|
| 61 |
tower.head = head;
|
| 62 |
tower.headTopY = tower.mesh.position.y + head.position.y + 0.45;
|
|
|
|
|
|
|
| 63 |
|
| 64 |
tower.trackedTargets = new Map();
|
| 65 |
tower.arcPool = [];
|
|
@@ -360,12 +362,20 @@ export default {
|
|
| 360 |
const head = tower.headMesh;
|
| 361 |
if (!head) return;
|
| 362 |
|
| 363 |
-
|
| 364 |
-
const
|
| 365 |
-
head.position.y =
|
| 366 |
tower.headTopY =
|
| 367 |
(tower.mesh?.position.y ?? 0.25) + head.position.y + 0.45;
|
| 368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
if (lvl > 1) {
|
| 370 |
head.material.color?.set?.(0xa5d6ff);
|
| 371 |
head.material.emissive?.set?.(0x9a135a);
|
|
@@ -430,4 +440,4 @@ export default {
|
|
| 430 |
tower.arcPool.length = 0;
|
| 431 |
}
|
| 432 |
},
|
| 433 |
-
};
|
|
|
|
| 60 |
tower.headMesh = head;
|
| 61 |
tower.head = head;
|
| 62 |
tower.headTopY = tower.mesh.position.y + head.position.y + 0.45;
|
| 63 |
+
// Remember base head Y so applyVisualLevel can position absolutely per level
|
| 64 |
+
tower._electricBaseHeadY = head.position.y;
|
| 65 |
|
| 66 |
tower.trackedTargets = new Map();
|
| 67 |
tower.arcPool = [];
|
|
|
|
| 362 |
const head = tower.headMesh;
|
| 363 |
if (!head) return;
|
| 364 |
|
| 365 |
+
// Do not raise the head on level gain — height remains constant
|
| 366 |
+
const baseY = typeof tower._electricBaseHeadY === 'number' ? tower._electricBaseHeadY : head.position.y;
|
| 367 |
+
head.position.y = baseY;
|
| 368 |
tower.headTopY =
|
| 369 |
(tower.mesh?.position.y ?? 0.25) + head.position.y + 0.45;
|
| 370 |
|
| 371 |
+
// Remove any existing base level ring before creating a new one
|
| 372 |
+
if (tower.levelRing) {
|
| 373 |
+
tower.scene.remove(tower.levelRing);
|
| 374 |
+
tower.levelRing.geometry?.dispose?.();
|
| 375 |
+
if (tower.levelRing.material?.dispose) tower.levelRing.material.dispose();
|
| 376 |
+
tower.levelRing = null;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
if (lvl > 1) {
|
| 380 |
head.material.color?.set?.(0xa5d6ff);
|
| 381 |
head.material.emissive?.set?.(0x9a135a);
|
|
|
|
| 440 |
tower.arcPool.length = 0;
|
| 441 |
}
|
| 442 |
},
|
| 443 |
+
};
|
src/entities/towers/kinds/slow.js
CHANGED
|
@@ -83,8 +83,7 @@ export default {
|
|
| 83 |
tower.levelRing = null;
|
| 84 |
}
|
| 85 |
|
| 86 |
-
|
| 87 |
-
const visualExtra = Math.min(VISUAL_TOP_CAP, extraRaw);
|
| 88 |
|
| 89 |
if (lvl <= 1) {
|
| 90 |
if (baseMat) {
|
|
@@ -93,10 +92,8 @@ export default {
|
|
| 93 |
baseMat.emissiveIntensity = 0.0;
|
| 94 |
}
|
| 95 |
|
| 96 |
-
head
|
| 97 |
-
|
| 98 |
-
tower.headTopY =
|
| 99 |
-
(tower.mesh?.position.y ?? 0.25) + head.position.y + 0.55;
|
| 100 |
|
| 101 |
headMat.color?.set?.(0xffb6c1);
|
| 102 |
headMat.emissive?.set?.(0x4a0a2a);
|
|
@@ -108,9 +105,8 @@ export default {
|
|
| 108 |
baseMat.emissiveIntensity = 0.08;
|
| 109 |
}
|
| 110 |
|
| 111 |
-
head
|
| 112 |
-
|
| 113 |
-
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.6;
|
| 114 |
|
| 115 |
headMat.color?.set?.(0xffc6d9);
|
| 116 |
headMat.emissive?.set?.(0x9a135a);
|
|
@@ -141,4 +137,4 @@ export default {
|
|
| 141 |
tower.scene.add(ring);
|
| 142 |
}
|
| 143 |
},
|
| 144 |
-
};
|
|
|
|
| 83 |
tower.levelRing = null;
|
| 84 |
}
|
| 85 |
|
| 86 |
+
// No height increment on level gain — only rings should reflect level
|
|
|
|
| 87 |
|
| 88 |
if (lvl <= 1) {
|
| 89 |
if (baseMat) {
|
|
|
|
| 92 |
baseMat.emissiveIntensity = 0.0;
|
| 93 |
}
|
| 94 |
|
| 95 |
+
// Keep original head height/position; recompute top using current head position
|
| 96 |
+
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4;
|
|
|
|
|
|
|
| 97 |
|
| 98 |
headMat.color?.set?.(0xffb6c1);
|
| 99 |
headMat.emissive?.set?.(0x4a0a2a);
|
|
|
|
| 105 |
baseMat.emissiveIntensity = 0.08;
|
| 106 |
}
|
| 107 |
|
| 108 |
+
// Keep original head height/position; recompute top using current head position
|
| 109 |
+
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4;
|
|
|
|
| 110 |
|
| 111 |
headMat.color?.set?.(0xffc6d9);
|
| 112 |
headMat.emissive?.set?.(0x9a135a);
|
|
|
|
| 137 |
tower.scene.add(ring);
|
| 138 |
}
|
| 139 |
},
|
| 140 |
+
};
|
src/entities/towers/kinds/sniper.js
CHANGED
|
@@ -171,9 +171,7 @@ export default {
|
|
| 171 |
baseMat.roughness = 0.35;
|
| 172 |
}
|
| 173 |
|
| 174 |
-
//
|
| 175 |
-
head.scale.set(1, 1, 1);
|
| 176 |
-
head.position.y = 0.95 + visualExtra;
|
| 177 |
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.55;
|
| 178 |
|
| 179 |
headMat.color?.set?.(0xb0bec5);
|
|
@@ -191,10 +189,8 @@ export default {
|
|
| 191 |
baseMat.roughness = 0.3;
|
| 192 |
}
|
| 193 |
|
| 194 |
-
//
|
| 195 |
-
|
| 196 |
-
head.position.y = 0.95 + visualExtra;
|
| 197 |
-
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.6;
|
| 198 |
|
| 199 |
headMat.color?.set?.(0x90a4ae);
|
| 200 |
headMat.emissive?.set?.(0x550000);
|
|
@@ -228,7 +224,7 @@ export default {
|
|
| 228 |
tower.scene.add(ring);
|
| 229 |
}
|
| 230 |
|
| 231 |
-
//
|
| 232 |
const headTopOffset = 0.55;
|
| 233 |
tower.headTopY = tower.mesh.position.y + head.position.y + headTopOffset;
|
| 234 |
|
|
@@ -243,4 +239,4 @@ export default {
|
|
| 243 |
onDestroy(tower) {
|
| 244 |
this.removeLaser(tower);
|
| 245 |
},
|
| 246 |
-
};
|
|
|
|
| 171 |
baseMat.roughness = 0.35;
|
| 172 |
}
|
| 173 |
|
| 174 |
+
// Keep original head height/position; recompute top using current head position
|
|
|
|
|
|
|
| 175 |
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.55;
|
| 176 |
|
| 177 |
headMat.color?.set?.(0xb0bec5);
|
|
|
|
| 189 |
baseMat.roughness = 0.3;
|
| 190 |
}
|
| 191 |
|
| 192 |
+
// Keep original head height/position; recompute top using current head position
|
| 193 |
+
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.55;
|
|
|
|
|
|
|
| 194 |
|
| 195 |
headMat.color?.set?.(0x90a4ae);
|
| 196 |
headMat.emissive?.set?.(0x550000);
|
|
|
|
| 224 |
tower.scene.add(ring);
|
| 225 |
}
|
| 226 |
|
| 227 |
+
// Recompute headTopY using current head position
|
| 228 |
const headTopOffset = 0.55;
|
| 229 |
tower.headTopY = tower.mesh.position.y + head.position.y + headTopOffset;
|
| 230 |
|
|
|
|
| 239 |
onDestroy(tower) {
|
| 240 |
this.removeLaser(tower);
|
| 241 |
},
|
| 242 |
+
};
|