using SpaceGame.Simulation.Api.Contracts; using SpaceGame.Simulation.Api.Data; namespace SpaceGame.Simulation.Api.Simulation; public sealed partial class SimulationEngine { private static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) => capabilities.All(cap => definition.Capabilities.Contains(cap, StringComparer.Ordinal)); private static int CountStationModules(StationRuntime station, string moduleId) => station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal)); private static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId) { if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition)) { return; } station.Modules.Add(new StationModuleRuntime { Id = $"{station.Id}-module-{station.Modules.Count + 1}", ModuleId = moduleId, Health = definition.Hull, MaxHealth = definition.Hull, }); station.Radius = GetStationRadius(world, station); } private static float GetStationRadius(SimulationWorld world, StationRuntime station) { var totalArea = station.Modules .Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f) .Sum(); return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f))); } private static float GetStationStorageCapacity(StationRuntime station, string storageClass) { var baseCapacity = storageClass switch { "manufactured" => 400f, _ => 0f, }; var bulkBays = CountStationModules(station, "module_arg_stor_solid_m_01"); var liquidTanks = CountStationModules(station, "module_arg_stor_liquid_m_01"); var containerBays = CountStationModules(station, "module_arg_stor_container_m_01"); var moduleCapacity = storageClass switch { "solid" => bulkBays * 1000f, "liquid" => liquidTanks * 500f, "container" => containerBays * 800f, "manufactured" => containerBays * 200f, _ => 0f, }; return baseCapacity + moduleCapacity; } private static int CountModules(IEnumerable modules, string moduleId) => modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal)); private static float GetInventoryAmount(IReadOnlyDictionary inventory, string itemId) => inventory.TryGetValue(itemId, out var amount) ? amount : 0f; private static void AddInventory(IDictionary inventory, string itemId, float amount) { if (amount <= 0f) { return; } inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary)inventory, itemId) + amount; } private static float RemoveInventory(IDictionary inventory, string itemId, float amount) { var current = GetInventoryAmount((IReadOnlyDictionary)inventory, itemId); var removed = MathF.Min(current, amount); var remaining = current - removed; if (remaining <= 0.001f) { inventory.Remove(itemId); } else { inventory[itemId] = remaining; } return removed; } private static bool HasStationModules(StationRuntime station, params string[] modules) => modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal))); private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) => HasShipCapabilities(ship.Definition, "mining") && world.ItemDefinitions.TryGetValue(node.ItemId, out var item) && string.Equals(item.CargoKind, ship.Definition.CargoKind, StringComparison.Ordinal); private static bool CanBuildClaimBeacon(ShipRuntime ship) => string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal); private static float ComputeWorkforceRatio(float population, float workforceRequired) { if (workforceRequired <= 0.01f) { return 1f; } var staffedRatio = MathF.Min(1f, population / workforceRequired); return 0.1f + (0.9f * staffedRatio); } private static string? GetStorageRequirement(string storageClass) => storageClass switch { "solid" => "module_arg_stor_solid_m_01", "liquid" => "module_arg_stor_liquid_m_01", _ => null, }; private static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount) { if (amount <= 0f || !world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition)) { return 0f; } var storageClass = itemDefinition.CargoKind; var requiredModule = GetStorageRequirement(storageClass); if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal)) { return 0f; } var capacity = GetStationStorageCapacity(station, storageClass); if (capacity <= 0.01f) { return 0f; } var used = station.Inventory .Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageClass) .Sum(entry => entry.Value); var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used)); if (accepted <= 0.01f) { return 0f; } AddInventory(station.Inventory, itemId, accepted); return accepted; } private static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) => recipe.Inputs.All(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f >= input.Amount); private static ConstructionSiteRuntime? GetConstructionSiteForStation(SimulationWorld world, string stationId) => world.ConstructionSites.FirstOrDefault(site => string.Equals(site.StationId, stationId, StringComparison.Ordinal) && site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed); private static float GetConstructionDeliveredAmount(SimulationWorld world, ConstructionSiteRuntime site, string itemId) { if (site.StationId is not null && world.Stations.FirstOrDefault(candidate => candidate.Id == site.StationId) is { } station) { return GetInventoryAmount(station.Inventory, itemId); } return GetInventoryAmount(site.DeliveredItems, itemId); } private static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) => site.RequiredItems.All(entry => GetConstructionDeliveredAmount(world, site, entry.Key) + 0.001f >= entry.Value); }