Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/components/game/WebGPUGameCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function WebGPUGameCanvas() {
});

// Input handling hook
const { selectionState, handleMouseDown, handleMouseMove, handleMouseUp, handleContextMenu } = useGameInput({
const { selectionState } = useGameInput({
containerRef,
cameraRef: refs.camera,
gameRef,
Expand Down Expand Up @@ -646,10 +646,6 @@ export function WebGPUGameCanvas() {
<div
ref={containerRef}
className="absolute inset-0"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onContextMenu={handleContextMenu}
>
{/* Loading screen */}
{isLoading && (
Expand Down
40 changes: 0 additions & 40 deletions src/components/game/hooks/useGameInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ export interface UseGameInputProps {

export interface UseGameInputReturn {
selectionState: SelectionState;
/** @deprecated Use InputManager.getInstance() directly */
handleMouseDown: (e: React.MouseEvent) => void;
/** @deprecated Use InputManager.getInstance() directly */
handleMouseMove: (e: React.MouseEvent) => void;
/** @deprecated Use InputManager.getInstance() directly */
handleMouseUp: (e: React.MouseEvent) => void;
/** @deprecated Use InputManager.getInstance() directly */
handleContextMenu: (e: React.MouseEvent) => void;
}

// Default selection state for SSR or before initialization
Expand Down Expand Up @@ -185,39 +177,7 @@ export function useGameInput({
() => defaultSelectionState // Server snapshot
);

// =============================================================================
// LEGACY EVENT HANDLERS (for backwards compatibility)
// =============================================================================

// These are kept for backwards compatibility but are no longer needed
// since InputManager handles events directly on the container
const handleMouseDown = useCallback((e: React.MouseEvent) => {
// Events are now handled by InputManager
// This handler is kept for backwards compatibility
}, []);

const handleMouseMove = useCallback((e: React.MouseEvent) => {
// Events are now handled by InputManager
}, []);

const handleMouseUp = useCallback((e: React.MouseEvent) => {
// Events are now handled by InputManager
}, []);

const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault();
// Events are now handled by InputManager
}, []);

// =============================================================================
// RETURN
// =============================================================================

return {
selectionState,
handleMouseDown,
handleMouseMove,
handleMouseUp,
handleContextMenu,
};
}
74 changes: 23 additions & 51 deletions src/data/maps/MapTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,16 @@ export const TERRAIN_FEATURE_CONFIG: Record<TerrainFeature, TerrainFeatureConfig
/**
* Elevation now uses 0-255 range for smooth terrain.
* Gameplay zones for high-ground advantage:
* - Low ground: 0-85
* - Mid ground: 86-170
* - High ground: 171-255
* - Low ground: 0-85 (use ~60 for standard low)
* - Mid ground: 86-170 (use ~140 for standard mid)
* - High ground: 171-255 (use ~220 for standard high)
*/
export type Elevation = number; // 0-255

/**
* Legacy elevation levels for backwards compatibility with ramps
*/
export type ElevationLevel = 0 | 1 | 2;

/**
* Convert legacy 0-2 elevation to 0-255 scale
*/
export function legacyElevationTo256(level: ElevationLevel): Elevation {
const mapping: Record<ElevationLevel, Elevation> = {
0: 60, // Low ground
1: 140, // Mid ground
2: 220, // High ground
};
return mapping[level];
}
/** Standard elevation values for gameplay zones */
export const ELEVATION_LOW = 60;
export const ELEVATION_MID = 140;
export const ELEVATION_HIGH = 220;

/**
* Convert 0-255 elevation to gameplay zone (low/mid/high)
Expand Down Expand Up @@ -249,22 +237,17 @@ export function createTerrainGrid(
width: number,
height: number,
defaultTerrain: TerrainType = 'ground',
defaultElevation: ElevationLevel | Elevation = 1,
defaultElevation: Elevation = ELEVATION_MID,
defaultFeature: TerrainFeature = 'none'
): MapCell[][] {
const grid: MapCell[][] = [];

// Convert legacy elevation if needed
const elevation256 = typeof defaultElevation === 'number' && defaultElevation <= 2
? legacyElevationTo256(defaultElevation as ElevationLevel)
: defaultElevation as Elevation;

for (let y = 0; y < height; y++) {
grid[y] = [];
for (let x = 0; x < width; x++) {
grid[y][x] = {
terrain: defaultTerrain,
elevation: elevation256,
elevation: defaultElevation,
feature: defaultFeature,
textureId: Math.floor(Math.random() * 4), // Random texture variation
};
Expand Down Expand Up @@ -452,14 +435,9 @@ export function fillTerrainRect(
width: number,
height: number,
terrain: TerrainType,
elevation?: ElevationLevel | Elevation,
elevation?: Elevation,
feature?: TerrainFeature
): void {
// Convert legacy elevation if provided
const elevation256 = elevation !== undefined
? (elevation <= 2 ? legacyElevationTo256(elevation as ElevationLevel) : elevation)
: undefined;

for (let dy = 0; dy < height; dy++) {
for (let dx = 0; dx < width; dx++) {
const px = Math.floor(x + dx);
Expand All @@ -472,8 +450,8 @@ export function fillTerrainRect(
}

grid[py][px].terrain = terrain;
if (elevation256 !== undefined) {
grid[py][px].elevation = elevation256;
if (elevation !== undefined) {
grid[py][px].elevation = elevation;
}
if (feature !== undefined) {
grid[py][px].feature = feature;
Expand All @@ -491,14 +469,9 @@ export function fillTerrainCircle(
centerY: number,
radius: number,
terrain: TerrainType,
elevation?: ElevationLevel | Elevation,
elevation?: Elevation,
feature?: TerrainFeature
): void {
// Convert legacy elevation if provided
const elevation256 = elevation !== undefined
? (elevation <= 2 ? legacyElevationTo256(elevation as ElevationLevel) : elevation)
: undefined;

for (let y = -radius; y <= radius; y++) {
for (let x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius) {
Expand All @@ -512,8 +485,8 @@ export function fillTerrainCircle(
}

grid[py][px].terrain = terrain;
if (elevation256 !== undefined) {
grid[py][px].elevation = elevation256;
if (elevation !== undefined) {
grid[py][px].elevation = elevation;
}
if (feature !== undefined) {
grid[py][px].feature = feature;
Expand Down Expand Up @@ -577,18 +550,17 @@ export function createRampInTerrain(
* @param centerX - Center X of the platform
* @param centerY - Center Y of the platform
* @param radius - Radius of the buildable area
* @param elevation - Elevation level (0, 1, or 2)
* @param elevation - Elevation value (0-255, use ELEVATION_LOW/MID/HIGH constants)
* @param cliffWidth - Width of the cliff ring around the platform (default 3)
*/
export function createRaisedPlatform(
grid: MapCell[][],
centerX: number,
centerY: number,
radius: number,
elevation: ElevationLevel,
elevation: Elevation,
cliffWidth: number = 3
): void {
const elevation256 = legacyElevationTo256(elevation);
const outerRadius = radius + cliffWidth;

for (let dy = -outerRadius; dy <= outerRadius; dy++) {
Expand All @@ -607,7 +579,7 @@ export function createRaisedPlatform(
// Inner buildable area
grid[py][px] = {
terrain: 'ground',
elevation: elevation256,
elevation: elevation,
feature: 'none',
textureId: Math.floor(Math.random() * 4),
};
Expand All @@ -616,7 +588,7 @@ export function createRaisedPlatform(
if (!isRampOrNearRamp(grid, px, py, cliffWidth + 1)) {
grid[py][px] = {
terrain: 'unwalkable',
elevation: elevation256,
elevation: elevation,
feature: 'cliff',
textureId: Math.floor(Math.random() * 4),
};
Expand All @@ -630,17 +602,17 @@ export function createRaisedPlatform(
/**
* Create a raised rectangular platform with cliff edges.
* Useful for bases that need non-circular shapes.
* @param elevation - Elevation value (0-255, use ELEVATION_LOW/MID/HIGH constants)
*/
export function createRaisedRect(
grid: MapCell[][],
x: number,
y: number,
width: number,
height: number,
elevation: ElevationLevel,
elevation: Elevation,
cliffWidth: number = 3
): void {
const elevation256 = legacyElevationTo256(elevation);

// Create outer cliff ring first
for (let dy = -cliffWidth; dy < height + cliffWidth; dy++) {
Expand All @@ -661,15 +633,15 @@ export function createRaisedRect(
// Inner buildable area
grid[py][px] = {
terrain: 'ground',
elevation: elevation256,
elevation: elevation,
feature: 'none',
textureId: Math.floor(Math.random() * 4),
};
} else if (isOuter && !isRampOrNearRamp(grid, px, py, cliffWidth + 1)) {
// Cliff edge - buffer must be >= cliff width to ensure gap for ramps
grid[py][px] = {
terrain: 'unwalkable',
elevation: elevation256,
elevation: elevation,
feature: 'cliff',
textureId: Math.floor(Math.random() * 4),
};
Expand Down
14 changes: 7 additions & 7 deletions src/rendering/EnvironmentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class EnvironmentManager {
private waterMesh: WaterMesh | null = null;
private mapBorderFog: TSLMapBorderFog | null = null;
private particles: EnvironmentParticles | null = null;
private legacyDecorations: MapDecorations | null = null;
private mapDecorations: MapDecorations | null = null;
// AAA decoration light manager - pools 50 lights for hundreds of emissive decorations
private decorationLightManager: DecorationLightManager | null = null;
// Centralized emissive decoration manager with animation and light attachment
Expand Down Expand Up @@ -269,8 +269,8 @@ export class EnvironmentManager {
}

// MapDecorations handles watch towers and destructibles (non-instanced objects)
this.legacyDecorations = new MapDecorations(this.mapData, this.terrain, this.scene, this.decorationLightManager);
this.scene.add(this.legacyDecorations.group);
this.mapDecorations = new MapDecorations(this.mapData, this.terrain, this.scene, this.decorationLightManager);
this.scene.add(this.mapDecorations.group);
}

/**
Expand All @@ -296,8 +296,8 @@ export class EnvironmentManager {
}

// Update emissive decoration pulsing animation
if (this.legacyDecorations) {
this.legacyDecorations.update(deltaTime);
if (this.mapDecorations) {
this.mapDecorations.update(deltaTime);
}

// Update emissive decoration manager (crystal pulsing, etc.)
Expand Down Expand Up @@ -781,7 +781,7 @@ export class EnvironmentManager {
if (this.waterMesh) this.scene.remove(this.waterMesh.group);
if (this.mapBorderFog) this.scene.remove(this.mapBorderFog.mesh);
if (this.particles) this.scene.remove(this.particles.points);
if (this.legacyDecorations) this.scene.remove(this.legacyDecorations.group);
if (this.mapDecorations) this.scene.remove(this.mapDecorations.group);

// STEP 2: Now safe to dispose resources (no longer being rendered)
this.trees?.dispose();
Expand All @@ -794,7 +794,7 @@ export class EnvironmentManager {
this.waterMesh?.dispose();
this.mapBorderFog?.dispose();
this.particles?.dispose();
this.legacyDecorations?.dispose();
this.mapDecorations?.dispose();
this.decorationLightManager?.dispose();
this.emissiveDecorationManager?.dispose();
this.emissiveLightPool?.dispose();
Expand Down
2 changes: 1 addition & 1 deletion src/rendering/Terrain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { MapData, MapCell, TerrainType, ElevationLevel, Elevation, TerrainFeature, TERRAIN_FEATURE_CONFIG } from '@/data/maps';
import { MapData, MapCell, TerrainType, Elevation, TerrainFeature, TERRAIN_FEATURE_CONFIG } from '@/data/maps';
import { BiomeConfig, BIOMES, blendBiomeColors, BiomeType } from './Biomes';
import { TSLTerrainMaterial } from './tsl/TerrainMaterial';
import AssetManager from '@/assets/AssetManager';
Expand Down
22 changes: 0 additions & 22 deletions src/utils/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,6 @@ export function angle(x1: number, y1: number, x2: number, y2: number): number {
return Math.atan2(y2 - y1, x2 - x1);
}

/**
* @internal NON-DETERMINISTIC - DO NOT USE IN GAMEPLAY CODE
* Uses Math.random() which breaks multiplayer sync. For gameplay randomness,
* use SeededRandom instead. This function exists only for non-gameplay uses
* like UI effects or debugging.
* @deprecated Use SeededRandom.nextRange() for deterministic gameplay
*/
export function randomRange(min: number, max: number): number {
return min + Math.random() * (max - min);
}

/**
* @internal NON-DETERMINISTIC - DO NOT USE IN GAMEPLAY CODE
* Uses Math.random() which breaks multiplayer sync. For gameplay randomness,
* use SeededRandom instead. This function exists only for non-gameplay uses
* like UI effects or debugging.
* @deprecated Use SeededRandom.nextInt() for deterministic gameplay
*/
export function randomInt(min: number, max: number): number {
return Math.floor(randomRange(min, max + 1));
}

// Seeded random for deterministic gameplay
export class SeededRandom {
private seed: number;
Expand Down
12 changes: 0 additions & 12 deletions tests/utils/math.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import {
distanceXY,
normalize,
angle,
randomRange,
randomInt,
SeededRandom,
} from '@/utils/math';

Expand Down Expand Up @@ -47,16 +45,6 @@ describe('math utilities', () => {
expect(angle(0, 0, 0, 1)).toBe(Math.PI / 2);
});

it('uses Math.random for non-deterministic helpers', () => {
const originalRandom = Math.random;
Math.random = () => 0.5;

expect(randomRange(0, 10)).toBe(5);
expect(randomInt(0, 9)).toBe(5);

Math.random = originalRandom;
});

it('generates deterministic sequences with SeededRandom', () => {
const rngA = new SeededRandom(42);
const rngB = new SeededRandom(42);
Expand Down