diff --git a/src/core/GameLoop.js b/src/core/GameLoop.js index eea26b2..0c86028 100644 --- a/src/core/GameLoop.js +++ b/src/core/GameLoop.js @@ -334,9 +334,10 @@ export class GameLoop { // Update unit mesh position const mesh = this.unitMeshes.get(activeUnit.id); if (mesh) { + // Floor surface is at pos.y - 0.5, unit should be 0.6 above: pos.y + 0.1 mesh.position.set( activeUnit.position.x, - activeUnit.position.y + 0.6, + activeUnit.position.y + 0.1, activeUnit.position.z ); } @@ -513,7 +514,8 @@ export class GameLoop { // Update Mesh const mesh = this.unitMeshes.get(existingUnit.id); if (mesh) { - mesh.position.set(targetTile.x, targetTile.y + 0.6, targetTile.z); + // Floor surface is at pos.y - 0.5, unit should be 0.6 above: pos.y + 0.1 + mesh.position.set(targetTile.x, targetTile.y + 0.1, targetTile.z); } console.log( `Moved ${existingUnit.name} to ${targetTile.x},${targetTile.y},${targetTile.z}` @@ -554,14 +556,36 @@ export class GameLoop { ) return; const enemyCount = 2; - for (let i = 0; i < enemyCount; i++) { + let attempts = 0; + const maxAttempts = this.enemySpawnZone.length * 2; // Try up to 2x the zone size + + for (let i = 0; i < enemyCount && attempts < maxAttempts; attempts++) { const spotIndex = Math.floor(Math.random() * this.enemySpawnZone.length); const spot = this.enemySpawnZone[spotIndex]; - if (spot && !this.grid.isOccupied(spot)) { + + if (!spot) continue; + + // Check if position is walkable (not just unoccupied) + // Find the correct walkable Y for this position + const walkableY = this.movementSystem?.findWalkableY( + spot.x, + spot.z, + spot.y + ); + if (walkableY === null) continue; + + const walkablePos = { x: spot.x, y: walkableY, z: spot.z }; + + // Check if position is not occupied and is walkable (not solid) + if ( + !this.grid.isOccupied(walkablePos) && + !this.grid.isSolid(walkablePos) + ) { const enemy = this.unitManager.createUnit("ENEMY_DEFAULT", "ENEMY"); - this.grid.placeUnit(enemy, spot); - this.createUnitMesh(enemy, spot); + this.grid.placeUnit(enemy, walkablePos); + this.createUnitMesh(enemy, walkablePos); this.enemySpawnZone.splice(spotIndex, 1); + i++; // Only increment if we successfully placed an enemy } } @@ -651,24 +675,92 @@ export class GameLoop { const reachablePositions = this.movementSystem.getReachableTiles(activeUnit); - // Create blue highlight material - const highlightMaterial = new THREE.MeshBasicMaterial({ - color: 0x0066ff, // Blue color + // Create glowing blue outline materials with multiple layers for enhanced glow + // Outer glow layers (fade outward, decreasing opacity) + const outerGlowMaterial = new THREE.LineBasicMaterial({ + color: 0x0066ff, transparent: true, - opacity: 0.4, + opacity: 0.3, }); - // Create geometry for highlights (plane on the ground) - const geometry = new THREE.PlaneGeometry(1, 1); - geometry.rotateX(-Math.PI / 2); + const midGlowMaterial = new THREE.LineBasicMaterial({ + color: 0x0088ff, + transparent: true, + opacity: 0.5, + }); - // Create highlight meshes for each reachable position + // Inner bright outline (main glow - brightest) + const highlightMaterial = new THREE.LineBasicMaterial({ + color: 0x00ccff, // Very bright cyan-blue for maximum visibility + transparent: true, + opacity: 1.0, + }); + + // Thick inner outline (for thickness simulation) + const thickMaterial = new THREE.LineBasicMaterial({ + color: 0x00aaff, + transparent: true, + opacity: 0.8, + }); + + // Create base plane geometry for the tile + const baseGeometry = new THREE.PlaneGeometry(1, 1); + baseGeometry.rotateX(-Math.PI / 2); + + // Create highlight outlines for each reachable position reachablePositions.forEach((pos) => { - const mesh = new THREE.Mesh(geometry, highlightMaterial); - // Position just above floor surface (pos.y is the air space, floor surface is at pos.y) - mesh.position.set(pos.x, pos.y + 0.01, pos.z); - this.scene.add(mesh); - this.movementHighlights.add(mesh); + // Get the correct floor surface height for this position + const walkableY = this.movementSystem.findWalkableY(pos.x, pos.z, pos.y); + if (walkableY === null) return; // Skip if no valid floor found + + // Floor surface is at the walkable Y coordinate (top of the floor block) + // Adjust by -0.5 to account for voxel centering + const floorSurfaceY = walkableY - 0.5; + + // Create multiple glow layers for enhanced visibility and fade effect + // Outer glow (largest, most transparent) + const outerGlowGeometry = new THREE.PlaneGeometry(1.15, 1.15); + outerGlowGeometry.rotateX(-Math.PI / 2); + const outerGlowEdges = new THREE.EdgesGeometry(outerGlowGeometry); + const outerGlowLines = new THREE.LineSegments( + outerGlowEdges, + outerGlowMaterial + ); + outerGlowLines.position.set(pos.x, floorSurfaceY + 0.003, pos.z); + this.scene.add(outerGlowLines); + this.movementHighlights.add(outerGlowLines); + + // Mid glow (medium size) + const midGlowGeometry = new THREE.PlaneGeometry(1.08, 1.08); + midGlowGeometry.rotateX(-Math.PI / 2); + const midGlowEdges = new THREE.EdgesGeometry(midGlowGeometry); + const midGlowLines = new THREE.LineSegments( + midGlowEdges, + midGlowMaterial + ); + midGlowLines.position.set(pos.x, floorSurfaceY + 0.002, pos.z); + this.scene.add(midGlowLines); + this.movementHighlights.add(midGlowLines); + + // Thick inner outline (slightly larger than base for thickness) + const thickGeometry = new THREE.PlaneGeometry(1.02, 1.02); + thickGeometry.rotateX(-Math.PI / 2); + const thickEdges = new THREE.EdgesGeometry(thickGeometry); + const thickLines = new THREE.LineSegments(thickEdges, thickMaterial); + thickLines.position.set(pos.x, floorSurfaceY + 0.001, pos.z); + this.scene.add(thickLines); + this.movementHighlights.add(thickLines); + + // Main bright outline (exact size, brightest) + const edgesGeometry = new THREE.EdgesGeometry(baseGeometry); + const lineSegments = new THREE.LineSegments( + edgesGeometry, + highlightMaterial + ); + // Position exactly on floor surface + lineSegments.position.set(pos.x, floorSurfaceY, pos.z); + this.scene.add(lineSegments); + this.movementHighlights.add(lineSegments); }); } @@ -684,7 +776,9 @@ export class GameLoop { else if (unit.team === "ENEMY") color = 0x550000; const material = new THREE.MeshStandardMaterial({ color: color }); const mesh = new THREE.Mesh(geometry, material); - mesh.position.set(pos.x, pos.y + 0.6, pos.z); + // Floor surface is at pos.y - 0.5 (floor block at pos.y-1, top at pos.y-0.5) + // Unit should be 0.6 units above floor surface: (pos.y - 0.5) + 0.6 = pos.y + 0.1 + mesh.position.set(pos.x, pos.y + 0.1, pos.z); this.scene.add(mesh); this.unitMeshes.set(unit.id, mesh); } diff --git a/src/systems/MovementSystem.js b/src/systems/MovementSystem.js index 6db60a3..e28897e 100644 --- a/src/systems/MovementSystem.js +++ b/src/systems/MovementSystem.js @@ -106,11 +106,12 @@ export class MovementSystem { if (walkableY === null) continue; const pos = { x, y: walkableY, z }; - const posKey = `${x},${y},${z}`; + // Use walkableY in the key, not the reference y + const posKey = `${x},${walkableY},${z}`; // Check if position is not occupied (or is the starting position) // Starting position is always reachable (unit is already there) - const isStartPos = x === start.x && z === start.z && y === start.y; + const isStartPos = x === start.x && z === start.z && walkableY === start.y; if (!this.grid.isOccupied(pos) || isStartPos) { reachable.add(posKey); }