120 lines
4.8 KiB
C#
120 lines
4.8 KiB
C#
using SpaceGame.Simulation.Api.Data;
|
|
using SpaceGame.Simulation.Api.Contracts;
|
|
|
|
namespace SpaceGame.Simulation.Api.Simulation;
|
|
|
|
public sealed partial class SimulationEngine
|
|
{
|
|
private const int StrategicControlTargetSystems = 5;
|
|
|
|
private void UpdateStations(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
{
|
|
var factionPopulation = new Dictionary<string, float>(StringComparer.Ordinal);
|
|
foreach (var station in world.Stations)
|
|
{
|
|
UpdateStationPopulation(station, deltaSeconds, events);
|
|
ReviewStationMarketOrders(world, station);
|
|
RunStationProduction(world, station, deltaSeconds, events);
|
|
factionPopulation[station.FactionId] = GetInventoryAmount(factionPopulation, station.FactionId) + station.Population;
|
|
}
|
|
|
|
foreach (var faction in world.Factions)
|
|
{
|
|
faction.PopulationTotal = GetInventoryAmount(factionPopulation, faction.Id);
|
|
}
|
|
}
|
|
|
|
private void UpdateStationPopulation(StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
{
|
|
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
|
|
|
var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
|
|
var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
|
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
|
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
|
|
|
if (waterSatisfied)
|
|
{
|
|
if (habitatModules > 0 && station.Population < station.PopulationCapacity)
|
|
{
|
|
station.Population = MathF.Min(station.PopulationCapacity, station.Population + (PopulationGrowthPerSecond * deltaSeconds));
|
|
}
|
|
}
|
|
else if (station.Population > 0f)
|
|
{
|
|
var previous = station.Population;
|
|
station.Population = MathF.Max(0f, station.Population - (PopulationAttritionPerSecond * deltaSeconds));
|
|
if (MathF.Floor(previous) > MathF.Floor(station.Population))
|
|
{
|
|
events.Add(new SimulationEventRecord("station", station.Id, "population-loss", $"{station.Label} lost population due to support shortages.", DateTimeOffset.UtcNow));
|
|
}
|
|
}
|
|
|
|
station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
|
|
}
|
|
|
|
private float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
|
{
|
|
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
var spawnPosition = new Vector3(station.Position.X + GetStationRadius(world, station) + 32f, station.Position.Y, station.Position.Z);
|
|
var ship = new ShipRuntime
|
|
{
|
|
Id = $"ship-{world.Ships.Count + 1}",
|
|
SystemId = station.SystemId,
|
|
Definition = definition,
|
|
FactionId = station.FactionId,
|
|
Position = spawnPosition,
|
|
TargetPosition = spawnPosition,
|
|
SpatialState = CreateSpawnedShipSpatialState(station, spawnPosition),
|
|
DefaultBehavior = CreateSpawnedShipBehavior(definition, station),
|
|
ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold),
|
|
Health = definition.MaxHealth,
|
|
};
|
|
|
|
world.Ships.Add(ship);
|
|
if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
|
|
{
|
|
faction.ShipsBuilt += 1;
|
|
}
|
|
|
|
events.Add(new SimulationEventRecord("station", station.Id, "ship-built", $"{station.Label} launched {definition.Label}.", DateTimeOffset.UtcNow));
|
|
return 1f;
|
|
}
|
|
|
|
private static ShipSpatialStateRuntime CreateSpawnedShipSpatialState(StationRuntime station, Vector3 position) => new()
|
|
{
|
|
CurrentSystemId = station.SystemId,
|
|
SpaceLayer = SpaceLayerKinds.LocalSpace,
|
|
CurrentCelestialId = station.CelestialId,
|
|
LocalPosition = position,
|
|
SystemPosition = position,
|
|
MovementRegime = MovementRegimeKinds.LocalFlight,
|
|
};
|
|
|
|
private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station)
|
|
{
|
|
if (!string.Equals(definition.Kind, "military", StringComparison.Ordinal))
|
|
{
|
|
return new DefaultBehaviorRuntime { Kind = "idle" };
|
|
}
|
|
|
|
var patrolRadius = station.Radius + 90f;
|
|
return new DefaultBehaviorRuntime
|
|
{
|
|
Kind = "patrol",
|
|
PatrolPoints =
|
|
[
|
|
new Vector3(station.Position.X + patrolRadius, station.Position.Y, station.Position.Z),
|
|
new Vector3(station.Position.X, station.Position.Y, station.Position.Z + patrolRadius),
|
|
new Vector3(station.Position.X - patrolRadius, station.Position.Y, station.Position.Z),
|
|
new Vector3(station.Position.X, station.Position.Y, station.Position.Z - patrolRadius),
|
|
],
|
|
};
|
|
}
|
|
}
|