victor HF Staff commited on
Commit
0ee2f3f
·
1 Parent(s): 72b06d8
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
- const extraRaw = Math.max(0, (lvl - 1) * VISUAL_TOP_INCREMENT);
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.scale.set(1, 1, 1);
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.scale.set(1, 2, 1);
103
- head.position.y = 0.65 + 0.4 + visualExtra;
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
- const liftRaw = Math.max(0, (lvl - 1) * ELECTRIC_BALL_LIFT_PER_LEVEL);
364
- const lift = Math.min(ELECTRIC_BALL_LIFT_CAP, liftRaw);
365
- head.position.y = head.position.y + lift;
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
- const extraRaw = Math.max(0, (lvl - 1) * VISUAL_TOP_INCREMENT);
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.scale.set(1, 1, 1);
97
- head.position.y = 0.8 + visualExtra;
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.scale.set(1.1, 1.15, 1.1);
112
- head.position.y = 0.9 + visualExtra;
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
- // Sniper cone head baseline
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
- // Slightly larger cone; raise by visualExtra
195
- head.scale.set(1.1, 1.2, 1.1);
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
- // Update headTopY after position changes
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
+ };