Files
space-game/apps/backend/Shared/Runtime/SimulationRuntimeSupport.cs

301 lines
11 KiB
C#

namespace SpaceGame.Api.Shared.Runtime;
internal static class SimulationRuntimeSupport
{
internal static bool CanWarp(ShipDefinition definition) =>
definition.Engines.Count > 0;
internal static bool CanFtl(ShipDefinition definition) =>
definition.Engines.Count > 0;
internal static bool IsMiningShip(ShipDefinition definition) =>
definition.Type is ShipType.Miner or ShipType.LargeMiner;
internal static bool IsTransportShip(ShipDefinition definition) =>
definition.Type is ShipType.Freighter or ShipType.Transporter or ShipType.Courier or ShipType.Resupplier;
internal static bool IsConstructionShip(ShipDefinition definition) =>
definition.Type == ShipType.Builder;
internal static bool IsMilitaryShip(ShipDefinition definition) =>
definition.Type is ShipType.Fighter
or ShipType.HeavyFighter
or ShipType.Destroyer
or ShipType.Bomber
or ShipType.Frigate
or ShipType.Interceptor
or ShipType.Corvette
or ShipType.Battleship
or ShipType.Gunboat;
internal static string? GetShipCategory(ShipDefinition definition)
{
if (IsMilitaryShip(definition))
{
return "military";
}
if (IsConstructionShip(definition))
{
return "construction";
}
if (IsTransportShip(definition))
{
return "transport";
}
if (IsMiningShip(definition))
{
return "mining";
}
return null;
}
internal static int CountStationModules(StationRuntime station, ModuleType moduleType) =>
station.Modules.Count(module => module.ModuleType == moduleType);
internal static float GetStationSupportedPopulation(
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
StationRuntime station) =>
40f + station.Modules
.Select(module => moduleDefinitions.TryGetValue(module.ModuleId, out var definition) && definition is HabitationModuleDefinition habitation
? habitation.SupportedPopulation
: 0f)
.Sum();
internal static float GetStationRequiredWorkforce(
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
StationRuntime station) =>
MathF.Max(12f, station.Modules
.Select(module => moduleDefinitions.TryGetValue(module.ModuleId, out var definition)
&& definition is ProductionLaneModuleDefinition productionLane
? productionLane.RequiredWorkforce
: 0f)
.Sum());
internal static float GetStationStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
{
SyncStorageModuleLevels(world, station, storageKind);
return GetStorageModules(world, station, storageKind)
.Sum(entry => entry.Definition.StorageCapacity);
}
internal static bool HasStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
{
SyncStorageModuleLevels(world, station, storageKind);
return GetStorageModules(world, station, storageKind).Any();
}
private static IEnumerable<(StorageStationModuleRuntime Module, StorageModuleDefinition Definition)> GetStorageModules(
SimulationWorld world,
StationRuntime station,
StorageKind storageKind) =>
station.Modules
.OfType<StorageStationModuleRuntime>()
.Where(module => module.StorageKind == storageKind)
.Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) && definition is StorageModuleDefinition storageDefinition
? (Module: module, Definition: storageDefinition)
: ((StorageStationModuleRuntime Module, StorageModuleDefinition Definition)?)null)
.Where(entry => entry is not null && entry.Value.Definition.StorageKind == storageKind)
.Select(entry => entry!.Value);
private static void SyncStorageModuleLevels(SimulationWorld world, StationRuntime station, StorageKind storageKind)
{
var storageModules = GetStorageModules(world, station, storageKind)
.OrderBy(entry => entry.Module.Id, StringComparer.Ordinal)
.ToList();
if (storageModules.Count == 0)
{
return;
}
var remaining = station.Inventory
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageKind)
.Sum(entry => entry.Value);
foreach (var (module, definition) in storageModules)
{
module.CurrentLevel = MathF.Min(remaining, definition.StorageCapacity);
remaining = MathF.Max(0f, remaining - definition.StorageCapacity);
}
}
internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
{
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
{
return;
}
station.Modules.Add(StationModuleRuntime.Create($"{station.Id}-module-{station.Modules.Count + 1}", definition));
station.Radius = GetStationRadius(world, station);
}
internal 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)));
}
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
internal static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
{
if (amount <= 0f)
{
return;
}
inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId) + amount;
}
internal static float RemoveInventory(IDictionary<string, float> inventory, string itemId, float amount)
{
var current = GetInventoryAmount((IReadOnlyDictionary<string, float>)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;
}
internal static bool HasStationModules(StationRuntime station, params string[] modules) =>
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
internal static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
IsMiningShip(ship.Definition)
&& world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
&& item.CargoKind is not null
&& ship.Definition.SupportsCargoKind(item.CargoKind.Value);
internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
IsMilitaryShip(ship.Definition);
internal 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);
}
internal static string? GetStorageRequirement(
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
StorageKind? storageKind)
{
if (storageKind is not { } requiredStorageKind)
{
return null;
}
return moduleDefinitions.Values
.OfType<StorageModuleDefinition>()
.Where(definition => definition.StorageKind == requiredStorageKind)
.OrderBy(definition => GetPreferredStorageModuleRank(definition.Id))
.ThenBy(definition => definition.Id, StringComparer.Ordinal)
.Select(definition => definition.Id)
.FirstOrDefault();
}
private static int GetPreferredStorageModuleRank(string moduleId)
{
if (moduleId.Contains("_m_", StringComparison.Ordinal))
{
return 0;
}
if (moduleId.Contains("_s_", StringComparison.Ordinal))
{
return 1;
}
if (moduleId.Contains("_l_", StringComparison.Ordinal))
{
return 2;
}
return 3;
}
internal static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
{
if (amount <= 0f || !world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
{
return 0f;
}
var storageKind = itemDefinition.CargoKind;
if (storageKind is null)
{
return 0f;
}
if (!HasStorageCapacity(world, station, storageKind.Value))
{
return 0f;
}
var capacity = GetStationStorageCapacity(world, station, storageKind.Value);
if (capacity <= 0.01f)
{
return 0f;
}
var used = station.Inventory
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageKind)
.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;
}
internal static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) =>
recipe.Inputs.All(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f >= input.Amount);
internal 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);
internal 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);
}
internal static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) =>
site.RequiredItems.All(entry => GetConstructionDeliveredAmount(world, site, entry.Key) + 0.001f >= entry.Value);
internal static float GetShipCargoAmount(ShipRuntime ship) =>
ship.Inventory.Values.Sum();
}