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
8 changes: 7 additions & 1 deletion src/components/game/BattleSimulatorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ export const BattleSimulatorPanel = memo(function BattleSimulatorPanel() {
return;
}

// Register both players as AI-controlled so the AI takes over and fights
const player1Faction = playerSlots[0]?.faction ?? 'dominion';
const player2Faction = playerSlots[1]?.faction ?? 'dominion';
bridge.registerAI('player1', player1Faction, 'medium');
bridge.registerAI('player2', player2Faction, 'medium');

const currentTick = bridge.currentTick;

// Get all units from render state adapter
Expand Down Expand Up @@ -278,7 +284,7 @@ export const BattleSimulatorPanel = memo(function BattleSimulatorPanel() {
bridge.resume();
setIsPaused(false);
setSelectedUnit(null);
}, []);
}, [playerSlots]);

const handlePauseToggle = useCallback(() => {
const bridge = getWorkerBridge();
Expand Down
9 changes: 7 additions & 2 deletions src/components/game/hooks/useWorkerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import {
type RenderState,
type GameEvent,
} from '@/engine/workers';
import { useGameSetupStore, getLocalPlayerId, isBattleSimulatorMode, type GameSetupState } from '@/store/gameSetupStore';
import {
useGameSetupStore,
getLocalPlayerId,
isBattleSimulatorMode,
type GameSetupState,
} from '@/store/gameSetupStore';
import { useUIStore } from '@/store/uiStore';
import { useGameStore } from '@/store/gameStore';
import { isMultiplayerMode } from '@/store/multiplayerStore';
Expand Down Expand Up @@ -158,7 +163,7 @@ export function useWorkerBridge({ map, onGameOver }: UseWorkerBridgeProps): UseW
tickRate: 20,
isMultiplayer,
playerId: localPlayerId ?? 'spectator',
aiEnabled: !isBattleSimulatorMode() && !isMultiplayer,
aiEnabled: !isMultiplayer,
aiDifficulty: 'medium',
fogOfWar,
},
Expand Down
25 changes: 25 additions & 0 deletions src/engine/workers/GameWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,14 @@ export class WorkerGame extends GameCore {
return this.gameTime;
}

/**
* Force send a render state update to the main thread.
* Used when entities are spawned/destroyed while the game is paused.
*/
public forceRenderStateUpdate(): void {
this.sendRenderState();
}

// ============================================================================
// RENDER STATE COLLECTION
// ============================================================================
Expand Down Expand Up @@ -1417,12 +1425,29 @@ if (typeof self !== 'undefined') {
y: message.y,
playerId: message.playerId,
});
// Send render state so main thread sees the new entity (even when paused)
game.forceRenderStateUpdate();
break;
}

case 'destroyEntity': {
if (!game) return;
game.eventBus.emit('entity:destroy', { entityId: message.entityId });
// Send render state so main thread sees the entity removal (even when paused)
game.forceRenderStateUpdate();
break;
}

case 'registerAI': {
if (!game) return;
const enhancedAI = game.world.getSystem(EnhancedAISystem);
if (enhancedAI) {
enhancedAI.registerAI(
message.playerId,
message.faction,
message.difficulty ?? 'medium'
);
}
break;
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/engine/workers/WorkerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,22 @@ export class WorkerBridge {
} satisfies MainToWorkerMessage);
}

/**
* Register a player as AI-controlled (for battle simulator)
*/
public registerAI(
playerId: string,
faction: string,
difficulty: 'easy' | 'medium' | 'hard' | 'insane' = 'medium'
): void {
this.worker?.postMessage({
type: 'registerAI',
playerId,
faction,
difficulty,
} satisfies MainToWorkerMessage);
}

public setControlGroup(groupNumber: number, entityIds: number[]): void {
this.worker?.postMessage({
type: 'setControlGroup',
Expand Down
50 changes: 43 additions & 7 deletions src/engine/workers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,12 @@ export interface SelectionChangedEvent {

export interface AlertEvent {
type: 'alert';
alertType: 'under_attack' | 'unit_ready' | 'research_complete' | 'resources_low' | 'base_destroyed';
alertType:
| 'under_attack'
| 'unit_ready'
| 'research_complete'
| 'resources_low'
| 'base_destroyed';
position?: { x: number; y: number };
playerId: string;
details?: string;
Expand Down Expand Up @@ -418,7 +423,13 @@ export type MainToWorkerMessage =
| { type: 'spawnEntities'; mapData: SpawnMapData }
| { type: 'setPerformanceCollection'; enabled: boolean }
| { type: 'spawnUnit'; unitType: string; x: number; y: number; playerId: string }
| { type: 'destroyEntity'; entityId: number };
| { type: 'destroyEntity'; entityId: number }
| {
type: 'registerAI';
playerId: string;
faction: string;
difficulty?: 'easy' | 'medium' | 'hard' | 'insane';
};

/**
* Player slot info for spawning
Expand Down Expand Up @@ -503,7 +514,21 @@ export type { GameCommand } from '../core/GameCommand';
*/
export const TRANSFORM_FLOATS_PER_ENTITY = 11;

export function packTransforms(entities: Array<{ x: number; y: number; z: number; rotation: number; scaleX: number; scaleY: number; scaleZ: number; prevX: number; prevY: number; prevZ: number; prevRotation: number }>): Float32Array {
export function packTransforms(
entities: Array<{
x: number;
y: number;
z: number;
rotation: number;
scaleX: number;
scaleY: number;
scaleZ: number;
prevX: number;
prevY: number;
prevZ: number;
prevRotation: number;
}>
): Float32Array {
const buffer = new Float32Array(entities.length * TRANSFORM_FLOATS_PER_ENTITY);
let offset = 0;
for (const e of entities) {
Expand All @@ -522,10 +547,21 @@ export function packTransforms(entities: Array<{ x: number; y: number; z: number
return buffer;
}

export function unpackTransform(buffer: Float32Array, index: number): {
x: number; y: number; z: number; rotation: number;
scaleX: number; scaleY: number; scaleZ: number;
prevX: number; prevY: number; prevZ: number; prevRotation: number;
export function unpackTransform(
buffer: Float32Array,
index: number
): {
x: number;
y: number;
z: number;
rotation: number;
scaleX: number;
scaleY: number;
scaleZ: number;
prevX: number;
prevY: number;
prevZ: number;
prevRotation: number;
} {
const offset = index * TRANSFORM_FLOATS_PER_ENTITY;
return {
Expand Down
Loading