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 |
+
};
|