diff --git a/src/data/ai/factions/dominion.ts b/src/data/ai/factions/dominion.ts index eb5067ce..6250078c 100644 --- a/src/data/ai/factions/dominion.ts +++ b/src/data/ai/factions/dominion.ts @@ -75,28 +75,28 @@ const DOMINION_MACRO_RULES: MacroRule[] = [ priority: 90, conditions: [ // workerSaturation = workers / (bases * 16), so < 1.0 means under-saturated - { type: 'workerSaturation', operator: '<', value: 1.0 }, + { type: 'workerSaturation', operator: '<', value: 1.2 }, // Allow slight over-saturation for smoother production { type: 'minerals', operator: '>=', value: 50 }, - { type: 'supplyRatio', operator: '<', value: 0.95 }, + { type: 'supplyRatio', operator: '<', value: 0.9 }, // Stop earlier to leave room for army ], action: { type: 'train', targetId: 'fabricator' }, - cooldownTicks: 30, + cooldownTicks: 25, // Slightly faster worker production }, // === Production Building Scaling === - // First infantry bay + // First infantry bay - LOW threshold to ensure it gets built { id: 'infantry_bay_first', name: 'First Infantry Bay', description: 'Build first production building', priority: 85, conditions: [ - { type: 'workers', operator: '>=', value: 10 }, + { type: 'workers', operator: '>=', value: 6 }, // Lowered from 10 - build early { type: 'buildingCount', operator: '==', value: 0, targetId: 'infantry_bay' }, { type: 'minerals', operator: '>=', value: 150 }, ], action: { type: 'build', targetId: 'infantry_bay' }, - cooldownTicks: 100, + cooldownTicks: 60, // Faster retry }, // Second infantry bay - ALL DIFFICULTIES need to scale production @@ -143,12 +143,12 @@ const DOMINION_MACRO_RULES: MacroRule[] = [ description: 'Get gas for tech units', priority: 80, conditions: [ - { type: 'workers', operator: '>=', value: 10 }, // Lowered from 12 + { type: 'workers', operator: '>=', value: 8 }, // Lowered from 10 - get gas earlier { type: 'buildingCount', operator: '==', value: 0, targetId: 'extractor' }, { type: 'minerals', operator: '>=', value: 75 }, ], action: { type: 'build', targetId: 'extractor' }, - cooldownTicks: 100, + cooldownTicks: 60, // Faster retry }, // Second extractor at main base - most bases have 2 vespene geysers @@ -436,8 +436,8 @@ const DOMINION_MACRO_RULES: MacroRule[] = [ priority: 40, conditions: [ { type: 'buildingCount', operator: '>=', value: 1, targetId: 'infantry_bay' }, - { type: 'minerals', operator: '>=', value: 75 }, - { type: 'supplyRatio', operator: '<', value: 0.9 }, + { type: 'minerals', operator: '>=', value: 50 }, // Lowered - always produce + { type: 'supplyRatio', operator: '<', value: 0.95 }, // More room to produce ], action: { type: 'train', @@ -447,7 +447,7 @@ const DOMINION_MACRO_RULES: MacroRule[] = [ { id: 'vanguard', weight: 3 }, ], }, - cooldownTicks: 15, + cooldownTicks: 10, // Faster production }, { @@ -464,6 +464,28 @@ const DOMINION_MACRO_RULES: MacroRule[] = [ cooldownTicks: 18, }, + // === CATCHALL: Spend excess minerals === + // If we have lots of minerals and infantry_bay, train units + { + id: 'train_excess_minerals', + name: 'Spend Excess Minerals', + description: 'Dont float minerals - produce something!', + priority: 25, // Low priority but catches stockpiling + conditions: [ + { type: 'buildingCount', operator: '>=', value: 1, targetId: 'infantry_bay' }, + { type: 'minerals', operator: '>=', value: 200 }, // Floating too much + ], + action: { + type: 'train', + options: [ + { id: 'trooper', weight: 10 }, + { id: 'breacher', weight: 5 }, + { id: 'vanguard', weight: 3 }, + ], + }, + cooldownTicks: 8, // Very fast - spend those minerals! + }, + // === Expansion === // Expand when resources are depleting (simulation-based economy trigger) { diff --git a/src/engine/systems/ai/AIBuildOrderExecutor.ts b/src/engine/systems/ai/AIBuildOrderExecutor.ts index 9f08317d..e469ea21 100644 --- a/src/engine/systems/ai/AIBuildOrderExecutor.ts +++ b/src/engine/systems/ai/AIBuildOrderExecutor.ts @@ -79,6 +79,28 @@ export class AIBuildOrderExecutor { return; } + // For building steps, check if requirements are met before attempting + // Don't count "waiting for requirements" as a failure + if (step.type === 'building') { + const buildingDef = BUILDING_DEFINITIONS[step.id]; + if (buildingDef?.requirements && buildingDef.requirements.length > 0) { + for (const reqBuildingId of buildingDef.requirements) { + if (!this.hasCompleteBuildingOfType(ai, reqBuildingId)) { + // Requirements not met - just wait, don't count as failure + return; + } + } + } + } + + // For unit steps, check if we have a production building + if (step.type === 'unit') { + if (!this.hasProductionBuildingForUnit(ai, step.id)) { + // No production building yet - wait, don't count as failure + return; + } + } + // Execute the step let success = this.executeBuildOrderStep(ai, step); @@ -97,6 +119,29 @@ export class AIBuildOrderExecutor { } } + /** + * Check if we have a completed production building that can train the specified unit. + */ + private hasProductionBuildingForUnit(ai: AIPlayer, unitType: string): boolean { + const buildings = this.coordinator.getCachedBuildings(); + + for (const entity of buildings) { + const selectable = entity.get('Selectable'); + const building = entity.get('Building'); + const health = entity.get('Health'); + + if (!selectable || !building || !health) continue; + if (selectable.playerId !== ai.playerId) continue; + if (health.isDead()) continue; + if (!building.isComplete()) continue; + if (!building.canProduce.includes(unitType)) continue; + + return true; + } + + return false; + } + private executeBuildOrderStep(ai: AIPlayer, step: BuildOrderStep): boolean { // BuildOrderStep uses 'id' for the target, and 'type' values: 'unit', 'building', 'research', 'ability' switch (step.type) { diff --git a/src/engine/systems/ai/AICoordinator.ts b/src/engine/systems/ai/AICoordinator.ts index e4f7c46c..f6dbaa46 100644 --- a/src/engine/systems/ai/AICoordinator.ts +++ b/src/engine/systems/ai/AICoordinator.ts @@ -666,10 +666,12 @@ export class AICoordinator extends System { // Follow build order if still in progress if (ai.buildOrderIndex < ai.buildOrder.length) { this.buildOrderExecutor.executeBuildOrder(ai); - return; + // IMPORTANT: Don't return early! Continue to macro rules below. + // This allows the AI to keep producing while waiting for build order steps. } - // Post-build order: continuous macro + // Always run macro rules - they complement the build order + // Build order handles early sequencing, macro rules handle continuous production this.buildOrderExecutor.doMacro(ai); }