Complete universe model migration

This commit is contained in:
2026-04-07 14:16:59 -04:00
parent d0c6e30304
commit 6c92ab50c8
76 changed files with 2061 additions and 1072 deletions

View File

@@ -69,7 +69,7 @@ public sealed partial class ShipAiService
}
var targetPosition = ResolveCurrentTargetPosition(world, subTask);
var targetCelestial = ResolveTravelTargetCelestial(world, subTask, targetPosition);
var targetAnchor = ResolveTravelTargetAnchor(world, subTask, targetPosition);
ship.TargetPosition = targetPosition;
if (ship.SystemId != subTask.TargetSystemId)
@@ -81,32 +81,33 @@ public sealed partial class ShipAiService
return SubTaskOutcome.Failed;
}
var destinationEntryCelestial = ResolveSystemEntryCelestial(world, subTask.TargetSystemId);
var destinationEntryPosition = destinationEntryCelestial?.Position ?? targetPosition;
return UpdateFtlTransit(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, destinationEntryPosition, destinationEntryCelestial, completeOnArrival, targetPosition);
var destinationEntryAnchor = ResolveSystemEntryAnchor(world, subTask.TargetSystemId) ?? targetAnchor;
var destinationEntryPosition = destinationEntryAnchor?.Position ?? targetPosition;
return UpdateFtlTransit(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, destinationEntryPosition, destinationEntryAnchor, completeOnArrival, targetPosition, targetAnchor);
}
var currentCelestial = ResolveCurrentCelestial(world, ship);
if (targetCelestial is not null
&& currentCelestial is not null
&& !string.Equals(currentCelestial.Id, targetCelestial.Id, StringComparison.Ordinal))
var currentAnchor = ResolveCurrentAnchor(world, ship);
if (targetAnchor is not null
&& currentAnchor is not null
&& !string.Equals(currentAnchor.Id, targetAnchor.Id, StringComparison.Ordinal))
{
if (!CanWarp(ship.Definition))
{
return UpdateLocalTravel(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, targetPosition, targetCelestial, completeOnArrival);
return UpdateLocalTravel(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, targetPosition, currentAnchor, targetAnchor, completeOnArrival);
}
return UpdateWarpTransit(world, ship, subTask, deltaSeconds, targetPosition, targetCelestial, completeOnArrival);
return UpdateWarpTransit(world, ship, subTask, deltaSeconds, targetPosition, currentAnchor, targetAnchor, completeOnArrival);
}
if (targetCelestial is not null
&& ship.Position.DistanceTo(targetPosition) > WarpEngageDistanceKilometers
if (targetAnchor is not null
&& currentAnchor is not null
&& !string.Equals(currentAnchor.Id, targetAnchor.Id, StringComparison.Ordinal)
&& CanWarp(ship.Definition))
{
return UpdateWarpTransit(world, ship, subTask, deltaSeconds, targetPosition, targetCelestial, completeOnArrival);
return UpdateWarpTransit(world, ship, subTask, deltaSeconds, targetPosition, currentAnchor, targetAnchor, completeOnArrival);
}
return UpdateLocalTravel(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, targetPosition, targetCelestial, completeOnArrival);
return UpdateLocalTravel(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, targetPosition, currentAnchor, targetAnchor, completeOnArrival);
}
private SubTaskOutcome UpdateAttackSubTask(SimulationWorld world, ShipRuntime ship, ShipSubTaskRuntime subTask, float deltaSeconds)
@@ -157,7 +158,7 @@ public sealed partial class ShipAiService
private SubTaskOutcome UpdateMineSubTask(SimulationWorld world, ShipRuntime ship, ShipSubTaskRuntime subTask, float deltaSeconds)
{
var node = ResolveNode(world, subTask.TargetEntityId ?? subTask.TargetNodeId);
var node = ResolveNode(world, subTask.TargetResourceNodeId ?? subTask.TargetEntityId);
if (node is null || !CanExtractNode(ship, node, world))
{
subTask.BlockingReason = "node-missing";
@@ -165,9 +166,28 @@ public sealed partial class ShipAiService
return SubTaskOutcome.Failed;
}
var targetPosition = subTask.TargetPosition ?? GetResourceHoldPosition(node.Position, ship.Id, 20f);
var deposit = ResolveResourceDeposit(world, subTask.TargetResourceDepositId);
if (deposit is null || !string.Equals(deposit.NodeId, node.Id, StringComparison.Ordinal) || deposit.OreRemaining <= 0.01f)
{
deposit = SelectMiningDeposit(node, ship.Id);
subTask.TargetResourceDepositId = deposit?.Id;
}
if (deposit is null)
{
SyncNodeOreTotals(node);
return SubTaskOutcome.Completed;
}
var targetPosition = GetResourceHoldPosition(deposit.Position, ship.Id, 20f);
subTask.TargetPosition = targetPosition;
var approachThreshold = MathF.Max(subTask.Threshold, 8f);
var distanceToTarget = ship.Position.DistanceTo(targetPosition);
var distanceToDeposit = ship.Position.DistanceTo(deposit.Position);
var effectivelyAtDeposit = string.Equals(ship.SpatialState.CurrentAnchorId, node.AnchorId, StringComparison.Ordinal)
&& distanceToDeposit <= approachThreshold;
ship.TargetPosition = targetPosition;
if (ship.Position.DistanceTo(targetPosition) > MathF.Max(subTask.Threshold, 8f))
if (distanceToTarget > approachThreshold && !effectivelyAtDeposit)
{
ship.State = ShipState.MiningApproach;
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
@@ -188,14 +208,15 @@ public sealed partial class ShipAiService
var remainingCapacity = MathF.Max(0f, ship.Definition.GetTotalCargoCapacity() - cargoAmount);
var mined = MathF.Min(balance.MiningRate * GetSkillFactor(ship.Skills.Mining), remainingCapacity);
mined = MathF.Min(mined, node.OreRemaining);
mined = MathF.Min(mined, deposit.OreRemaining);
if (mined <= 0.01f)
{
return SubTaskOutcome.Completed;
}
AddInventory(ship.Inventory, node.ItemId, mined);
node.OreRemaining = MathF.Max(0f, node.OreRemaining - mined);
deposit.OreRemaining = MathF.Max(0f, deposit.OreRemaining - mined);
SyncNodeOreTotals(node);
if (GetShipCargoAmount(ship) >= ship.Definition.GetTotalCargoCapacity() - 0.01f || node.OreRemaining <= 0.01f)
{
return SubTaskOutcome.Completed;
@@ -605,15 +626,22 @@ public sealed partial class ShipAiService
float deltaSeconds,
string targetSystemId,
Vector3 targetPosition,
CelestialRuntime? targetCelestial,
AnchorRuntime? currentAnchor,
AnchorRuntime? targetAnchor,
bool completeOnArrival)
{
var distance = ship.Position.DistanceTo(targetPosition);
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
ship.SpatialState.Transit = null;
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
ship.SpatialState.DestinationAnchorId = targetAnchor?.Id ?? currentAnchor?.Id;
subTask.Progress = Math.Clamp(1f - (distance / MathF.Max(distance + GetLocalTravelSpeed(ship), 1f)), 0f, 1f);
ship.SpatialState.SystemPosition = currentAnchor is null
? ship.Position
: new Vector3(
currentAnchor.Position.X + ship.Position.X,
currentAnchor.Position.Y + ship.Position.Y,
currentAnchor.Position.Z + ship.Position.Z);
if (distance <= MathF.Max(subTask.Threshold, balance.ArrivalThreshold))
{
@@ -621,13 +649,26 @@ public sealed partial class ShipAiService
ship.TargetPosition = targetPosition;
ship.SystemId = targetSystemId;
ship.SpatialState.CurrentSystemId = targetSystemId;
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
ship.SpatialState.CurrentAnchorId = targetAnchor?.Id ?? currentAnchor?.Id;
ship.SpatialState.SystemPosition = targetAnchor is null
? targetPosition
: new Vector3(
targetAnchor.Position.X + targetPosition.X,
targetAnchor.Position.Y + targetPosition.Y,
targetAnchor.Position.Z + targetPosition.Z);
ship.State = ShipState.Arriving;
return completeOnArrival ? SubTaskOutcome.Completed : SubTaskOutcome.Active;
}
ship.State = ShipState.LocalFlight;
ship.SpatialState.CurrentAnchorId = currentAnchor?.Id;
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
ship.SpatialState.SystemPosition = currentAnchor is null
? ship.Position
: new Vector3(
currentAnchor.Position.X + ship.Position.X,
currentAnchor.Position.Y + ship.Position.Y,
currentAnchor.Position.Z + ship.Position.Z);
return SubTaskOutcome.Active;
}
@@ -637,18 +678,24 @@ public sealed partial class ShipAiService
ShipSubTaskRuntime subTask,
float deltaSeconds,
Vector3 targetPosition,
CelestialRuntime targetCelestial,
AnchorRuntime currentAnchor,
AnchorRuntime targetAnchor,
bool completeOnArrival)
{
var transit = ship.SpatialState.Transit;
if (transit is null || transit.Regime != MovementRegimeKind.Warp || transit.DestinationNodeId != targetCelestial.Id)
if (transit is null || transit.Regime != MovementRegimeKind.Warp || transit.DestinationAnchorId != targetAnchor.Id)
{
var originAnchorPosition = currentAnchor.Position;
var destinationAnchorPosition = targetAnchor.Position;
var initialSpoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
var initialTravelDuration = MathF.Max(0.1f, originAnchorPosition.DistanceTo(destinationAnchorPosition) / MathF.Max(GetWarpTravelSpeed(ship), 0.001f));
transit = new ShipTransitRuntime
{
Regime = MovementRegimeKind.Warp,
OriginNodeId = ship.SpatialState.CurrentCelestialId,
DestinationNodeId = targetCelestial.Id,
OriginAnchorId = currentAnchor.Id,
DestinationAnchorId = targetAnchor.Id,
StartedAtUtc = world.GeneratedAtUtc,
ArrivalDueAtUtc = world.GeneratedAtUtc.AddSeconds(initialSpoolDuration + initialTravelDuration),
};
ship.SpatialState.Transit = transit;
subTask.ElapsedSeconds = 0f;
@@ -656,33 +703,47 @@ public sealed partial class ShipAiService
ship.SpatialState.SpaceLayer = SpaceLayerKind.SystemSpace;
ship.SpatialState.MovementRegime = MovementRegimeKind.Warp;
ship.SpatialState.CurrentCelestialId = null;
ship.SpatialState.DestinationNodeId = targetCelestial.Id;
ship.SpatialState.CurrentAnchorId = null;
ship.SpatialState.DestinationAnchorId = targetAnchor.Id;
var spoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
if (ship.State != ShipState.Warping)
var spoolDurationSeconds = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
var startedAtUtc = transit.StartedAtUtc ?? world.GeneratedAtUtc;
var arrivalDueAtUtc = transit.ArrivalDueAtUtc ?? world.GeneratedAtUtc;
var totalDuration = MathF.Max(0.1f, (float)(arrivalDueAtUtc - startedAtUtc).TotalSeconds);
var elapsedSeconds = Math.Clamp((float)(world.GeneratedAtUtc - startedAtUtc).TotalSeconds, 0f, totalDuration);
var originPosition = ResolveAnchorPosition(world, transit.OriginAnchorId, currentAnchor.Position);
var destinationPosition = ResolveAnchorPosition(world, transit.DestinationAnchorId, targetAnchor.Position);
if (elapsedSeconds < spoolDurationSeconds)
{
ship.State = ShipState.SpoolingWarp;
if (!AdvanceTimedSubTask(subTask, deltaSeconds, spoolDuration))
{
return SubTaskOutcome.Active;
}
ship.State = ShipState.Warping;
ship.Position = Vector3.Zero;
ship.TargetPosition = Vector3.Zero;
ship.SpatialState.SystemPosition = originPosition;
transit.Progress = Math.Clamp(elapsedSeconds / totalDuration, 0f, 1f);
subTask.Progress = transit.Progress;
return SubTaskOutcome.Active;
}
var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null
? ship.Position.DistanceTo(targetPosition)
: (world.Celestials.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition)));
ship.Position = ship.Position.MoveToward(targetPosition, GetWarpTravelSpeed(ship) * deltaSeconds);
transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance));
ship.State = ShipState.Warping;
var warpTravelDuration = MathF.Max(0.001f, totalDuration - spoolDurationSeconds);
var travelElapsed = Math.Clamp(elapsedSeconds - spoolDurationSeconds, 0f, warpTravelDuration);
var travelProgress = Math.Clamp(travelElapsed / warpTravelDuration, 0f, 1f);
var travelDelta = destinationPosition.Subtract(originPosition);
ship.Position = Vector3.Zero;
ship.TargetPosition = Vector3.Zero;
ship.SpatialState.SystemPosition = new Vector3(
originPosition.X + (travelDelta.X * travelProgress),
originPosition.Y + (travelDelta.Y * travelProgress),
originPosition.Z + (travelDelta.Z * travelProgress));
transit.Progress = Math.Clamp(elapsedSeconds / totalDuration, 0f, 1f);
subTask.Progress = transit.Progress;
if (ship.Position.DistanceTo(targetPosition) > 18f)
if (elapsedSeconds < totalDuration - 0.001f)
{
return SubTaskOutcome.Active;
}
return CompleteTransitArrival(ship, subTask.TargetSystemId ?? ship.SystemId, targetPosition, targetCelestial, completeOnArrival);
return CompleteTransitArrival(ship, subTask.TargetSystemId ?? ship.SystemId, targetPosition, targetAnchor, completeOnArrival);
}
private SubTaskOutcome UpdateFtlTransit(
@@ -692,20 +753,24 @@ public sealed partial class ShipAiService
float deltaSeconds,
string targetSystemId,
Vector3 entryPosition,
CelestialRuntime? targetCelestial,
AnchorRuntime? entryAnchor,
bool completeOnArrival,
Vector3 finalTargetPosition)
Vector3 finalTargetPosition,
AnchorRuntime? finalTargetAnchor)
{
var destinationNodeId = targetCelestial?.Id;
var destinationAnchorId = entryAnchor?.Id;
var transit = ship.SpatialState.Transit;
if (transit is null || transit.Regime != MovementRegimeKind.FtlTransit || transit.DestinationNodeId != destinationNodeId)
if (transit is null || transit.Regime != MovementRegimeKind.FtlTransit || transit.DestinationAnchorId != destinationAnchorId)
{
var initialTravelDuration = MathF.Max(0.1f, ResolveSystemGalaxyPosition(world, ship.SystemId).DistanceTo(ResolveSystemGalaxyPosition(world, targetSystemId)) / MathF.Max(ship.Definition.FtlSpeed * GetSkillFactor(ship.Skills.Navigation), 0.001f));
var initialSpoolDuration = MathF.Max(ship.Definition.SpoolTime, 0.1f);
transit = new ShipTransitRuntime
{
Regime = MovementRegimeKind.FtlTransit,
OriginNodeId = ship.SpatialState.CurrentCelestialId,
DestinationNodeId = destinationNodeId,
OriginAnchorId = ship.SpatialState.CurrentAnchorId,
DestinationAnchorId = destinationAnchorId,
StartedAtUtc = world.GeneratedAtUtc,
ArrivalDueAtUtc = world.GeneratedAtUtc.AddSeconds(initialSpoolDuration + initialTravelDuration),
};
ship.SpatialState.Transit = transit;
subTask.ElapsedSeconds = 0f;
@@ -713,39 +778,32 @@ public sealed partial class ShipAiService
ship.SpatialState.SpaceLayer = SpaceLayerKind.GalaxySpace;
ship.SpatialState.MovementRegime = MovementRegimeKind.FtlTransit;
ship.SpatialState.CurrentCelestialId = null;
ship.SpatialState.DestinationNodeId = destinationNodeId;
ship.SpatialState.CurrentAnchorId = null;
ship.SpatialState.DestinationAnchorId = destinationAnchorId;
if (ship.State != ShipState.Ftl)
{
ship.State = ShipState.SpoolingFtl;
if (!AdvanceTimedSubTask(subTask, deltaSeconds, MathF.Max(ship.Definition.SpoolTime, 0.1f)))
{
return SubTaskOutcome.Active;
}
ship.State = ShipState.Ftl;
}
var originSystemPosition = ResolveSystemGalaxyPosition(world, ship.SystemId);
var destinationSystemPosition = ResolveSystemGalaxyPosition(world, targetSystemId);
var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition));
transit.Progress = MathF.Min(1f, transit.Progress + ((ship.Definition.FtlSpeed * GetSkillFactor(ship.Skills.Navigation)) * deltaSeconds / totalDistance));
var spoolDurationSeconds = MathF.Max(ship.Definition.SpoolTime, 0.1f);
var startedAtUtc = transit.StartedAtUtc ?? world.GeneratedAtUtc;
var arrivalDueAtUtc = transit.ArrivalDueAtUtc ?? world.GeneratedAtUtc;
var totalDuration = MathF.Max(0.1f, (float)(arrivalDueAtUtc - startedAtUtc).TotalSeconds);
var elapsedSeconds = Math.Clamp((float)(world.GeneratedAtUtc - startedAtUtc).TotalSeconds, 0f, totalDuration);
ship.State = elapsedSeconds < spoolDurationSeconds ? ShipState.SpoolingFtl : ShipState.Ftl;
transit.Progress = Math.Clamp(elapsedSeconds / totalDuration, 0f, 1f);
subTask.Progress = transit.Progress;
if (transit.Progress < 0.999f)
if (elapsedSeconds < totalDuration - 0.001f)
{
return SubTaskOutcome.Active;
}
ship.Position = entryPosition;
ship.Position = Vector3.Zero;
ship.TargetPosition = finalTargetPosition;
ship.SystemId = targetSystemId;
ship.SpatialState.CurrentSystemId = targetSystemId;
ship.SpatialState.Transit = null;
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
ship.SpatialState.CurrentAnchorId = entryAnchor?.Id;
ship.SpatialState.DestinationAnchorId = finalTargetAnchor?.Id ?? entryAnchor?.Id;
ship.SpatialState.SystemPosition = entryPosition;
ship.State = ShipState.Arriving;
// Cross-system travel is only complete once the ship finishes the
@@ -753,7 +811,7 @@ public sealed partial class ShipAiService
return SubTaskOutcome.Active;
}
private static SubTaskOutcome CompleteTransitArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, CelestialRuntime? targetCelestial, bool completeOnArrival)
private static SubTaskOutcome CompleteTransitArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, AnchorRuntime? targetAnchor, bool completeOnArrival)
{
ship.Position = targetPosition;
ship.TargetPosition = targetPosition;
@@ -762,8 +820,14 @@ public sealed partial class ShipAiService
ship.SpatialState.Transit = null;
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
ship.SpatialState.CurrentAnchorId = targetAnchor?.Id;
ship.SpatialState.DestinationAnchorId = targetAnchor?.Id;
ship.SpatialState.SystemPosition = targetAnchor is null
? targetPosition
: new Vector3(
targetAnchor.Position.X + targetPosition.X,
targetAnchor.Position.Y + targetPosition.Y,
targetAnchor.Position.Z + targetPosition.Z);
ship.State = ShipState.Arriving;
return completeOnArrival ? SubTaskOutcome.Completed : SubTaskOutcome.Active;
}