Refactor simulation and viewer architecture
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user