238 lines
9.5 KiB
C#
238 lines
9.5 KiB
C#
using SpaceGame.Simulation.Api.Contracts;
|
|
using SpaceGame.Simulation.Api.Data;
|
|
|
|
namespace SpaceGame.Simulation.Api.Simulation;
|
|
|
|
public sealed partial class SimulationEngine
|
|
{
|
|
private static bool HasShipModules(ShipDefinition definition, params string[] modules) =>
|
|
modules.All(moduleId => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
|
|
|
private static bool CanTransportWorkers(ShipRuntime ship) =>
|
|
CountModules(ship.Definition.Modules, "habitat-ring") > 0;
|
|
|
|
private static float GetWorkerTransportCapacity(ShipRuntime ship) =>
|
|
CountModules(ship.Definition.Modules, "habitat-ring") * 120f;
|
|
|
|
private static void UpdateStationPower(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
{
|
|
foreach (var station in world.Stations)
|
|
{
|
|
var previousEnergy = station.EnergyStored;
|
|
GenerateStationEnergy(station, deltaSeconds);
|
|
|
|
if (previousEnergy > 0.01f && station.EnergyStored <= 0.01f && GetInventoryAmount(station.Inventory, "fuel") <= 0.01f)
|
|
{
|
|
events.Add(new SimulationEventRecord("station", station.Id, "power-lost", $"{station.Definition.Label} ran out of fuel and power", DateTimeOffset.UtcNow));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void UpdateShipPower(ShipRuntime ship, SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
{
|
|
var previousEnergy = ship.EnergyStored;
|
|
GenerateShipEnergy(ship, world, deltaSeconds);
|
|
|
|
if (previousEnergy > 0.01f && ship.EnergyStored <= 0.01f && GetInventoryAmount(ship.Inventory, "fuel") <= 0.01f)
|
|
{
|
|
events.Add(new SimulationEventRecord("ship", ship.Id, "power-lost", $"{ship.Definition.Label} ran out of fuel and power", DateTimeOffset.UtcNow));
|
|
}
|
|
}
|
|
|
|
private static void GenerateStationEnergy(StationRuntime station, float deltaSeconds)
|
|
{
|
|
var powerCores = CountModules(station.InstalledModules, "power-core");
|
|
var tanks = CountModules(station.InstalledModules, "liquid-tank");
|
|
if (powerCores <= 0 || tanks <= 0)
|
|
{
|
|
station.EnergyStored = 0f;
|
|
station.Inventory.Remove("fuel");
|
|
return;
|
|
}
|
|
|
|
var energyCapacity = powerCores * StationEnergyPerPowerCore;
|
|
var fuelStored = GetInventoryAmount(station.Inventory, "fuel");
|
|
var desiredEnergy = MathF.Max(0f, energyCapacity - station.EnergyStored);
|
|
if (desiredEnergy <= 0.01f || fuelStored <= 0.01f)
|
|
{
|
|
station.EnergyStored = MathF.Min(station.EnergyStored, energyCapacity);
|
|
station.Inventory["fuel"] = MathF.Min(fuelStored, tanks * StationFuelPerTank);
|
|
return;
|
|
}
|
|
|
|
var generated = MathF.Min(desiredEnergy, powerCores * 24f * deltaSeconds);
|
|
var requiredFuel = generated / StationFuelToEnergyRatio;
|
|
var consumedFuel = MathF.Min(requiredFuel, fuelStored);
|
|
var actualGenerated = consumedFuel * StationFuelToEnergyRatio;
|
|
|
|
RemoveInventory(station.Inventory, "fuel", consumedFuel);
|
|
station.EnergyStored = MathF.Min(energyCapacity, station.EnergyStored + actualGenerated);
|
|
}
|
|
|
|
private static void GenerateShipEnergy(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
|
{
|
|
var reactors = CountModules(ship.Definition.Modules, "reactor-core");
|
|
var capacitors = CountModules(ship.Definition.Modules, "capacitor-bank");
|
|
if (reactors <= 0 || capacitors <= 0)
|
|
{
|
|
ship.EnergyStored = 0f;
|
|
ship.Inventory.Remove("fuel");
|
|
return;
|
|
}
|
|
|
|
var energyCapacity = capacitors * CapacitorEnergyPerModule;
|
|
var fuelCapacity = reactors * ShipFuelPerReactor;
|
|
var fuelStored = GetInventoryAmount(ship.Inventory, "fuel");
|
|
var desiredEnergy = MathF.Max(0f, energyCapacity - ship.EnergyStored);
|
|
if (desiredEnergy <= 0.01f || fuelStored <= 0.01f)
|
|
{
|
|
ship.EnergyStored = MathF.Min(ship.EnergyStored, energyCapacity);
|
|
ship.Inventory["fuel"] = MathF.Min(fuelStored, fuelCapacity);
|
|
return;
|
|
}
|
|
|
|
var generated = MathF.Min(desiredEnergy, world.Balance.Energy.ShipRechargeRate * reactors * deltaSeconds);
|
|
var requiredFuel = generated / ShipFuelToEnergyRatio;
|
|
var consumedFuel = MathF.Min(requiredFuel, fuelStored);
|
|
var actualGenerated = consumedFuel * ShipFuelToEnergyRatio;
|
|
|
|
RemoveInventory(ship.Inventory, "fuel", consumedFuel);
|
|
ship.EnergyStored = MathF.Min(energyCapacity, ship.EnergyStored + actualGenerated);
|
|
}
|
|
|
|
private static bool TryConsumeShipEnergy(ShipRuntime ship, float amount)
|
|
{
|
|
if (ship.EnergyStored + 0.0001f < amount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ship.EnergyStored = MathF.Max(0f, ship.EnergyStored - amount);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryConsumeStationEnergy(StationRuntime station, float amount)
|
|
{
|
|
if (station.EnergyStored + 0.0001f < amount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
station.EnergyStored = MathF.Max(0f, station.EnergyStored - amount);
|
|
return true;
|
|
}
|
|
|
|
private static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
|
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
|
|
|
private static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
|
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
|
|
|
private static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
|
{
|
|
if (amount <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId) + amount;
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
|
|
private static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
|
modules.All(moduleId => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
|
|
|
|
private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node) =>
|
|
node.ItemId switch
|
|
{
|
|
"ore" => HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", "mining-turret"),
|
|
"gas" => HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", "gas-extractor"),
|
|
_ => false,
|
|
};
|
|
|
|
private static float GetShipFuelCapacity(ShipRuntime ship) =>
|
|
CountModules(ship.Definition.Modules, "reactor-core") * ShipFuelPerReactor;
|
|
|
|
internal static bool NeedsRefuel(ShipRuntime ship) =>
|
|
GetInventoryAmount(ship.Inventory, "fuel") < (GetShipFuelCapacity(ship) * 0.7f);
|
|
|
|
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
|
|
{
|
|
"bulk-solid" => "bulk-bay",
|
|
"bulk-liquid" => "liquid-tank",
|
|
"bulk-gas" => "gas-tank",
|
|
_ => 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.Storage;
|
|
var requiredModule = GetStorageRequirement(storageClass);
|
|
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
if (!station.Definition.Storage.TryGetValue(storageClass, out var capacity))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
var used = station.Inventory
|
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.Storage == 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 bool IsConstructionSiteReady(ConstructionSiteRuntime site) =>
|
|
site.RequiredItems.All(entry => GetInventoryAmount(site.DeliveredItems, entry.Key) + 0.001f >= entry.Value);
|
|
}
|