diff --git a/apps/backend/Data/WorldDefinitions.cs b/apps/backend/Data/WorldDefinitions.cs index aba7ab6..1d1d6f0 100644 --- a/apps/backend/Data/WorldDefinitions.cs +++ b/apps/backend/Data/WorldDefinitions.cs @@ -98,6 +98,7 @@ public sealed class ModuleDefinition public string Description { get; set; } = string.Empty; public required string Type { get; set; } public string? Product { get; set; } + public string ProductionMode { get; set; } = "passive"; public float Radius { get; set; } = 12f; public float Hull { get; set; } = 100f; public float WorkforceNeeded { get; set; } diff --git a/apps/backend/Simulation/SimulationEngine.Replication.cs b/apps/backend/Simulation/SimulationEngine.Replication.cs index c37f6ba..c8b68f0 100644 --- a/apps/backend/Simulation/SimulationEngine.Replication.cs +++ b/apps/backend/Simulation/SimulationEngine.Replication.cs @@ -565,7 +565,7 @@ public sealed partial class SimulationEngine station.MarketOrderIds.OrderBy(orderId => orderId, StringComparer.Ordinal).ToList()); private static IReadOnlyList ToStationActionProgressSnapshots(SimulationWorld world, StationRuntime station) => - GetStationProductionLanes(station) + GetStationProductionLanes(world, station) .Select(laneKey => { var recipe = SelectProductionRecipe(world, station, laneKey); diff --git a/apps/backend/Simulation/SimulationEngine.StationSystems.cs b/apps/backend/Simulation/SimulationEngine.StationSystems.cs index 359163a..23cf3c3 100644 --- a/apps/backend/Simulation/SimulationEngine.StationSystems.cs +++ b/apps/backend/Simulation/SimulationEngine.StationSystems.cs @@ -86,7 +86,7 @@ public sealed partial class SimulationEngine private void RunStationProduction(SimulationWorld world, StationRuntime station, float deltaSeconds, ICollection events) { var faction = world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId); - foreach (var laneKey in GetStationProductionLanes(station)) + foreach (var laneKey in GetStationProductionLanes(world, station)) { var recipe = SelectProductionRecipe(world, station, laneKey); if (recipe is null) @@ -95,7 +95,7 @@ public sealed partial class SimulationEngine continue; } - var throughput = GetStationProductionThroughput(station, recipe); + var throughput = GetStationProductionThroughput(world, station, recipe); var produced = 0f; station.ProductionLaneTimers[laneKey] = GetStationProductionTimer(station, laneKey) + (deltaSeconds * station.WorkforceEffectiveRatio * throughput); @@ -132,26 +132,21 @@ public sealed partial class SimulationEngine } } - private static IEnumerable GetStationProductionLanes(StationRuntime station) + private static IEnumerable GetStationProductionLanes(SimulationWorld world, StationRuntime station) { - if (CountModules(station.InstalledModules, "refinery-stack") > 0) + foreach (var moduleId in station.InstalledModules.Distinct(StringComparer.Ordinal)) { - yield return "refinery"; - } + if (!world.ModuleDefinitions.TryGetValue(moduleId, out var def) || string.IsNullOrEmpty(def.ProductionMode)) + { + continue; + } - if (CountModules(station.InstalledModules, "fabricator-array") > 0) - { - yield return "fabrication"; - } + if (string.Equals(def.ProductionMode, "commanded", StringComparison.Ordinal) && station.CommanderId is null) + { + continue; + } - if (CountModules(station.InstalledModules, "component-factory") > 0) - { - yield return "components"; - } - - if (CountModules(station.InstalledModules, "ship-factory") > 0) - { - yield return "shipyard"; + yield return moduleId; } } @@ -160,34 +155,13 @@ public sealed partial class SimulationEngine private static RecipeDefinition? SelectProductionRecipe(SimulationWorld world, StationRuntime station, string laneKey) => world.Recipes.Values - .Where(recipe => RecipeAppliesToStation(station, recipe) && string.Equals(GetStationProductionLaneKey(recipe), laneKey, StringComparison.Ordinal)) + .Where(recipe => RecipeAppliesToStation(station, recipe) && string.Equals(GetStationProductionLaneKey(world, recipe), laneKey, StringComparison.Ordinal)) .OrderByDescending(recipe => GetStationRecipePriority(world, station, recipe)) .FirstOrDefault(recipe => CanRunRecipe(world, station, recipe)); - private static string? GetStationProductionLaneKey(RecipeDefinition recipe) - { - if (recipe.RequiredModules.Contains("refinery-stack", StringComparer.Ordinal)) - { - return "refinery"; - } - - if (recipe.RequiredModules.Contains("fabricator-array", StringComparer.Ordinal)) - { - return "fabrication"; - } - - if (recipe.RequiredModules.Contains("component-factory", StringComparer.Ordinal)) - { - return "components"; - } - - if (recipe.RequiredModules.Contains("ship-factory", StringComparer.Ordinal)) - { - return "shipyard"; - } - - return null; - } + private static string? GetStationProductionLaneKey(SimulationWorld world, RecipeDefinition recipe) => + recipe.RequiredModules.FirstOrDefault(moduleId => + world.ModuleDefinitions.TryGetValue(moduleId, out var def) && !string.IsNullOrEmpty(def.ProductionMode)); private static float GetStationRecipePriority(SimulationWorld world, StationRuntime station, RecipeDefinition recipe) { @@ -467,29 +441,15 @@ public sealed partial class SimulationEngine }; } - private static float GetStationProductionThroughput(StationRuntime station, RecipeDefinition recipe) + private static float GetStationProductionThroughput(SimulationWorld world, StationRuntime station, RecipeDefinition recipe) { - if (recipe.RequiredModules.Contains("refinery-stack", StringComparer.Ordinal)) + var laneModuleId = GetStationProductionLaneKey(world, recipe); + if (laneModuleId is null) { - return Math.Max(1, CountModules(station.InstalledModules, "refinery-stack")); + return 1f; } - if (recipe.RequiredModules.Contains("fabricator-array", StringComparer.Ordinal)) - { - return Math.Max(1, CountModules(station.InstalledModules, "fabricator-array")); - } - - if (recipe.RequiredModules.Contains("component-factory", StringComparer.Ordinal)) - { - return Math.Max(1, CountModules(station.InstalledModules, "component-factory")); - } - - if (recipe.RequiredModules.Contains("ship-factory", StringComparer.Ordinal)) - { - return Math.Max(1, CountModules(station.InstalledModules, "ship-factory")); - } - - return 1f; + return Math.Max(1, CountModules(station.InstalledModules, laneModuleId)); } private sealed record DesiredMarketOrder(string Kind, string ItemId, float Amount, float Valuation, float? ReserveThreshold); diff --git a/shared/data/modules.json b/shared/data/modules.json index b7864a2..9f06219 100644 --- a/shared/data/modules.json +++ b/shared/data/modules.json @@ -63,6 +63,7 @@ "name": "Refinery Stack", "description": "Heavy refining line for ore to refined metals.", "type": "Production", + "productionMode": "passive", "product": "refined-metals", "hull": 180, "workforceNeeded": 18, @@ -81,6 +82,7 @@ "name": "Solar Array", "description": "Orbital solar generation and utility frame.", "type": "Production", + "productionMode": "passive", "hull": 110, "workforceNeeded": 6, "construction": { @@ -98,6 +100,7 @@ "name": "Fabricator Array", "description": "General fabrication line for industrial goods and prefab kits.", "type": "Build Module", + "productionMode": "commanded", "hull": 200, "workforceNeeded": 20, "construction": { @@ -115,6 +118,7 @@ "name": "Component Factory", "description": "Assembly line for ship-grade modules and integrated components.", "type": "Build Module", + "productionMode": "commanded", "hull": 220, "workforceNeeded": 24, "construction": { @@ -136,6 +140,7 @@ "name": "Ship Factory", "description": "Slip-line and integration yard for final ship assembly.", "type": "Build Module", + "productionMode": "commanded", "hull": 260, "workforceNeeded": 28, "construction": {