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
44 changes: 33 additions & 11 deletions src/data/ai/factions/dominion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand All @@ -447,7 +447,7 @@ const DOMINION_MACRO_RULES: MacroRule[] = [
{ id: 'vanguard', weight: 3 },
],
},
cooldownTicks: 15,
cooldownTicks: 10, // Faster production
},

{
Expand All @@ -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)
{
Expand Down
45 changes: 45 additions & 0 deletions src/engine/systems/ai/AIBuildOrderExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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>('Selectable');
const building = entity.get<Building>('Building');
const health = entity.get<Health>('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) {
Expand Down
6 changes: 4 additions & 2 deletions src/engine/systems/ai/AICoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down