Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 6 additions & 1 deletion src/data/abilities/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ export interface AbilityDataDefinition {
}

// ==================== ABILITY DEFINITIONS ====================
// NOTE: These TypeScript definitions are for reference and type checking only.
// The authoritative ability data is loaded from JSON files at runtime:
// public/data/factions/{faction}/abilities.json
// Any gameplay-affecting values should be updated in the JSON files.
// This TS file may be removed in a future refactor.

export const ABILITY_DEFINITIONS: Record<string, AbilityDataDefinition> = {
// === INFANTRY ABILITIES ===
Expand All @@ -106,7 +111,7 @@ export const ABILITY_DEFINITIONS: Record<string, AbilityDataDefinition> = {
description: 'Increases attack speed and movement speed at the cost of health.',
targetType: 'instant',
healthCost: 10,
cooldown: 0, // Can use any time
cooldown: 10, // Matches JSON authority: public/data/factions/dominion/abilities.json
duration: 11,
effects: [
{ type: 'buff', value: 1.5, duration: 11 }, // 50% speed boost
Expand Down
17 changes: 1 addition & 16 deletions src/data/rendering.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,26 +339,11 @@ export const RENDER_ORDER = {

/** Faction-specific projectile colors */
export const FACTION_COLORS = {
terran: {
primary: 0xffaa00, // Orange-yellow tracer
secondary: 0xff6600, // Orange trail
glow: 0xffdd44, // Bright yellow glow
},
dominion: {
primary: 0xffaa00, // Orange-yellow tracer (same as terran for now)
primary: 0xffaa00, // Orange-yellow tracer
secondary: 0xff6600, // Orange trail
glow: 0xffdd44, // Bright yellow glow
},
protoss: {
primary: 0x4488ff, // Blue energy
secondary: 0x8844ff, // Purple trail
glow: 0x66aaff, // Cyan glow
},
zerg: {
primary: 0x88ff44, // Acid green
secondary: 0x44aa00, // Dark green trail
glow: 0xaaff66, // Bright green glow
},
neutral: {
Comment on lines 341 to 347

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore a valid fallback faction color

By removing the legacy keys here, any code that still falls back to 'terran' now resolves FACTION_COLORS[faction] to undefined and will throw when accessing .primary/.glow. BattleEffectsRenderer still defaults to 'terran' for attackerFaction/faction (see lines ~721 and ~761 in src/rendering/effects/BattleEffectsRenderer.ts), so missing/unknown factions will now crash projectile effects at runtime. Either update those fallbacks to an existing key (e.g. dominion/neutral) or add a defensive fallback when indexing FACTION_COLORS.

Useful? React with 👍 / 👎.

primary: 0xffaa00,
secondary: 0xff6600,
Expand Down
26 changes: 24 additions & 2 deletions src/hooks/useMultiplayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,34 @@ export interface GameStartPayload {
startTime: number;
}

// STUN servers
const ICE_SERVERS: RTCIceServer[] = [
// Default STUN servers (configurable via NEXT_PUBLIC_ICE_SERVERS env var)
const DEFAULT_ICE_SERVERS: RTCIceServer[] = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
];

function getIceServers(): RTCIceServer[] {
// Next.js inlines NEXT_PUBLIC_* env vars at build time
const envServers = (
typeof process !== 'undefined' ? process.env?.NEXT_PUBLIC_ICE_SERVERS : undefined
) as string | undefined;
if (envServers) {
try {
const parsed = JSON.parse(envServers) as RTCIceServer[];
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed;
}
} catch {
debugNetworking.warn('[Lobby] Invalid NEXT_PUBLIC_ICE_SERVERS format, using defaults');
}
}
return DEFAULT_ICE_SERVERS;
}

const ICE_SERVERS = getIceServers();

export type LobbyStatus =
| 'disabled' // Nostr not active (single-player mode)
| 'initializing'
Expand Down
8 changes: 7 additions & 1 deletion src/hooks/usePublicLobbies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,13 @@ export async function publishPublicLobby(
// Publish to relays
const results = await Promise.allSettled(
relays.map(r =>
Promise.all(pool.publish([r], event)).catch(() => [])
Promise.all(pool.publish([r], event)).catch((err) => {
// Log non-rate-limit errors for debugging
if (!err?.message?.includes('rate-limit')) {
debugNetworking.warn(`[LobbyBrowser] Relay publish failed for ${r}:`, err);
}
return [];
})
)
);

Expand Down
19 changes: 14 additions & 5 deletions src/workers/ai-decisions.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
*/

import { distance, SeededRandom } from '@/utils/math';

// Debug flag for worker logging (workers can't access UI store)
const _DEBUG = false;
import { debugAI, setWorkerDebugSettings } from '@/utils/debugLogger';
import type { DebugSettings } from '@/store/uiStore';

// Unit priority for focus fire (higher = more important to kill)
const DEFAULT_UNIT_PRIORITY: Record<string, number> = {
Expand Down Expand Up @@ -105,7 +104,12 @@ interface EvaluateMicroMessage {
seed: number;
}

type WorkerMessage = InitMessage | EvaluateMicroMessage;
interface SetDebugSettingsMessage {
type: 'setDebugSettings';
settings: DebugSettings;
}

type WorkerMessage = InitMessage | EvaluateMicroMessage | SetDebugSettingsMessage;

// Decision types returned to main thread
interface MicroDecision {
Expand Down Expand Up @@ -147,7 +151,7 @@ function init(workerConfig: AIWorkerConfig): boolean {
initialized = true;
return true;
} catch (error) {
console.error('[AIWorker] Init failed:', error);
debugAI.error('[AIWorker] Init failed:', error);
return false;
}
}
Expand Down Expand Up @@ -567,6 +571,11 @@ self.onmessage = (event: MessageEvent<WorkerMessage>) => {
});
break;
}

case 'setDebugSettings': {
setWorkerDebugSettings(message.settings);
break;
}
}
};

Expand Down
80 changes: 39 additions & 41 deletions src/workers/pathfinding.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,15 @@ import {
import { generateTileCache, generateSoloNavMesh } from '@recast-navigation/generators';
import { distance } from '@/utils/math';
import { NAVMESH_CONFIG, SOLO_NAVMESH_CONFIG } from '@/data/pathfinding.config';
import { debugPathfinding, setWorkerDebugSettings } from '@/utils/debugLogger';
import type { DebugSettings } from '@/store/uiStore';

const WALKABLE_DISTANCE_TOLERANCE = Math.max(
NAVMESH_CONFIG.walkableRadius * 1.5,
NAVMESH_CONFIG.cs * 1.5
);
const WALKABLE_HEIGHT_TOLERANCE = NAVMESH_CONFIG.walkableClimb * 2;

// Debug flag for worker logging (workers can't access UI store)
const DEBUG = false;

// Message types
interface InitMessage {
type: 'init';
Expand Down Expand Up @@ -130,6 +129,11 @@ interface IsWaterWalkableMessage {
y: number;
}

interface SetDebugSettingsMessage {
type: 'setDebugSettings';
settings: DebugSettings;
}

type WorkerMessage =
| InitMessage
| LoadNavMeshMessage
Expand All @@ -141,7 +145,8 @@ type WorkerMessage =
| FindNearestPointMessage
| LoadWaterNavMeshMessage
| FindWaterPathMessage
| IsWaterWalkableMessage;
| IsWaterWalkableMessage
| SetDebugSettingsMessage;

// Ground navmesh state
let navMesh: NavMesh | null = null;
Expand Down Expand Up @@ -174,7 +179,7 @@ async function initialize(): Promise<boolean> {
initialized = true;
return true;
} catch (error) {
console.error('[PathfindingWorker] WASM init failed:', error);
debugPathfinding.error('[PathfindingWorker] WASM init failed:', error);
return false;
}
}
Expand All @@ -184,14 +189,14 @@ async function initialize(): Promise<boolean> {
*/
function loadNavMesh(data: Uint8Array): boolean {
if (!initialized) {
console.error('[PathfindingWorker] WASM not initialized');
debugPathfinding.error('[PathfindingWorker] WASM not initialized');
return false;
}

try {
const result = importNavMesh(data);
if (!result.navMesh) {
console.error('[PathfindingWorker] Failed to import navmesh');
debugPathfinding.error('[PathfindingWorker] Failed to import navmesh');
return false;
}

Expand All @@ -211,7 +216,7 @@ function loadNavMesh(data: Uint8Array): boolean {

return true;
} catch (error) {
console.error('[PathfindingWorker] Error loading navmesh:', error);
debugPathfinding.error('[PathfindingWorker] Error loading navmesh:', error);
return false;
}
}
Expand All @@ -227,19 +232,16 @@ function loadNavMeshFromGeometry(
heightMapH?: number
): boolean {
if (!initialized) {
console.error('[PathfindingWorker] WASM not initialized');
debugPathfinding.error('[PathfindingWorker] WASM not initialized');
return false;
}

if (DEBUG) {
// eslint-disable-next-line no-console -- Debug logging gated by DEBUG flag
console.log('[PathfindingWorker] Generating navmesh from geometry:', {
vertices: positions.length / 3,
triangles: indices.length / 3,
positionsType: positions.constructor.name,
indicesType: indices.constructor.name,
});
}
debugPathfinding.log('[PathfindingWorker] Generating navmesh from geometry:', {
vertices: positions.length / 3,
triangles: indices.length / 3,
positionsType: positions.constructor.name,
indicesType: indices.constructor.name,
});

if (heightMapData && heightMapW && heightMapH) {
heightMap = heightMapData;
Expand All @@ -264,17 +266,13 @@ function loadNavMeshFromGeometry(
heightMapWidth = heightMapW;
heightMapHeight = heightMapH;
}
// eslint-disable-next-line no-console -- Debug logging gated by DEBUG flag
if (DEBUG) console.log('[PathfindingWorker] TileCache navmesh generated successfully');
debugPathfinding.log('[PathfindingWorker] TileCache navmesh generated successfully');
return true;
}

// TileCache failed - try solo navmesh fallback
if (DEBUG) {
console.warn('[PathfindingWorker] TileCache generation failed:', (result as { error?: string }).error);
// eslint-disable-next-line no-console -- Debug logging gated by DEBUG flag
console.log('[PathfindingWorker] Trying solo navmesh fallback...');
}
debugPathfinding.warn('[PathfindingWorker] TileCache generation failed:', (result as { error?: string }).error);
debugPathfinding.log('[PathfindingWorker] Trying solo navmesh fallback...');

// Solo navmesh fallback (no dynamic obstacles but more robust)
const soloResult = generateSoloNavMesh(positions, indices, SOLO_NAVMESH_CONFIG);
Expand All @@ -288,15 +286,14 @@ function loadNavMeshFromGeometry(
heightMapWidth = heightMapW;
heightMapHeight = heightMapH;
}
// eslint-disable-next-line no-console -- Debug logging gated by DEBUG flag
if (DEBUG) console.log('[PathfindingWorker] Solo navmesh generated successfully');
debugPathfinding.log('[PathfindingWorker] Solo navmesh generated successfully');
return true;
}

console.error('[PathfindingWorker] Solo navmesh also failed:', (soloResult as { error?: string }).error);
debugPathfinding.error('[PathfindingWorker] Solo navmesh also failed:', (soloResult as { error?: string }).error);
return false;
} catch (error) {
console.error('[PathfindingWorker] Error generating navmesh:', error);
debugPathfinding.error('[PathfindingWorker] Error generating navmesh:', error);
return false;
}
}
Expand Down Expand Up @@ -552,17 +549,14 @@ function removeObstacle(entityId: number): boolean {
*/
function loadWaterNavMesh(positions: Float32Array, indices: Uint32Array): boolean {
if (!initialized) {
console.error('[PathfindingWorker] WASM not initialized');
debugPathfinding.error('[PathfindingWorker] WASM not initialized');
return false;
}

if (DEBUG) {
// eslint-disable-next-line no-console -- Debug logging gated by DEBUG flag
console.log('[PathfindingWorker] Generating water navmesh:', {
vertices: positions.length / 3,
triangles: indices.length / 3,
});
}
debugPathfinding.log('[PathfindingWorker] Generating water navmesh:', {
vertices: positions.length / 3,
triangles: indices.length / 3,
});

try {
// Use solo navmesh for water (no dynamic obstacles needed on water)
Expand All @@ -571,15 +565,14 @@ function loadWaterNavMesh(positions: Float32Array, indices: Uint32Array): boolea
if (result.success && result.navMesh) {
waterNavMesh = result.navMesh;
waterNavMeshQuery = new NavMeshQuery(waterNavMesh);
// eslint-disable-next-line no-console -- Debug logging gated by DEBUG flag
if (DEBUG) console.log('[PathfindingWorker] Water navmesh generated successfully');
debugPathfinding.log('[PathfindingWorker] Water navmesh generated successfully');
return true;
}

console.error('[PathfindingWorker] Water navmesh generation failed');
debugPathfinding.error('[PathfindingWorker] Water navmesh generation failed');
return false;
} catch (error) {
console.error('[PathfindingWorker] Error generating water navmesh:', error);
debugPathfinding.error('[PathfindingWorker] Error generating water navmesh:', error);
return false;
}
}
Expand Down Expand Up @@ -771,6 +764,11 @@ self.onmessage = async (event: MessageEvent<WorkerMessage>) => {
});
break;
}

case 'setDebugSettings': {
setWorkerDebugSettings(message.settings);
break;
}
}
};

Expand Down
Loading
Loading