Refactor world bootstrap and allow empty startup worlds

This commit is contained in:
2026-03-29 13:22:48 -04:00
parent 640e147ea8
commit 0bb72bee35
79 changed files with 173146 additions and 9235 deletions

View File

@@ -1,4 +1,3 @@
using Microsoft.Extensions.Options;
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;
using static SpaceGame.Api.Stations.Simulation.StationSimulationService;
@@ -6,7 +5,7 @@ using static SpaceGame.Api.Stations.Simulation.StationSimulationService;
namespace SpaceGame.Api.Ships.Simulation;
public sealed class ShipAiService(
IOptions<BalanceOptions> balance)
IBalanceService balance)
{
private const float WarpEngageDistanceKilometers = 250_000f;
private const float FrigateDps = 7f;
@@ -249,7 +248,7 @@ public sealed class ShipAiService(
plan.Steps.Add(CreateStep("step-flee-travel", "travel", "Travel to safe station",
[
CreateSubTask("sub-flee-travel", ShipTaskKinds.Travel, $"Travel to {safeStation.Label}", safeStation.SystemId, safeStation.Position, safeStation.Id, MathF.Max(world.Balance.ArrivalThreshold, safeStation.Radius + 12f), 0f)
CreateSubTask("sub-flee-travel", ShipTaskKinds.Travel, $"Travel to {safeStation.Label}", safeStation.SystemId, safeStation.Position, safeStation.Id, MathF.Max(balance.ArrivalThreshold, safeStation.Radius + 12f), 0f)
]));
plan.Steps.Add(CreateStep("step-flee-dock", "dock", "Dock at safe station",
[
@@ -353,7 +352,7 @@ public sealed class ShipAiService(
[
CreateStep("step-dock-travel", "travel", $"Travel to {station.Label}",
[
CreateSubTask("sub-dock-travel", ShipTaskKinds.Travel, $"Travel to {station.Label}", station.SystemId, station.Position, station.Id, MathF.Max(world.Balance.ArrivalThreshold, station.Radius + 12f), 0f)
CreateSubTask("sub-dock-travel", ShipTaskKinds.Travel, $"Travel to {station.Label}", station.SystemId, station.Position, station.Id, MathF.Max(balance.ArrivalThreshold, station.Radius + 12f), 0f)
]),
CreateStep("step-dock", "dock", $"Dock at {station.Label}",
[
@@ -1220,13 +1219,13 @@ public sealed class ShipAiService(
}
ship.State = ShipState.Mining;
if (!AdvanceTimedSubTask(subTask, deltaSeconds, world.Balance.MiningCycleSeconds))
if (!AdvanceTimedSubTask(subTask, deltaSeconds, balance.MiningCycleSeconds))
{
return SubTaskOutcome.Active;
}
var remainingCapacity = MathF.Max(0f, ship.Definition.CargoCapacity - cargoAmount);
var mined = MathF.Min(world.Balance.MiningRate * GetSkillFactor(ship.Skills.Mining), remainingCapacity);
var mined = MathF.Min(balance.MiningRate * GetSkillFactor(ship.Skills.Mining), remainingCapacity);
mined = MathF.Min(mined, node.OreRemaining);
if (mined <= 0.01f)
{
@@ -1282,7 +1281,7 @@ public sealed class ShipAiService(
}
ship.State = ShipState.Docking;
if (!AdvanceTimedSubTask(subTask, deltaSeconds, world.Balance.DockingDuration))
if (!AdvanceTimedSubTask(subTask, deltaSeconds, balance.DockingDuration))
{
return SubTaskOutcome.Active;
}
@@ -1311,16 +1310,16 @@ public sealed class ShipAiService(
return SubTaskOutcome.Completed;
}
var undockTarget = GetUndockTargetPosition(station, ship.AssignedDockingPadIndex, world.Balance.UndockDistance);
var undockTarget = GetUndockTargetPosition(station, ship.AssignedDockingPadIndex, balance.UndockDistance);
ship.TargetPosition = undockTarget;
ship.State = ShipState.Undocking;
if (!AdvanceTimedSubTask(subTask, deltaSeconds, world.Balance.UndockingDuration))
if (!AdvanceTimedSubTask(subTask, deltaSeconds, balance.UndockingDuration))
{
ship.Position = GetShipDockedPosition(ship, station);
return SubTaskOutcome.Active;
}
ship.Position = ship.Position.MoveToward(undockTarget, world.Balance.UndockDistance);
ship.Position = ship.Position.MoveToward(undockTarget, balance.UndockDistance);
if (ship.Position.DistanceTo(undockTarget) > MathF.Max(subTask.Threshold, 4f))
{
return SubTaskOutcome.Active;
@@ -1359,7 +1358,7 @@ public sealed class ShipAiService(
var desiredAmount = subTask.Amount > 0f ? subTask.Amount : ship.Definition.CargoCapacity;
var availableCapacity = MathF.Max(0f, ship.Definition.CargoCapacity - GetShipCargoAmount(ship));
var transferRate = world.Balance.TransferRate * GetSkillFactor(ship.Skills.Trade);
var transferRate = balance.TransferRate * GetSkillFactor(ship.Skills.Trade);
var moved = MathF.Min(transferRate * deltaSeconds, MathF.Min(availableCapacity, GetInventoryAmount(station.Inventory, itemId)));
if (moved > 0.01f)
{
@@ -1392,7 +1391,7 @@ public sealed class ShipAiService(
ship.TargetPosition = GetShipDockedPosition(ship, station);
ship.Position = ship.TargetPosition;
ship.State = ShipState.Transferring;
var transferRate = world.Balance.TransferRate * GetSkillFactor(Math.Max(ship.Skills.Trade, ship.Skills.Mining));
var transferRate = balance.TransferRate * GetSkillFactor(Math.Max(ship.Skills.Trade, ship.Skills.Mining));
if (subTask.ItemId is not null)
{
@@ -1451,7 +1450,7 @@ public sealed class ShipAiService(
return SubTaskOutcome.Failed;
}
var transferRate = world.Balance.TransferRate * GetSkillFactor(Math.Max(ship.Skills.Trade, ship.Skills.Navigation));
var transferRate = balance.TransferRate * GetSkillFactor(Math.Max(ship.Skills.Trade, ship.Skills.Navigation));
var desiredAmount = subTask.Amount > 0f ? subTask.Amount : GetInventoryAmount(ship.Inventory, subTask.ItemId);
var moved = MathF.Min(transferRate * deltaSeconds, MathF.Min(targetCapacity, GetInventoryAmount(ship.Inventory, subTask.ItemId)));
if (moved > 0.01f)
@@ -1491,12 +1490,12 @@ public sealed class ShipAiService(
return SubTaskOutcome.Completed;
}
if (!AdvanceTimedSubTask(subTask, deltaSeconds, MathF.Max(0.4f, world.Balance.MiningCycleSeconds * 0.8f)))
if (!AdvanceTimedSubTask(subTask, deltaSeconds, MathF.Max(0.4f, balance.MiningCycleSeconds * 0.8f)))
{
return SubTaskOutcome.Active;
}
var salvageRate = world.Balance.TransferRate * GetSkillFactor(Math.Max(ship.Skills.Mining, ship.Skills.Trade));
var salvageRate = balance.TransferRate * GetSkillFactor(Math.Max(ship.Skills.Mining, ship.Skills.Trade));
var recovered = MathF.Min(salvageRate, MathF.Min(remainingCapacity, wreck.RemainingAmount));
if (recovered > 0.01f)
{
@@ -1537,7 +1536,7 @@ public sealed class ShipAiService(
ship.TargetPosition = supportPosition;
ship.Position = supportPosition;
ship.State = ShipState.DeliveringConstruction;
var transferRate = world.Balance.TransferRate * GetSkillFactor(ship.Skills.Construction);
var transferRate = balance.TransferRate * GetSkillFactor(ship.Skills.Construction);
foreach (var required in site.RequiredItems.OrderBy(entry => entry.Key, StringComparer.Ordinal))
{
var delivered = GetInventoryAmount(site.DeliveredItems, required.Key);
@@ -1654,7 +1653,7 @@ public sealed class ShipAiService(
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
subTask.Progress = Math.Clamp(1f - (distance / MathF.Max(distance + GetLocalTravelSpeed(ship), 1f)), 0f, 1f);
if (distance <= MathF.Max(subTask.Threshold, world.Balance.ArrivalThreshold))
if (distance <= MathF.Max(subTask.Threshold, balance.ArrivalThreshold))
{
ship.Position = targetPosition;
ship.TargetPosition = targetPosition;