Compare commits
3 Commits
fdcf83ccec
...
6c92ab50c8
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c92ab50c8 | |||
| d0c6e30304 | |||
| 75568324f5 |
@@ -1097,14 +1097,14 @@ internal sealed class CommanderPlanningService
|
|||||||
{
|
{
|
||||||
theaters.Add(new FactionTheaterRuntime
|
theaters.Add(new FactionTheaterRuntime
|
||||||
{
|
{
|
||||||
Id = $"theater-expansion-{expansionProject.SystemId}-{expansionProject.CelestialId}",
|
Id = $"theater-expansion-{expansionProject.SystemId}-{expansionProject.AnchorId}",
|
||||||
Kind = "expansion-front",
|
Kind = "expansion-front",
|
||||||
SystemId = expansionProject.SystemId,
|
SystemId = expansionProject.SystemId,
|
||||||
Status = "active",
|
Status = "active",
|
||||||
Priority = 65f + (economicAssessment.HasShipyard ? 0f : 15f),
|
Priority = 65f + (economicAssessment.HasShipyard ? 0f : 15f),
|
||||||
SupplyRisk = ComputeSystemRisk(world, faction, expansionProject.SystemId),
|
SupplyRisk = ComputeSystemRisk(world, faction, expansionProject.SystemId),
|
||||||
FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, expansionProject.SystemId),
|
FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, expansionProject.SystemId),
|
||||||
AnchorEntityId = expansionProject.SiteId ?? expansionProject.CelestialId,
|
AnchorEntityId = expansionProject.SiteId ?? expansionProject.AnchorId,
|
||||||
AnchorPosition = ResolveExpansionAnchor(world, expansionProject),
|
AnchorPosition = ResolveExpansionAnchor(world, expansionProject),
|
||||||
UpdatedAtUtc = nowUtc,
|
UpdatedAtUtc = nowUtc,
|
||||||
});
|
});
|
||||||
@@ -1272,7 +1272,7 @@ internal sealed class CommanderPlanningService
|
|||||||
],
|
],
|
||||||
"expansion" =>
|
"expansion" =>
|
||||||
[
|
[
|
||||||
new FactionPlanStepRuntime { Id = $"{campaign.Id}-claim", Kind = "secure-claim", Status = "active", Summary = $"Secure {expansionProject?.CelestialId ?? campaign.TargetEntityId} for construction." },
|
new FactionPlanStepRuntime { Id = $"{campaign.Id}-claim", Kind = "secure-claim", Status = "active", Summary = $"Secure {expansionProject?.AnchorId ?? campaign.TargetEntityId} for construction." },
|
||||||
new FactionPlanStepRuntime { Id = $"{campaign.Id}-supply", Kind = "supply-site", Status = "planned", Summary = "Move construction materials to the site." },
|
new FactionPlanStepRuntime { Id = $"{campaign.Id}-supply", Kind = "supply-site", Status = "planned", Summary = "Move construction materials to the site." },
|
||||||
new FactionPlanStepRuntime { Id = $"{campaign.Id}-guard", Kind = "guard-site", Status = "planned", Summary = "Defend the expansion site until operational." },
|
new FactionPlanStepRuntime { Id = $"{campaign.Id}-guard", Kind = "guard-site", Status = "planned", Summary = "Defend the expansion site until operational." },
|
||||||
],
|
],
|
||||||
@@ -2725,7 +2725,7 @@ internal sealed class CommanderPlanningService
|
|||||||
AreaSystemId = areaSystemId,
|
AreaSystemId = areaSystemId,
|
||||||
TargetEntityId = objective.TargetEntityId,
|
TargetEntityId = objective.TargetEntityId,
|
||||||
ItemId = objective.ItemId ?? fallback.ItemId,
|
ItemId = objective.ItemId ?? fallback.ItemId,
|
||||||
PreferredNodeId = fallback.PreferredNodeId,
|
PreferredAnchorId = fallback.PreferredAnchorId,
|
||||||
PreferredConstructionSiteId = objective.Kind is "construct-site" or "supply-site" ? objective.TargetEntityId : fallback.PreferredConstructionSiteId,
|
PreferredConstructionSiteId = objective.Kind is "construct-site" or "supply-site" ? objective.TargetEntityId : fallback.PreferredConstructionSiteId,
|
||||||
PreferredModuleId = fallback.PreferredModuleId,
|
PreferredModuleId = fallback.PreferredModuleId,
|
||||||
TargetPosition = objective.TargetPosition ?? fallback.TargetPosition,
|
TargetPosition = objective.TargetPosition ?? fallback.TargetPosition,
|
||||||
@@ -2750,7 +2750,7 @@ internal sealed class CommanderPlanningService
|
|||||||
target.AreaSystemId = source.AreaSystemId;
|
target.AreaSystemId = source.AreaSystemId;
|
||||||
target.TargetEntityId = source.TargetEntityId;
|
target.TargetEntityId = source.TargetEntityId;
|
||||||
target.ItemId = source.ItemId;
|
target.ItemId = source.ItemId;
|
||||||
target.PreferredNodeId = source.PreferredNodeId;
|
target.PreferredAnchorId = source.PreferredAnchorId;
|
||||||
target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
|
target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
|
||||||
target.PreferredModuleId = source.PreferredModuleId;
|
target.PreferredModuleId = source.PreferredModuleId;
|
||||||
target.TargetPosition = source.TargetPosition;
|
target.TargetPosition = source.TargetPosition;
|
||||||
@@ -2771,7 +2771,7 @@ internal sealed class CommanderPlanningService
|
|||||||
&& string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
|
&& string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
|
&& string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.PreferredNodeId, right.PreferredNodeId, StringComparison.Ordinal)
|
&& string.Equals(left.PreferredAnchorId, right.PreferredAnchorId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
|
&& string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
|
&& string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
|
||||||
&& Nullable.Equals(left.TargetPosition, right.TargetPosition)
|
&& Nullable.Equals(left.TargetPosition, right.TargetPosition)
|
||||||
@@ -2792,7 +2792,7 @@ internal sealed class CommanderPlanningService
|
|||||||
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
|
&& string.Equals(left.AnchorId, right.AnchorId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
||||||
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
||||||
@@ -2863,9 +2863,10 @@ internal sealed class CommanderPlanningService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == entityId);
|
var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == entityId);
|
||||||
if (site?.CelestialId is { } celestialId)
|
if (site is not null)
|
||||||
{
|
{
|
||||||
return world.Celestials.FirstOrDefault(celestial => celestial.Id == celestialId)?.Position;
|
return world.Anchors.FirstOrDefault(anchor => anchor.Id == site.AnchorId)?.Position
|
||||||
|
?? Vector3.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -2919,7 +2920,7 @@ internal sealed class CommanderPlanningService
|
|||||||
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
|
&& string.Equals(left.AnchorId, right.AnchorId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
||||||
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
||||||
@@ -3382,7 +3383,7 @@ internal sealed class CommanderPlanningService
|
|||||||
{
|
{
|
||||||
"defense-front" => $"Defend {theater.SystemId} from hostile pressure.",
|
"defense-front" => $"Defend {theater.SystemId} from hostile pressure.",
|
||||||
"offense-front" => $"Project force into {theater.SystemId}.",
|
"offense-front" => $"Project force into {theater.SystemId}.",
|
||||||
"expansion-front" => $"Expand into {expansionProject?.CelestialId ?? theater.SystemId}.",
|
"expansion-front" => $"Expand into {expansionProject?.AnchorId ?? theater.SystemId}.",
|
||||||
"economic-front" => $"Stabilize commodity shortages around {theater.AnchorEntityId ?? theater.SystemId}.",
|
"economic-front" => $"Stabilize commodity shortages around {theater.AnchorEntityId ?? theater.SystemId}.",
|
||||||
_ => theater.Kind,
|
_ => theater.Kind,
|
||||||
};
|
};
|
||||||
@@ -3424,13 +3425,13 @@ internal sealed class CommanderPlanningService
|
|||||||
private static Vector3 ResolveExpansionAnchor(SimulationWorld world, IndustryExpansionProject project)
|
private static Vector3 ResolveExpansionAnchor(SimulationWorld world, IndustryExpansionProject project)
|
||||||
{
|
{
|
||||||
if (project.SiteId is not null
|
if (project.SiteId is not null
|
||||||
&& world.ConstructionSites.FirstOrDefault(site => site.Id == project.SiteId) is { } site
|
&& world.ConstructionSites.FirstOrDefault(site => site.Id == project.SiteId) is { } site)
|
||||||
&& world.Celestials.FirstOrDefault(candidate => candidate.Id == site.CelestialId) is { } siteCelestial)
|
|
||||||
{
|
{
|
||||||
return siteCelestial.Position;
|
return world.Anchors.FirstOrDefault(candidate => candidate.Id == site.AnchorId)?.Position
|
||||||
|
?? Vector3.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
return world.Celestials.FirstOrDefault(candidate => candidate.Id == project.CelestialId)?.Position
|
return world.Anchors.FirstOrDefault(candidate => candidate.Id == project.AnchorId)?.Position
|
||||||
?? ResolveSystemAnchor(world, project.SystemId);
|
?? ResolveSystemAnchor(world, project.SystemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public sealed record TerritoryClaimSnapshot(
|
|||||||
string? SourceClaimId,
|
string? SourceClaimId,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
string CelestialId,
|
string AnchorId,
|
||||||
string Status,
|
string Status,
|
||||||
string ClaimKind,
|
string ClaimKind,
|
||||||
float ClaimStrength,
|
float ClaimStrength,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public sealed class TerritoryClaimRuntime
|
|||||||
public string? SourceClaimId { get; set; }
|
public string? SourceClaimId { get; set; }
|
||||||
public required string FactionId { get; set; }
|
public required string FactionId { get; set; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public required string CelestialId { get; set; }
|
public required string AnchorId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string ClaimKind { get; set; } = "infrastructure";
|
public string ClaimKind { get; set; } = "infrastructure";
|
||||||
public float ClaimStrength { get; set; }
|
public float ClaimStrength { get; set; }
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ internal sealed class GeopoliticalSimulationService
|
|||||||
SourceClaimId = claim.Id,
|
SourceClaimId = claim.Id,
|
||||||
FactionId = claim.FactionId,
|
FactionId = claim.FactionId,
|
||||||
SystemId = claim.SystemId,
|
SystemId = claim.SystemId,
|
||||||
CelestialId = claim.CelestialId,
|
AnchorId = claim.AnchorId,
|
||||||
Status = claim.State,
|
Status = claim.State,
|
||||||
ClaimKind = "infrastructure",
|
ClaimKind = "infrastructure",
|
||||||
ClaimStrength = claim.State == ClaimStateKinds.Active ? 1f : 0.65f,
|
ClaimStrength = claim.State == ClaimStateKinds.Active ? 1f : 0.65f,
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ internal static class FactionIndustryPlanner
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetCelestial = SelectFoundationCelestial(world, factionId, bottleneckCommodity);
|
var targetAnchor = SelectFoundationAnchor(world, factionId, bottleneckCommodity);
|
||||||
if (targetCelestial is null)
|
if (targetAnchor is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportStation = SelectSupportStation(world, factionId, moduleId, targetCelestial.SystemId);
|
var supportStation = SelectSupportStation(world, factionId, moduleId, targetAnchor.SystemId);
|
||||||
if (supportStation is null)
|
if (supportStation is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -36,8 +36,8 @@ internal static class FactionIndustryPlanner
|
|||||||
return new IndustryExpansionProject(
|
return new IndustryExpansionProject(
|
||||||
bottleneckCommodity,
|
bottleneckCommodity,
|
||||||
moduleId,
|
moduleId,
|
||||||
targetCelestial.SystemId,
|
targetAnchor.SystemId,
|
||||||
targetCelestial.Id,
|
targetAnchor.Id,
|
||||||
supportStation.Id);
|
supportStation.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,13 +93,13 @@ internal static class FactionIndustryPlanner
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetCelestial = SelectLogisticsFoundationCelestial(world, factionId);
|
var targetAnchor = SelectLogisticsFoundationAnchor(world, factionId);
|
||||||
if (targetCelestial is null)
|
if (targetAnchor is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportStation = SelectSupportStation(world, factionId, shipyardModuleId, targetCelestial.SystemId);
|
var supportStation = SelectSupportStation(world, factionId, shipyardModuleId, targetAnchor.SystemId);
|
||||||
if (supportStation is null)
|
if (supportStation is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -108,8 +108,8 @@ internal static class FactionIndustryPlanner
|
|||||||
return new IndustryExpansionProject(
|
return new IndustryExpansionProject(
|
||||||
"shipyard",
|
"shipyard",
|
||||||
shipyardModuleId,
|
shipyardModuleId,
|
||||||
targetCelestial.SystemId,
|
targetAnchor.SystemId,
|
||||||
targetCelestial.Id,
|
targetAnchor.Id,
|
||||||
supportStation.Id);
|
supportStation.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,13 +129,13 @@ internal static class FactionIndustryPlanner
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bootstrapCelestial = SelectFoundationCelestial(world, factionId, bootstrapCommodity);
|
var bootstrapAnchor = SelectFoundationAnchor(world, factionId, bootstrapCommodity);
|
||||||
if (bootstrapCelestial is null)
|
if (bootstrapAnchor is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bootstrapSupportStation = SelectSupportStation(world, factionId, bootstrapModuleId, bootstrapCelestial.SystemId);
|
var bootstrapSupportStation = SelectSupportStation(world, factionId, bootstrapModuleId, bootstrapAnchor.SystemId);
|
||||||
if (bootstrapSupportStation is null)
|
if (bootstrapSupportStation is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -144,8 +144,8 @@ internal static class FactionIndustryPlanner
|
|||||||
return new IndustryExpansionProject(
|
return new IndustryExpansionProject(
|
||||||
bootstrapCommodity,
|
bootstrapCommodity,
|
||||||
bootstrapModuleId,
|
bootstrapModuleId,
|
||||||
bootstrapCelestial.SystemId,
|
bootstrapAnchor.SystemId,
|
||||||
bootstrapCelestial.Id,
|
bootstrapAnchor.Id,
|
||||||
bootstrapSupportStation.Id);
|
bootstrapSupportStation.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,13 +161,13 @@ internal static class FactionIndustryPlanner
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetCelestial = SelectFoundationCelestial(world, factionId, commodityId);
|
var targetAnchor = SelectFoundationAnchor(world, factionId, commodityId);
|
||||||
if (targetCelestial is null)
|
if (targetAnchor is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportStation = SelectSupportStation(world, factionId, moduleId, targetCelestial.SystemId);
|
var supportStation = SelectSupportStation(world, factionId, moduleId, targetAnchor.SystemId);
|
||||||
if (supportStation is null)
|
if (supportStation is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -176,8 +176,8 @@ internal static class FactionIndustryPlanner
|
|||||||
return new IndustryExpansionProject(
|
return new IndustryExpansionProject(
|
||||||
commodityId,
|
commodityId,
|
||||||
moduleId,
|
moduleId,
|
||||||
targetCelestial.SystemId,
|
targetAnchor.SystemId,
|
||||||
targetCelestial.Id,
|
targetAnchor.Id,
|
||||||
supportStation.Id);
|
supportStation.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ internal static class FactionIndustryPlanner
|
|||||||
site.TargetDefinitionId,
|
site.TargetDefinitionId,
|
||||||
site.BlueprintId,
|
site.BlueprintId,
|
||||||
site.SystemId,
|
site.SystemId,
|
||||||
site.CelestialId,
|
site.AnchorId,
|
||||||
supportStationId,
|
supportStationId,
|
||||||
site.Id);
|
site.Id);
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ internal static class FactionIndustryPlanner
|
|||||||
}
|
}
|
||||||
|
|
||||||
var nowUtc = DateTimeOffset.UtcNow;
|
var nowUtc = DateTimeOffset.UtcNow;
|
||||||
var claimId = $"claim-{factionId}-{project.CelestialId}";
|
var claimId = $"claim-{factionId}-{project.AnchorId}";
|
||||||
if (world.Claims.All(candidate => candidate.Id != claimId))
|
if (world.Claims.All(candidate => candidate.Id != claimId))
|
||||||
{
|
{
|
||||||
world.Claims.Add(new ClaimRuntime
|
world.Claims.Add(new ClaimRuntime
|
||||||
@@ -233,7 +233,7 @@ internal static class FactionIndustryPlanner
|
|||||||
Id = claimId,
|
Id = claimId,
|
||||||
FactionId = factionId,
|
FactionId = factionId,
|
||||||
SystemId = project.SystemId,
|
SystemId = project.SystemId,
|
||||||
CelestialId = project.CelestialId,
|
AnchorId = project.AnchorId,
|
||||||
PlacedAtUtc = nowUtc,
|
PlacedAtUtc = nowUtc,
|
||||||
ActivatesAtUtc = nowUtc.AddSeconds(8),
|
ActivatesAtUtc = nowUtc.AddSeconds(8),
|
||||||
State = ClaimStateKinds.Activating,
|
State = ClaimStateKinds.Activating,
|
||||||
@@ -246,7 +246,7 @@ internal static class FactionIndustryPlanner
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var siteId = $"site-{factionId}-{project.CelestialId}";
|
var siteId = $"site-{factionId}-{project.AnchorId}";
|
||||||
if (world.ConstructionSites.Any(candidate => candidate.Id == siteId))
|
if (world.ConstructionSites.Any(candidate => candidate.Id == siteId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -257,7 +257,7 @@ internal static class FactionIndustryPlanner
|
|||||||
Id = siteId,
|
Id = siteId,
|
||||||
FactionId = factionId,
|
FactionId = factionId,
|
||||||
SystemId = project.SystemId,
|
SystemId = project.SystemId,
|
||||||
CelestialId = project.CelestialId,
|
AnchorId = project.AnchorId,
|
||||||
TargetKind = "station-foundation",
|
TargetKind = "station-foundation",
|
||||||
TargetDefinitionId = project.CommodityId,
|
TargetDefinitionId = project.CommodityId,
|
||||||
BlueprintId = project.ModuleId,
|
BlueprintId = project.ModuleId,
|
||||||
@@ -450,51 +450,51 @@ internal static class FactionIndustryPlanner
|
|||||||
private static float GetTargetLevelSeconds(string itemId) =>
|
private static float GetTargetLevelSeconds(string itemId) =>
|
||||||
string.Equals(itemId, "water", StringComparison.Ordinal) ? WaterTargetLevelSeconds : CommodityTargetLevelSeconds;
|
string.Equals(itemId, "water", StringComparison.Ordinal) ? WaterTargetLevelSeconds : CommodityTargetLevelSeconds;
|
||||||
|
|
||||||
private static CelestialRuntime? SelectFoundationCelestial(SimulationWorld world, string factionId, string commodityId)
|
private static AnchorRuntime? SelectFoundationAnchor(SimulationWorld world, string factionId, string commodityId)
|
||||||
{
|
{
|
||||||
var resourceItems = ResolveRootResourceItems(world, commodityId);
|
var resourceItems = ResolveRootResourceItems(world, commodityId);
|
||||||
return world.Celestials
|
return world.Anchors
|
||||||
.Where(celestial =>
|
.Where(anchor =>
|
||||||
celestial.Kind == SpatialNodeKind.LagrangePoint
|
anchor.Kind == SpatialNodeKind.LagrangePoint
|
||||||
&& celestial.OccupyingStructureId is null
|
&& anchor.OccupyingStructureId is null
|
||||||
&& world.Claims.All(claim => claim.CelestialId != celestial.Id || claim.State == ClaimStateKinds.Destroyed)
|
&& world.Claims.All(claim => claim.AnchorId != anchor.Id || claim.State == ClaimStateKinds.Destroyed)
|
||||||
&& IsExpansionSystemEligible(world, factionId, celestial.SystemId))
|
&& IsExpansionSystemEligible(world, factionId, anchor.SystemId))
|
||||||
.OrderByDescending(celestial => ScoreCelestial(world, factionId, celestial, resourceItems))
|
.OrderByDescending(anchor => ScoreAnchor(world, factionId, anchor, resourceItems))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CelestialRuntime? SelectLogisticsFoundationCelestial(SimulationWorld world, string factionId)
|
private static AnchorRuntime? SelectLogisticsFoundationAnchor(SimulationWorld world, string factionId)
|
||||||
{
|
{
|
||||||
return world.Celestials
|
return world.Anchors
|
||||||
.Where(celestial =>
|
.Where(anchor =>
|
||||||
celestial.Kind == SpatialNodeKind.LagrangePoint
|
anchor.Kind == SpatialNodeKind.LagrangePoint
|
||||||
&& celestial.OccupyingStructureId is null
|
&& anchor.OccupyingStructureId is null
|
||||||
&& world.Claims.All(claim => claim.CelestialId != celestial.Id || claim.State == ClaimStateKinds.Destroyed)
|
&& world.Claims.All(claim => claim.AnchorId != anchor.Id || claim.State == ClaimStateKinds.Destroyed)
|
||||||
&& IsExpansionSystemEligible(world, factionId, celestial.SystemId))
|
&& IsExpansionSystemEligible(world, factionId, anchor.SystemId))
|
||||||
.OrderByDescending(celestial => world.Stations.Count(station =>
|
.OrderByDescending(anchor => world.Stations.Count(station =>
|
||||||
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
||||||
&& string.Equals(station.SystemId, celestial.SystemId, StringComparison.Ordinal)))
|
&& string.Equals(station.SystemId, anchor.SystemId, StringComparison.Ordinal)))
|
||||||
.ThenByDescending(celestial => world.Stations
|
.ThenByDescending(anchor => world.Stations
|
||||||
.Where(station =>
|
.Where(station =>
|
||||||
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
||||||
&& string.Equals(station.SystemId, celestial.SystemId, StringComparison.Ordinal))
|
&& string.Equals(station.SystemId, anchor.SystemId, StringComparison.Ordinal))
|
||||||
.Sum(station => station.Inventory.Values.Sum()))
|
.Sum(station => station.Inventory.Values.Sum()))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float ScoreCelestial(SimulationWorld world, string factionId, CelestialRuntime celestial, IReadOnlyCollection<string> resourceItems)
|
private static float ScoreAnchor(SimulationWorld world, string factionId, AnchorRuntime anchor, IReadOnlyCollection<string> resourceItems)
|
||||||
{
|
{
|
||||||
var resourceScore = world.Nodes
|
var resourceScore = world.Nodes
|
||||||
.Where(node => node.SystemId == celestial.SystemId && resourceItems.Contains(node.ItemId, StringComparer.Ordinal))
|
.Where(node => node.SystemId == anchor.SystemId && resourceItems.Contains(node.ItemId, StringComparer.Ordinal))
|
||||||
.Sum(node => node.OreRemaining);
|
.Sum(node => node.OreRemaining);
|
||||||
var factionPresence = world.Stations.Count(station =>
|
var factionPresence = world.Stations.Count(station =>
|
||||||
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
||||||
&& string.Equals(station.SystemId, celestial.SystemId, StringComparison.Ordinal));
|
&& string.Equals(station.SystemId, anchor.SystemId, StringComparison.Ordinal));
|
||||||
var controlState = GeopoliticalSimulationService.GetSystemControlState(world, celestial.SystemId);
|
var controlState = GeopoliticalSimulationService.GetSystemControlState(world, anchor.SystemId);
|
||||||
var region = GeopoliticalSimulationService.GetPrimaryEconomicRegion(world, factionId, celestial.SystemId);
|
var region = GeopoliticalSimulationService.GetPrimaryEconomicRegion(world, factionId, anchor.SystemId);
|
||||||
var strategicProfile = world.Geopolitics?.Territory.StrategicProfiles.FirstOrDefault(profile => profile.SystemId == celestial.SystemId);
|
var strategicProfile = world.Geopolitics?.Territory.StrategicProfiles.FirstOrDefault(profile => profile.SystemId == anchor.SystemId);
|
||||||
var pressure = world.Geopolitics?.Territory.Pressures
|
var pressure = world.Geopolitics?.Territory.Pressures
|
||||||
.Where(entry => entry.SystemId == celestial.SystemId && entry.FactionId == factionId)
|
.Where(entry => entry.SystemId == anchor.SystemId && entry.FactionId == factionId)
|
||||||
.OrderByDescending(entry => entry.HostileInfluence)
|
.OrderByDescending(entry => entry.HostileInfluence)
|
||||||
.ThenBy(entry => entry.Id, StringComparer.Ordinal)
|
.ThenBy(entry => entry.Id, StringComparer.Ordinal)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
@@ -515,7 +515,7 @@ internal static class FactionIndustryPlanner
|
|||||||
};
|
};
|
||||||
var securityPenalty = ((pressure?.HostileInfluence ?? 0f) * 14f)
|
var securityPenalty = ((pressure?.HostileInfluence ?? 0f) * 14f)
|
||||||
+ ((strategicProfile?.TerritorialPressure ?? 0f) * 9f)
|
+ ((strategicProfile?.TerritorialPressure ?? 0f) * 9f)
|
||||||
+ ((world.Geopolitics is null ? 0f : GeopoliticalSimulationService.GetSystemRouteRisk(world, celestial.SystemId, factionId)) * 250f);
|
+ ((world.Geopolitics is null ? 0f : GeopoliticalSimulationService.GetSystemRouteRisk(world, anchor.SystemId, factionId)) * 250f);
|
||||||
return resourceScore
|
return resourceScore
|
||||||
+ (factionPresence * 5_000f)
|
+ (factionPresence * 5_000f)
|
||||||
+ controlBias
|
+ controlBias
|
||||||
@@ -585,6 +585,6 @@ internal sealed record IndustryExpansionProject(
|
|||||||
string CommodityId,
|
string CommodityId,
|
||||||
string ModuleId,
|
string ModuleId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
string CelestialId,
|
string AnchorId,
|
||||||
string SupportStationId,
|
string SupportStationId,
|
||||||
string? SiteId = null);
|
string? SiteId = null);
|
||||||
|
|||||||
@@ -17,16 +17,15 @@ public sealed class GetPlayerIdentitiesHandler(IAuthRepository authRepository, I
|
|||||||
public override async Task HandleAsync(CancellationToken cancellationToken)
|
public override async Task HandleAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var users = await authRepository.ListUsersAsync(cancellationToken);
|
var users = await authRepository.ListUsersAsync(cancellationToken);
|
||||||
var playerFactionsById = playerStateStore.GetPlayerFactions()
|
var playerFactionsByPlayerId = playerStateStore.GetPlayerFactionsByPlayerId();
|
||||||
.ToDictionary(player => player.Id, StringComparer.Ordinal);
|
|
||||||
|
|
||||||
var responses = new List<PlayerIdentitySummaryResponse>(users.Count + playerFactionsById.Count);
|
var responses = new List<PlayerIdentitySummaryResponse>(users.Count + playerFactionsByPlayerId.Count);
|
||||||
var seenIds = new HashSet<string>(StringComparer.Ordinal);
|
var seenIds = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
foreach (var user in users)
|
foreach (var user in users)
|
||||||
{
|
{
|
||||||
var userId = user.Id.ToString("N");
|
var userId = user.Id.ToString("N");
|
||||||
playerFactionsById.TryGetValue(userId, out var playerFaction);
|
playerFactionsByPlayerId.TryGetValue(userId, out var playerFaction);
|
||||||
responses.Add(new PlayerIdentitySummaryResponse(
|
responses.Add(new PlayerIdentitySummaryResponse(
|
||||||
userId,
|
userId,
|
||||||
user.Email,
|
user.Email,
|
||||||
@@ -38,19 +37,19 @@ public sealed class GetPlayerIdentitiesHandler(IAuthRepository authRepository, I
|
|||||||
seenIds.Add(userId);
|
seenIds.Add(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var playerFaction in playerStateStore.GetPlayerFactions())
|
foreach (var (playerId, playerFaction) in playerFactionsByPlayerId)
|
||||||
{
|
{
|
||||||
if (!seenIds.Add(playerFaction.Id))
|
if (!seenIds.Add(playerId))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.Add(new PlayerIdentitySummaryResponse(
|
responses.Add(new PlayerIdentitySummaryResponse(
|
||||||
playerFaction.Id,
|
playerId,
|
||||||
$"{playerFaction.Id}@unknown",
|
$"{playerId}@unknown",
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
true,
|
true,
|
||||||
playerFaction.Id,
|
playerId,
|
||||||
playerFaction.Label,
|
playerFaction.Label,
|
||||||
playerFaction.SovereignFactionId));
|
playerFaction.SovereignFactionId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public sealed record PlayerDirectiveSnapshot(
|
|||||||
bool UseOrders,
|
bool UseOrders,
|
||||||
string? StagingOrderKind,
|
string? StagingOrderKind,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? PreferredNodeId,
|
string? PreferredAnchorId,
|
||||||
string? PreferredConstructionSiteId,
|
string? PreferredConstructionSiteId,
|
||||||
string? PreferredModuleId,
|
string? PreferredModuleId,
|
||||||
int Priority,
|
int Priority,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public sealed record PlayerDirectiveCommandRequest(
|
|||||||
string? SourceStationId,
|
string? SourceStationId,
|
||||||
string? DestinationStationId,
|
string? DestinationStationId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? PreferredNodeId,
|
string? PreferredAnchorId,
|
||||||
string? PreferredConstructionSiteId,
|
string? PreferredConstructionSiteId,
|
||||||
string? PreferredModuleId,
|
string? PreferredModuleId,
|
||||||
int Priority,
|
int Priority,
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ public sealed class PlayerDirectiveRuntime
|
|||||||
public bool UseOrders { get; set; }
|
public bool UseOrders { get; set; }
|
||||||
public string? StagingOrderKind { get; set; }
|
public string? StagingOrderKind { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? PreferredNodeId { get; set; }
|
public string? PreferredAnchorId { get; set; }
|
||||||
public string? PreferredConstructionSiteId { get; set; }
|
public string? PreferredConstructionSiteId { get; set; }
|
||||||
public string? PreferredModuleId { get; set; }
|
public string? PreferredModuleId { get; set; }
|
||||||
public int Priority { get; set; } = 50;
|
public int Priority { get; set; } = 50;
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ public interface IPlayerStateStore
|
|||||||
bool TryGetPlayerFaction(string playerId, out PlayerFactionRuntime playerFaction);
|
bool TryGetPlayerFaction(string playerId, out PlayerFactionRuntime playerFaction);
|
||||||
PlayerFactionRuntime GetOrAddPlayerFaction(string playerId, Func<PlayerFactionRuntime> factory);
|
PlayerFactionRuntime GetOrAddPlayerFaction(string playerId, Func<PlayerFactionRuntime> factory);
|
||||||
IReadOnlyCollection<PlayerFactionRuntime> GetPlayerFactions();
|
IReadOnlyCollection<PlayerFactionRuntime> GetPlayerFactions();
|
||||||
|
IReadOnlyDictionary<string, PlayerFactionRuntime> GetPlayerFactionsByPlayerId();
|
||||||
void Clear();
|
void Clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ public sealed class PlayerFactionProjectionService
|
|||||||
directive.UseOrders,
|
directive.UseOrders,
|
||||||
directive.StagingOrderKind,
|
directive.StagingOrderKind,
|
||||||
directive.ItemId,
|
directive.ItemId,
|
||||||
directive.PreferredNodeId,
|
directive.PreferredAnchorId,
|
||||||
directive.PreferredConstructionSiteId,
|
directive.PreferredConstructionSiteId,
|
||||||
directive.PreferredModuleId,
|
directive.PreferredModuleId,
|
||||||
directive.Priority,
|
directive.Priority,
|
||||||
@@ -261,7 +261,7 @@ public sealed class PlayerFactionProjectionService
|
|||||||
template.SourceStationId,
|
template.SourceStationId,
|
||||||
template.DestinationStationId,
|
template.DestinationStationId,
|
||||||
template.ItemId,
|
template.ItemId,
|
||||||
template.NodeId,
|
template.AnchorId,
|
||||||
template.ConstructionSiteId,
|
template.ConstructionSiteId,
|
||||||
template.ModuleId,
|
template.ModuleId,
|
||||||
template.WaitSeconds,
|
template.WaitSeconds,
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ internal sealed class PlayerFactionService
|
|||||||
directive.SourceStationId = request.SourceStationId;
|
directive.SourceStationId = request.SourceStationId;
|
||||||
directive.DestinationStationId = request.DestinationStationId;
|
directive.DestinationStationId = request.DestinationStationId;
|
||||||
directive.ItemId = request.ItemId;
|
directive.ItemId = request.ItemId;
|
||||||
directive.PreferredNodeId = request.PreferredNodeId;
|
directive.PreferredAnchorId = request.PreferredAnchorId;
|
||||||
directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
||||||
directive.PreferredModuleId = request.PreferredModuleId;
|
directive.PreferredModuleId = request.PreferredModuleId;
|
||||||
directive.Priority = request.Priority;
|
directive.Priority = request.Priority;
|
||||||
@@ -355,7 +355,7 @@ internal sealed class PlayerFactionService
|
|||||||
SourceStationId = template.SourceStationId,
|
SourceStationId = template.SourceStationId,
|
||||||
DestinationStationId = template.DestinationStationId,
|
DestinationStationId = template.DestinationStationId,
|
||||||
ItemId = template.ItemId,
|
ItemId = template.ItemId,
|
||||||
NodeId = template.NodeId,
|
AnchorId = template.AnchorId,
|
||||||
ConstructionSiteId = template.ConstructionSiteId,
|
ConstructionSiteId = template.ConstructionSiteId,
|
||||||
ModuleId = template.ModuleId,
|
ModuleId = template.ModuleId,
|
||||||
WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
|
WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
|
||||||
@@ -501,7 +501,7 @@ internal sealed class PlayerFactionService
|
|||||||
SourceStationId = template.SourceStationId,
|
SourceStationId = template.SourceStationId,
|
||||||
DestinationStationId = template.DestinationStationId,
|
DestinationStationId = template.DestinationStationId,
|
||||||
ItemId = template.ItemId,
|
ItemId = template.ItemId,
|
||||||
NodeId = template.NodeId,
|
AnchorId = template.AnchorId,
|
||||||
ConstructionSiteId = template.ConstructionSiteId,
|
ConstructionSiteId = template.ConstructionSiteId,
|
||||||
ModuleId = template.ModuleId,
|
ModuleId = template.ModuleId,
|
||||||
WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
|
WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
|
||||||
@@ -692,7 +692,7 @@ internal sealed class PlayerFactionService
|
|||||||
SourceStationId = request.SourceStationId,
|
SourceStationId = request.SourceStationId,
|
||||||
DestinationStationId = request.DestinationStationId,
|
DestinationStationId = request.DestinationStationId,
|
||||||
ItemId = request.ItemId,
|
ItemId = request.ItemId,
|
||||||
NodeId = request.NodeId,
|
AnchorId = request.AnchorId,
|
||||||
ConstructionSiteId = request.ConstructionSiteId,
|
ConstructionSiteId = request.ConstructionSiteId,
|
||||||
ModuleId = request.ModuleId,
|
ModuleId = request.ModuleId,
|
||||||
WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? 0f),
|
WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? 0f),
|
||||||
@@ -805,7 +805,7 @@ internal sealed class PlayerFactionService
|
|||||||
directive.SourceStationId = request.HomeStationId;
|
directive.SourceStationId = request.HomeStationId;
|
||||||
directive.DestinationStationId = null;
|
directive.DestinationStationId = null;
|
||||||
directive.ItemId = request.ItemId;
|
directive.ItemId = request.ItemId;
|
||||||
directive.PreferredNodeId = request.PreferredNodeId;
|
directive.PreferredAnchorId = request.PreferredAnchorId;
|
||||||
directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
||||||
directive.PreferredModuleId = request.PreferredModuleId;
|
directive.PreferredModuleId = request.PreferredModuleId;
|
||||||
directive.Priority = 100;
|
directive.Priority = 100;
|
||||||
@@ -831,7 +831,7 @@ internal sealed class PlayerFactionService
|
|||||||
SourceStationId = template.SourceStationId,
|
SourceStationId = template.SourceStationId,
|
||||||
DestinationStationId = template.DestinationStationId,
|
DestinationStationId = template.DestinationStationId,
|
||||||
ItemId = template.ItemId,
|
ItemId = template.ItemId,
|
||||||
NodeId = template.NodeId,
|
AnchorId = template.AnchorId,
|
||||||
ConstructionSiteId = template.ConstructionSiteId,
|
ConstructionSiteId = template.ConstructionSiteId,
|
||||||
ModuleId = template.ModuleId,
|
ModuleId = template.ModuleId,
|
||||||
WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
|
WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
|
||||||
@@ -1418,7 +1418,7 @@ internal sealed class PlayerFactionService
|
|||||||
AreaSystemId = directive?.TargetSystemId ?? directive?.HomeSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId,
|
AreaSystemId = directive?.TargetSystemId ?? directive?.HomeSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId,
|
||||||
TargetEntityId = directive?.TargetEntityId,
|
TargetEntityId = directive?.TargetEntityId,
|
||||||
ItemId = directive?.ItemId ?? automation?.PreferredItemId ?? ship.DefaultBehavior.ItemId,
|
ItemId = directive?.ItemId ?? automation?.PreferredItemId ?? ship.DefaultBehavior.ItemId,
|
||||||
PreferredNodeId = directive?.PreferredNodeId ?? ship.DefaultBehavior.PreferredNodeId,
|
PreferredAnchorId = directive?.PreferredAnchorId ?? ship.DefaultBehavior.PreferredAnchorId,
|
||||||
PreferredConstructionSiteId = directive?.PreferredConstructionSiteId ?? ship.DefaultBehavior.PreferredConstructionSiteId,
|
PreferredConstructionSiteId = directive?.PreferredConstructionSiteId ?? ship.DefaultBehavior.PreferredConstructionSiteId,
|
||||||
PreferredModuleId = directive?.PreferredModuleId ?? ship.DefaultBehavior.PreferredModuleId,
|
PreferredModuleId = directive?.PreferredModuleId ?? ship.DefaultBehavior.PreferredModuleId,
|
||||||
TargetPosition = directive?.TargetPosition,
|
TargetPosition = directive?.TargetPosition,
|
||||||
@@ -1461,7 +1461,7 @@ internal sealed class PlayerFactionService
|
|||||||
SourceStationId = directive.SourceStationId ?? directive.HomeStationId,
|
SourceStationId = directive.SourceStationId ?? directive.HomeStationId,
|
||||||
DestinationStationId = directive.DestinationStationId,
|
DestinationStationId = directive.DestinationStationId,
|
||||||
ItemId = directive.ItemId,
|
ItemId = directive.ItemId,
|
||||||
NodeId = directive.PreferredNodeId,
|
AnchorId = directive.PreferredAnchorId,
|
||||||
ConstructionSiteId = directive.PreferredConstructionSiteId,
|
ConstructionSiteId = directive.PreferredConstructionSiteId,
|
||||||
ModuleId = directive.PreferredModuleId,
|
ModuleId = directive.PreferredModuleId,
|
||||||
WaitSeconds = directive.WaitSeconds,
|
WaitSeconds = directive.WaitSeconds,
|
||||||
@@ -1525,7 +1525,7 @@ internal sealed class PlayerFactionService
|
|||||||
target.AreaSystemId = source.AreaSystemId;
|
target.AreaSystemId = source.AreaSystemId;
|
||||||
target.TargetEntityId = source.TargetEntityId;
|
target.TargetEntityId = source.TargetEntityId;
|
||||||
target.ItemId = source.ItemId;
|
target.ItemId = source.ItemId;
|
||||||
target.PreferredNodeId = source.PreferredNodeId;
|
target.PreferredAnchorId = source.PreferredAnchorId;
|
||||||
target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
|
target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
|
||||||
target.PreferredModuleId = source.PreferredModuleId;
|
target.PreferredModuleId = source.PreferredModuleId;
|
||||||
target.TargetPosition = source.TargetPosition;
|
target.TargetPosition = source.TargetPosition;
|
||||||
@@ -1546,7 +1546,7 @@ internal sealed class PlayerFactionService
|
|||||||
&& string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
|
&& string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
|
&& string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.PreferredNodeId, right.PreferredNodeId, StringComparison.Ordinal)
|
&& string.Equals(left.PreferredAnchorId, right.PreferredAnchorId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
|
&& string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
|
&& string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
|
||||||
&& Nullable.Equals(left.TargetPosition, right.TargetPosition)
|
&& Nullable.Equals(left.TargetPosition, right.TargetPosition)
|
||||||
@@ -1567,7 +1567,7 @@ internal sealed class PlayerFactionService
|
|||||||
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
|
&& string.Equals(left.AnchorId, right.AnchorId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
||||||
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
||||||
@@ -1589,7 +1589,7 @@ internal sealed class PlayerFactionService
|
|||||||
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
&& string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
|
&& string.Equals(left.AnchorId, right.AnchorId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
&& string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
&& string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
|
||||||
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
||||||
@@ -1634,7 +1634,7 @@ internal sealed class PlayerFactionService
|
|||||||
SourceStationId = template.SourceStationId,
|
SourceStationId = template.SourceStationId,
|
||||||
DestinationStationId = template.DestinationStationId,
|
DestinationStationId = template.DestinationStationId,
|
||||||
ItemId = template.ItemId,
|
ItemId = template.ItemId,
|
||||||
NodeId = template.NodeId,
|
AnchorId = template.AnchorId,
|
||||||
ConstructionSiteId = template.ConstructionSiteId,
|
ConstructionSiteId = template.ConstructionSiteId,
|
||||||
ModuleId = template.ModuleId,
|
ModuleId = template.ModuleId,
|
||||||
WaitSeconds = template.WaitSeconds,
|
WaitSeconds = template.WaitSeconds,
|
||||||
|
|||||||
@@ -22,5 +22,8 @@ public sealed class PlayerStateStore : IPlayerStateStore
|
|||||||
public IReadOnlyCollection<PlayerFactionRuntime> GetPlayerFactions() =>
|
public IReadOnlyCollection<PlayerFactionRuntime> GetPlayerFactions() =>
|
||||||
_playerFactions.Values.ToList();
|
_playerFactions.Values.ToList();
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, PlayerFactionRuntime> GetPlayerFactionsByPlayerId() =>
|
||||||
|
new Dictionary<string, PlayerFactionRuntime>(_playerFactions, StringComparer.Ordinal);
|
||||||
|
|
||||||
public void Clear() => _playerFactions.Clear();
|
public void Clear() => _playerFactions.Clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public enum SpatialNodeKind
|
|||||||
Planet,
|
Planet,
|
||||||
Moon,
|
Moon,
|
||||||
LagrangePoint,
|
LagrangePoint,
|
||||||
|
ResourceNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WorkStatus
|
public enum WorkStatus
|
||||||
@@ -286,6 +287,7 @@ public static class SimulationEnumMappings
|
|||||||
SpatialNodeKind.Planet => "planet",
|
SpatialNodeKind.Planet => "planet",
|
||||||
SpatialNodeKind.Moon => "moon",
|
SpatialNodeKind.Moon => "moon",
|
||||||
SpatialNodeKind.LagrangePoint => "lagrange-point",
|
SpatialNodeKind.LagrangePoint => "lagrange-point",
|
||||||
|
SpatialNodeKind.ResourceNode => "resource-node",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ public sealed partial class ShipAiService
|
|||||||
TargetSystemId = opportunity.Node.SystemId,
|
TargetSystemId = opportunity.Node.SystemId,
|
||||||
DestinationStationId = opportunity.DropOffStation.Id,
|
DestinationStationId = opportunity.DropOffStation.Id,
|
||||||
ItemId = opportunity.Node.ItemId,
|
ItemId = opportunity.Node.ItemId,
|
||||||
NodeId = opportunity.Node.Id,
|
AnchorId = opportunity.Node.AnchorId,
|
||||||
MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
|
MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
|
||||||
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
|
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
|
||||||
};
|
};
|
||||||
@@ -509,7 +509,7 @@ public sealed partial class ShipAiService
|
|||||||
SourceStationId = template.SourceStationId,
|
SourceStationId = template.SourceStationId,
|
||||||
DestinationStationId = template.DestinationStationId,
|
DestinationStationId = template.DestinationStationId,
|
||||||
ItemId = template.ItemId,
|
ItemId = template.ItemId,
|
||||||
NodeId = template.NodeId,
|
AnchorId = template.AnchorId,
|
||||||
ConstructionSiteId = template.ConstructionSiteId,
|
ConstructionSiteId = template.ConstructionSiteId,
|
||||||
ModuleId = template.ModuleId,
|
ModuleId = template.ModuleId,
|
||||||
WaitSeconds = template.WaitSeconds,
|
WaitSeconds = template.WaitSeconds,
|
||||||
@@ -561,7 +561,7 @@ public sealed partial class ShipAiService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = SelectLocalMiningNode(world, ship, systemId, itemId);
|
var node = SelectLocalMiningNode(world, ship, systemId, itemId, ship.DefaultBehavior.PreferredAnchorId);
|
||||||
if (node is null)
|
if (node is null)
|
||||||
{
|
{
|
||||||
ship.LastAccessFailureReason = "no-mineable-node";
|
ship.LastAccessFailureReason = "no-mineable-node";
|
||||||
@@ -578,8 +578,9 @@ public sealed partial class ShipAiService
|
|||||||
Priority = 0,
|
Priority = 0,
|
||||||
InterruptCurrentPlan = false,
|
InterruptCurrentPlan = false,
|
||||||
Label = $"Mine {itemId} in {systemId}",
|
Label = $"Mine {itemId} in {systemId}",
|
||||||
|
TargetEntityId = node.Id,
|
||||||
TargetSystemId = node.SystemId,
|
TargetSystemId = node.SystemId,
|
||||||
NodeId = node.Id,
|
AnchorId = node.AnchorId,
|
||||||
ItemId = node.ItemId,
|
ItemId = node.ItemId,
|
||||||
WaitSeconds = 0f,
|
WaitSeconds = 0f,
|
||||||
Radius = 0f,
|
Radius = 0f,
|
||||||
@@ -601,7 +602,7 @@ public sealed partial class ShipAiService
|
|||||||
&& left.TargetPosition == right.TargetPosition
|
&& left.TargetPosition == right.TargetPosition
|
||||||
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
&& string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||||
&& string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
|
&& string.Equals(left.AnchorId, right.AnchorId, StringComparison.Ordinal)
|
||||||
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
&& left.WaitSeconds.Equals(right.WaitSeconds)
|
||||||
&& left.Radius.Equals(right.Radius)
|
&& left.Radius.Equals(right.Radius)
|
||||||
&& left.MaxSystemRange == right.MaxSystemRange
|
&& left.MaxSystemRange == right.MaxSystemRange
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public sealed partial class ShipAiService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var targetPosition = ResolveCurrentTargetPosition(world, subTask);
|
var targetPosition = ResolveCurrentTargetPosition(world, subTask);
|
||||||
var targetCelestial = ResolveTravelTargetCelestial(world, subTask, targetPosition);
|
var targetAnchor = ResolveTravelTargetAnchor(world, subTask, targetPosition);
|
||||||
ship.TargetPosition = targetPosition;
|
ship.TargetPosition = targetPosition;
|
||||||
|
|
||||||
if (ship.SystemId != subTask.TargetSystemId)
|
if (ship.SystemId != subTask.TargetSystemId)
|
||||||
@@ -81,32 +81,33 @@ public sealed partial class ShipAiService
|
|||||||
return SubTaskOutcome.Failed;
|
return SubTaskOutcome.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
var destinationEntryCelestial = ResolveSystemEntryCelestial(world, subTask.TargetSystemId);
|
var destinationEntryAnchor = ResolveSystemEntryAnchor(world, subTask.TargetSystemId) ?? targetAnchor;
|
||||||
var destinationEntryPosition = destinationEntryCelestial?.Position ?? targetPosition;
|
var destinationEntryPosition = destinationEntryAnchor?.Position ?? targetPosition;
|
||||||
return UpdateFtlTransit(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, destinationEntryPosition, destinationEntryCelestial, completeOnArrival, targetPosition);
|
return UpdateFtlTransit(world, ship, subTask, deltaSeconds, subTask.TargetSystemId, destinationEntryPosition, destinationEntryAnchor, completeOnArrival, targetPosition, targetAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentCelestial = ResolveCurrentCelestial(world, ship);
|
var currentAnchor = ResolveCurrentAnchor(world, ship);
|
||||||
if (targetCelestial is not null
|
if (targetAnchor is not null
|
||||||
&& currentCelestial is not null
|
&& currentAnchor is not null
|
||||||
&& !string.Equals(currentCelestial.Id, targetCelestial.Id, StringComparison.Ordinal))
|
&& !string.Equals(currentAnchor.Id, targetAnchor.Id, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
if (!CanWarp(ship.Definition))
|
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
|
if (targetAnchor is not null
|
||||||
&& ship.Position.DistanceTo(targetPosition) > WarpEngageDistanceKilometers
|
&& currentAnchor is not null
|
||||||
|
&& !string.Equals(currentAnchor.Id, targetAnchor.Id, StringComparison.Ordinal)
|
||||||
&& CanWarp(ship.Definition))
|
&& 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)
|
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)
|
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))
|
if (node is null || !CanExtractNode(ship, node, world))
|
||||||
{
|
{
|
||||||
subTask.BlockingReason = "node-missing";
|
subTask.BlockingReason = "node-missing";
|
||||||
@@ -165,9 +166,28 @@ public sealed partial class ShipAiService
|
|||||||
return SubTaskOutcome.Failed;
|
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;
|
ship.TargetPosition = targetPosition;
|
||||||
if (ship.Position.DistanceTo(targetPosition) > MathF.Max(subTask.Threshold, 8f))
|
if (distanceToTarget > approachThreshold && !effectivelyAtDeposit)
|
||||||
{
|
{
|
||||||
ship.State = ShipState.MiningApproach;
|
ship.State = ShipState.MiningApproach;
|
||||||
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
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 remainingCapacity = MathF.Max(0f, ship.Definition.GetTotalCargoCapacity() - cargoAmount);
|
||||||
var mined = MathF.Min(balance.MiningRate * GetSkillFactor(ship.Skills.Mining), remainingCapacity);
|
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)
|
if (mined <= 0.01f)
|
||||||
{
|
{
|
||||||
return SubTaskOutcome.Completed;
|
return SubTaskOutcome.Completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInventory(ship.Inventory, node.ItemId, mined);
|
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)
|
if (GetShipCargoAmount(ship) >= ship.Definition.GetTotalCargoCapacity() - 0.01f || node.OreRemaining <= 0.01f)
|
||||||
{
|
{
|
||||||
return SubTaskOutcome.Completed;
|
return SubTaskOutcome.Completed;
|
||||||
@@ -605,15 +626,22 @@ public sealed partial class ShipAiService
|
|||||||
float deltaSeconds,
|
float deltaSeconds,
|
||||||
string targetSystemId,
|
string targetSystemId,
|
||||||
Vector3 targetPosition,
|
Vector3 targetPosition,
|
||||||
CelestialRuntime? targetCelestial,
|
AnchorRuntime? currentAnchor,
|
||||||
|
AnchorRuntime? targetAnchor,
|
||||||
bool completeOnArrival)
|
bool completeOnArrival)
|
||||||
{
|
{
|
||||||
var distance = ship.Position.DistanceTo(targetPosition);
|
var distance = ship.Position.DistanceTo(targetPosition);
|
||||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||||
ship.SpatialState.Transit = null;
|
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);
|
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))
|
if (distance <= MathF.Max(subTask.Threshold, balance.ArrivalThreshold))
|
||||||
{
|
{
|
||||||
@@ -621,13 +649,26 @@ public sealed partial class ShipAiService
|
|||||||
ship.TargetPosition = targetPosition;
|
ship.TargetPosition = targetPosition;
|
||||||
ship.SystemId = targetSystemId;
|
ship.SystemId = targetSystemId;
|
||||||
ship.SpatialState.CurrentSystemId = 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;
|
ship.State = ShipState.Arriving;
|
||||||
return completeOnArrival ? SubTaskOutcome.Completed : SubTaskOutcome.Active;
|
return completeOnArrival ? SubTaskOutcome.Completed : SubTaskOutcome.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
ship.State = ShipState.LocalFlight;
|
ship.State = ShipState.LocalFlight;
|
||||||
|
ship.SpatialState.CurrentAnchorId = currentAnchor?.Id;
|
||||||
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
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;
|
return SubTaskOutcome.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,18 +678,24 @@ public sealed partial class ShipAiService
|
|||||||
ShipSubTaskRuntime subTask,
|
ShipSubTaskRuntime subTask,
|
||||||
float deltaSeconds,
|
float deltaSeconds,
|
||||||
Vector3 targetPosition,
|
Vector3 targetPosition,
|
||||||
CelestialRuntime targetCelestial,
|
AnchorRuntime currentAnchor,
|
||||||
|
AnchorRuntime targetAnchor,
|
||||||
bool completeOnArrival)
|
bool completeOnArrival)
|
||||||
{
|
{
|
||||||
var transit = ship.SpatialState.Transit;
|
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
|
transit = new ShipTransitRuntime
|
||||||
{
|
{
|
||||||
Regime = MovementRegimeKind.Warp,
|
Regime = MovementRegimeKind.Warp,
|
||||||
OriginNodeId = ship.SpatialState.CurrentCelestialId,
|
OriginAnchorId = currentAnchor.Id,
|
||||||
DestinationNodeId = targetCelestial.Id,
|
DestinationAnchorId = targetAnchor.Id,
|
||||||
StartedAtUtc = world.GeneratedAtUtc,
|
StartedAtUtc = world.GeneratedAtUtc,
|
||||||
|
ArrivalDueAtUtc = world.GeneratedAtUtc.AddSeconds(initialSpoolDuration + initialTravelDuration),
|
||||||
};
|
};
|
||||||
ship.SpatialState.Transit = transit;
|
ship.SpatialState.Transit = transit;
|
||||||
subTask.ElapsedSeconds = 0f;
|
subTask.ElapsedSeconds = 0f;
|
||||||
@@ -656,33 +703,47 @@ public sealed partial class ShipAiService
|
|||||||
|
|
||||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.SystemSpace;
|
ship.SpatialState.SpaceLayer = SpaceLayerKind.SystemSpace;
|
||||||
ship.SpatialState.MovementRegime = MovementRegimeKind.Warp;
|
ship.SpatialState.MovementRegime = MovementRegimeKind.Warp;
|
||||||
ship.SpatialState.CurrentCelestialId = null;
|
ship.SpatialState.CurrentAnchorId = null;
|
||||||
ship.SpatialState.DestinationNodeId = targetCelestial.Id;
|
ship.SpatialState.DestinationAnchorId = targetAnchor.Id;
|
||||||
|
|
||||||
var spoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
|
var spoolDurationSeconds = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
|
||||||
if (ship.State != ShipState.Warping)
|
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;
|
ship.State = ShipState.SpoolingWarp;
|
||||||
if (!AdvanceTimedSubTask(subTask, deltaSeconds, spoolDuration))
|
ship.Position = Vector3.Zero;
|
||||||
{
|
ship.TargetPosition = Vector3.Zero;
|
||||||
return SubTaskOutcome.Active;
|
ship.SpatialState.SystemPosition = originPosition;
|
||||||
}
|
transit.Progress = Math.Clamp(elapsedSeconds / totalDuration, 0f, 1f);
|
||||||
|
subTask.Progress = transit.Progress;
|
||||||
ship.State = ShipState.Warping;
|
return SubTaskOutcome.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null
|
ship.State = ShipState.Warping;
|
||||||
? ship.Position.DistanceTo(targetPosition)
|
var warpTravelDuration = MathF.Max(0.001f, totalDuration - spoolDurationSeconds);
|
||||||
: (world.Celestials.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition)));
|
var travelElapsed = Math.Clamp(elapsedSeconds - spoolDurationSeconds, 0f, warpTravelDuration);
|
||||||
ship.Position = ship.Position.MoveToward(targetPosition, GetWarpTravelSpeed(ship) * deltaSeconds);
|
var travelProgress = Math.Clamp(travelElapsed / warpTravelDuration, 0f, 1f);
|
||||||
transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance));
|
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;
|
subTask.Progress = transit.Progress;
|
||||||
if (ship.Position.DistanceTo(targetPosition) > 18f)
|
if (elapsedSeconds < totalDuration - 0.001f)
|
||||||
{
|
{
|
||||||
return SubTaskOutcome.Active;
|
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(
|
private SubTaskOutcome UpdateFtlTransit(
|
||||||
@@ -692,20 +753,24 @@ public sealed partial class ShipAiService
|
|||||||
float deltaSeconds,
|
float deltaSeconds,
|
||||||
string targetSystemId,
|
string targetSystemId,
|
||||||
Vector3 entryPosition,
|
Vector3 entryPosition,
|
||||||
CelestialRuntime? targetCelestial,
|
AnchorRuntime? entryAnchor,
|
||||||
bool completeOnArrival,
|
bool completeOnArrival,
|
||||||
Vector3 finalTargetPosition)
|
Vector3 finalTargetPosition,
|
||||||
|
AnchorRuntime? finalTargetAnchor)
|
||||||
{
|
{
|
||||||
var destinationNodeId = targetCelestial?.Id;
|
var destinationAnchorId = entryAnchor?.Id;
|
||||||
var transit = ship.SpatialState.Transit;
|
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
|
transit = new ShipTransitRuntime
|
||||||
{
|
{
|
||||||
Regime = MovementRegimeKind.FtlTransit,
|
Regime = MovementRegimeKind.FtlTransit,
|
||||||
OriginNodeId = ship.SpatialState.CurrentCelestialId,
|
OriginAnchorId = ship.SpatialState.CurrentAnchorId,
|
||||||
DestinationNodeId = destinationNodeId,
|
DestinationAnchorId = destinationAnchorId,
|
||||||
StartedAtUtc = world.GeneratedAtUtc,
|
StartedAtUtc = world.GeneratedAtUtc,
|
||||||
|
ArrivalDueAtUtc = world.GeneratedAtUtc.AddSeconds(initialSpoolDuration + initialTravelDuration),
|
||||||
};
|
};
|
||||||
ship.SpatialState.Transit = transit;
|
ship.SpatialState.Transit = transit;
|
||||||
subTask.ElapsedSeconds = 0f;
|
subTask.ElapsedSeconds = 0f;
|
||||||
@@ -713,39 +778,32 @@ public sealed partial class ShipAiService
|
|||||||
|
|
||||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.GalaxySpace;
|
ship.SpatialState.SpaceLayer = SpaceLayerKind.GalaxySpace;
|
||||||
ship.SpatialState.MovementRegime = MovementRegimeKind.FtlTransit;
|
ship.SpatialState.MovementRegime = MovementRegimeKind.FtlTransit;
|
||||||
ship.SpatialState.CurrentCelestialId = null;
|
ship.SpatialState.CurrentAnchorId = null;
|
||||||
ship.SpatialState.DestinationNodeId = destinationNodeId;
|
ship.SpatialState.DestinationAnchorId = destinationAnchorId;
|
||||||
|
|
||||||
if (ship.State != ShipState.Ftl)
|
var spoolDurationSeconds = MathF.Max(ship.Definition.SpoolTime, 0.1f);
|
||||||
{
|
var startedAtUtc = transit.StartedAtUtc ?? world.GeneratedAtUtc;
|
||||||
ship.State = ShipState.SpoolingFtl;
|
var arrivalDueAtUtc = transit.ArrivalDueAtUtc ?? world.GeneratedAtUtc;
|
||||||
if (!AdvanceTimedSubTask(subTask, deltaSeconds, MathF.Max(ship.Definition.SpoolTime, 0.1f)))
|
var totalDuration = MathF.Max(0.1f, (float)(arrivalDueAtUtc - startedAtUtc).TotalSeconds);
|
||||||
{
|
var elapsedSeconds = Math.Clamp((float)(world.GeneratedAtUtc - startedAtUtc).TotalSeconds, 0f, totalDuration);
|
||||||
return SubTaskOutcome.Active;
|
ship.State = elapsedSeconds < spoolDurationSeconds ? ShipState.SpoolingFtl : ShipState.Ftl;
|
||||||
}
|
transit.Progress = Math.Clamp(elapsedSeconds / totalDuration, 0f, 1f);
|
||||||
|
|
||||||
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));
|
|
||||||
subTask.Progress = transit.Progress;
|
subTask.Progress = transit.Progress;
|
||||||
if (transit.Progress < 0.999f)
|
if (elapsedSeconds < totalDuration - 0.001f)
|
||||||
{
|
{
|
||||||
return SubTaskOutcome.Active;
|
return SubTaskOutcome.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
ship.Position = entryPosition;
|
ship.Position = Vector3.Zero;
|
||||||
ship.TargetPosition = finalTargetPosition;
|
ship.TargetPosition = finalTargetPosition;
|
||||||
ship.SystemId = targetSystemId;
|
ship.SystemId = targetSystemId;
|
||||||
ship.SpatialState.CurrentSystemId = targetSystemId;
|
ship.SpatialState.CurrentSystemId = targetSystemId;
|
||||||
ship.SpatialState.Transit = null;
|
ship.SpatialState.Transit = null;
|
||||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||||
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
|
ship.SpatialState.CurrentAnchorId = entryAnchor?.Id;
|
||||||
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
|
ship.SpatialState.DestinationAnchorId = finalTargetAnchor?.Id ?? entryAnchor?.Id;
|
||||||
|
ship.SpatialState.SystemPosition = entryPosition;
|
||||||
ship.State = ShipState.Arriving;
|
ship.State = ShipState.Arriving;
|
||||||
|
|
||||||
// Cross-system travel is only complete once the ship finishes the
|
// Cross-system travel is only complete once the ship finishes the
|
||||||
@@ -753,7 +811,7 @@ public sealed partial class ShipAiService
|
|||||||
return SubTaskOutcome.Active;
|
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.Position = targetPosition;
|
||||||
ship.TargetPosition = targetPosition;
|
ship.TargetPosition = targetPosition;
|
||||||
@@ -762,8 +820,14 @@ public sealed partial class ShipAiService
|
|||||||
ship.SpatialState.Transit = null;
|
ship.SpatialState.Transit = null;
|
||||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||||
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
|
ship.SpatialState.CurrentAnchorId = targetAnchor?.Id;
|
||||||
ship.SpatialState.DestinationNodeId = targetCelestial?.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;
|
ship.State = ShipState.Arriving;
|
||||||
return completeOnArrival ? SubTaskOutcome.Completed : SubTaskOutcome.Active;
|
return completeOnArrival ? SubTaskOutcome.Completed : SubTaskOutcome.Active;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ public sealed partial class ShipAiService
|
|||||||
{
|
{
|
||||||
private static Vector3 ResolveCurrentTargetPosition(SimulationWorld world, ShipSubTaskRuntime subTask)
|
private static Vector3 ResolveCurrentTargetPosition(SimulationWorld world, ShipSubTaskRuntime subTask)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(subTask.TargetAnchorId) && ResolveAnchor(world, subTask.TargetAnchorId) is not null)
|
||||||
|
{
|
||||||
|
return subTask.TargetPosition ?? Vector3.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(subTask.TargetEntityId))
|
if (!string.IsNullOrWhiteSpace(subTask.TargetEntityId))
|
||||||
{
|
{
|
||||||
var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
||||||
@@ -44,15 +49,20 @@ public sealed partial class ShipAiService
|
|||||||
if (!string.IsNullOrWhiteSpace(subTask.TargetEntityId))
|
if (!string.IsNullOrWhiteSpace(subTask.TargetEntityId))
|
||||||
{
|
{
|
||||||
var station = ResolveStation(world, subTask.TargetEntityId);
|
var station = ResolveStation(world, subTask.TargetEntityId);
|
||||||
if (station?.CelestialId is not null)
|
if (station?.AnchorId is not null)
|
||||||
{
|
{
|
||||||
return world.Celestials.FirstOrDefault(candidate => candidate.Id == station.CelestialId);
|
return ResolveAnchorBackedCelestial(world, station.AnchorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
||||||
if (site?.CelestialId is not null)
|
if (site?.AnchorId is not null)
|
||||||
{
|
{
|
||||||
return world.Celestials.FirstOrDefault(candidate => candidate.Id == site.CelestialId);
|
return ResolveAnchorBackedCelestial(world, site.AnchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResolveAnchor(world, subTask.TargetEntityId) is { } anchorBackedCelestialTarget)
|
||||||
|
{
|
||||||
|
return ResolveAnchorBackedCelestial(world, anchorBackedCelestialTarget.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
var celestial = world.Celestials.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
var celestial = world.Celestials.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
||||||
@@ -76,25 +86,145 @@ public sealed partial class ShipAiService
|
|||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AnchorRuntime? ResolveTravelTargetAnchor(SimulationWorld world, ShipSubTaskRuntime subTask, Vector3 targetPosition)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(subTask.TargetAnchorId) && ResolveAnchor(world, subTask.TargetAnchorId) is { } explicitTargetAnchor)
|
||||||
|
{
|
||||||
|
return explicitTargetAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(subTask.TargetEntityId))
|
||||||
|
{
|
||||||
|
var station = ResolveStation(world, subTask.TargetEntityId);
|
||||||
|
if (station?.AnchorId is not null)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, station.AnchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId);
|
||||||
|
if (site?.AnchorId is not null)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, site.AnchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = ResolveNode(world, subTask.TargetEntityId);
|
||||||
|
if (node is not null)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, node.AnchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResolveAnchor(world, subTask.TargetEntityId) is { } directAnchor)
|
||||||
|
{
|
||||||
|
return directAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world.Celestials.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId) is { } celestial)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, celestial.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world.Wrecks.FirstOrDefault(candidate => candidate.Id == subTask.TargetEntityId) is { } wreck)
|
||||||
|
{
|
||||||
|
return world.Anchors
|
||||||
|
.Where(candidate => candidate.SystemId == wreck.SystemId)
|
||||||
|
.OrderBy(candidate => candidate.Position.DistanceTo(wreck.Position))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return world.Anchors
|
||||||
|
.Where(candidate => subTask.TargetSystemId is null || candidate.SystemId == subTask.TargetSystemId)
|
||||||
|
.OrderBy(candidate => candidate.Position.DistanceTo(targetPosition))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnchorRuntime? ResolveCurrentAnchor(SimulationWorld world, ShipRuntime ship)
|
||||||
|
{
|
||||||
|
if (ship.SpatialState.CurrentAnchorId is not null && ResolveAnchor(world, ship.SpatialState.CurrentAnchorId) is { } explicitAnchor)
|
||||||
|
{
|
||||||
|
return explicitAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ship.DockedStationId is not null && ResolveStation(world, ship.DockedStationId)?.AnchorId is { } dockAnchorId)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, dockAnchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return world.Anchors
|
||||||
|
.Where(candidate => candidate.SystemId == ship.SystemId)
|
||||||
|
.OrderBy(candidate => candidate.Position.DistanceTo(ResolveShipSystemPosition(world, ship)))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private static CelestialRuntime? ResolveCurrentCelestial(SimulationWorld world, ShipRuntime ship)
|
private static CelestialRuntime? ResolveCurrentCelestial(SimulationWorld world, ShipRuntime ship)
|
||||||
{
|
{
|
||||||
if (ship.SpatialState.CurrentCelestialId is not null)
|
if (ship.SpatialState.CurrentAnchorId is not null && ResolveAnchorBackedCelestial(world, ship.SpatialState.CurrentAnchorId) is { } currentAnchorCelestial)
|
||||||
{
|
{
|
||||||
return world.Celestials.FirstOrDefault(candidate => candidate.Id == ship.SpatialState.CurrentCelestialId);
|
return currentAnchorCelestial;
|
||||||
}
|
}
|
||||||
|
|
||||||
return world.Celestials
|
return world.Celestials
|
||||||
.Where(candidate => candidate.SystemId == ship.SystemId)
|
.Where(candidate => candidate.SystemId == ship.SystemId)
|
||||||
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
.OrderBy(candidate => candidate.Position.DistanceTo(ResolveShipSystemPosition(world, ship)))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CelestialRuntime? ResolveSystemEntryCelestial(SimulationWorld world, string systemId) =>
|
private static CelestialRuntime? ResolveSystemEntryCelestial(SimulationWorld world, string systemId) =>
|
||||||
world.Celestials.FirstOrDefault(candidate => candidate.SystemId == systemId && candidate.Kind == SpatialNodeKind.Star);
|
world.Celestials.FirstOrDefault(candidate => candidate.SystemId == systemId && candidate.Kind == SpatialNodeKind.Star);
|
||||||
|
|
||||||
|
private static AnchorRuntime? ResolveSystemEntryAnchor(SimulationWorld world, string systemId) =>
|
||||||
|
world.Anchors.FirstOrDefault(candidate => candidate.SystemId == systemId && candidate.Kind == SpatialNodeKind.Star);
|
||||||
|
|
||||||
private static Vector3 ResolveSystemGalaxyPosition(SimulationWorld world, string systemId) =>
|
private static Vector3 ResolveSystemGalaxyPosition(SimulationWorld world, string systemId) =>
|
||||||
world.Systems.FirstOrDefault(candidate => candidate.Definition.Id == systemId)?.Position ?? Vector3.Zero;
|
world.Systems.FirstOrDefault(candidate => candidate.Definition.Id == systemId)?.Position ?? Vector3.Zero;
|
||||||
|
|
||||||
|
private static Vector3 ResolveAnchorPosition(SimulationWorld world, string? anchorId, Vector3 fallbackPosition) =>
|
||||||
|
ResolveAnchor(world, anchorId)?.Position ?? fallbackPosition;
|
||||||
|
|
||||||
|
private static Vector3 ResolveStationSystemPosition(SimulationWorld world, StationRuntime station)
|
||||||
|
{
|
||||||
|
if (station.AnchorId is not null && ResolveAnchor(world, station.AnchorId) is { } anchor)
|
||||||
|
{
|
||||||
|
return new Vector3(
|
||||||
|
anchor.Position.X + station.Position.X,
|
||||||
|
anchor.Position.Y + station.Position.Y,
|
||||||
|
anchor.Position.Z + station.Position.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return station.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ResolveNodeSystemPosition(SimulationWorld world, ResourceNodeRuntime node)
|
||||||
|
{
|
||||||
|
if (ResolveAnchor(world, node.AnchorId) is { } anchor)
|
||||||
|
{
|
||||||
|
return new Vector3(
|
||||||
|
anchor.Position.X + node.Position.X,
|
||||||
|
anchor.Position.Y + node.Position.Y,
|
||||||
|
anchor.Position.Z + node.Position.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 ResolveShipSystemPosition(SimulationWorld world, ShipRuntime ship)
|
||||||
|
{
|
||||||
|
if (ship.SpatialState.SystemPosition is { } systemPosition)
|
||||||
|
{
|
||||||
|
return systemPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResolveCurrentAnchor(world, ship) is { } anchor)
|
||||||
|
{
|
||||||
|
return new Vector3(
|
||||||
|
anchor.Position.X + ship.Position.X,
|
||||||
|
anchor.Position.Y + ship.Position.Y,
|
||||||
|
anchor.Position.Z + ship.Position.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ship.Position;
|
||||||
|
}
|
||||||
|
|
||||||
private static float GetLocalTravelSpeed(ShipRuntime ship) =>
|
private static float GetLocalTravelSpeed(ShipRuntime ship) =>
|
||||||
SimulationUnits.MetersPerSecondToKilometersPerSecond(ship.Definition.Speed) * GetSkillFactor(ship.Skills.Navigation);
|
SimulationUnits.MetersPerSecondToKilometersPerSecond(ship.Definition.Speed) * GetSkillFactor(ship.Skills.Navigation);
|
||||||
|
|
||||||
@@ -183,6 +313,7 @@ public sealed partial class ShipAiService
|
|||||||
{
|
{
|
||||||
var policy = ResolvePolicy(world, ship.PolicySetId);
|
var policy = ResolvePolicy(world, ship.PolicySetId);
|
||||||
var preferredItemId = assignment?.ItemId ?? ship.DefaultBehavior.ItemId;
|
var preferredItemId = assignment?.ItemId ?? ship.DefaultBehavior.ItemId;
|
||||||
|
var preferredAnchorId = ship.DefaultBehavior.PreferredAnchorId;
|
||||||
var rangeBudget = ResolveBehaviorSystemRange(world, ship, behaviorKind, ship.DefaultBehavior.MaxSystemRange);
|
var rangeBudget = ResolveBehaviorSystemRange(world, ship, behaviorKind, ship.DefaultBehavior.MaxSystemRange);
|
||||||
var effectiveMiningSkill = GetEffectiveSkillLevel(world, ship, skills => skills.Mining, skills => skills.Coordination);
|
var effectiveMiningSkill = GetEffectiveSkillLevel(world, ship, skills => skills.Mining, skills => skills.Coordination);
|
||||||
string? deniedReason = null;
|
string? deniedReason = null;
|
||||||
@@ -194,6 +325,11 @@ public sealed partial class ShipAiService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preferredAnchorId is not null && !string.Equals(node.AnchorId, preferredAnchorId, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!TryCheckSystemAllowed(world, policy, ship.FactionId, node.SystemId, "military", out var reason))
|
if (!TryCheckSystemAllowed(world, policy, ship.FactionId, node.SystemId, "military", out var reason))
|
||||||
{
|
{
|
||||||
deniedReason ??= reason;
|
deniedReason ??= reason;
|
||||||
@@ -214,7 +350,7 @@ public sealed partial class ShipAiService
|
|||||||
+ (effectiveMiningSkill * 10f)
|
+ (effectiveMiningSkill * 10f)
|
||||||
- distancePenalty
|
- distancePenalty
|
||||||
- routeRiskPenalty
|
- routeRiskPenalty
|
||||||
- node.Position.DistanceTo(ship.Position);
|
- ResolveNodeSystemPosition(world, node).DistanceTo(ResolveShipSystemPosition(world, ship));
|
||||||
return new MiningOpportunity(node, buyer, score, $"Mine {node.ItemId} for {buyer.Label}");
|
return new MiningOpportunity(node, buyer, score, $"Mine {node.ItemId} for {buyer.Label}");
|
||||||
})
|
})
|
||||||
.OrderByDescending(candidate => candidate.Score)
|
.OrderByDescending(candidate => candidate.Score)
|
||||||
@@ -452,7 +588,7 @@ public sealed partial class ShipAiService
|
|||||||
?? homeStation;
|
?? homeStation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResourceNodeRuntime? SelectLocalMiningNode(SimulationWorld world, ShipRuntime ship, string systemId, string itemId)
|
private static ResourceNodeRuntime? SelectLocalMiningNode(SimulationWorld world, ShipRuntime ship, string systemId, string itemId, string? anchorId = null)
|
||||||
{
|
{
|
||||||
var policy = ResolvePolicy(world, ship.PolicySetId);
|
var policy = ResolvePolicy(world, ship.PolicySetId);
|
||||||
string? deniedReason = null;
|
string? deniedReason = null;
|
||||||
@@ -467,6 +603,11 @@ public sealed partial class ShipAiService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (anchorId is not null && !string.Equals(candidate.AnchorId, anchorId, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!TryCheckSystemAllowed(world, policy, ship.FactionId, candidate.SystemId, "military", out var reason))
|
if (!TryCheckSystemAllowed(world, policy, ship.FactionId, candidate.SystemId, "military", out var reason))
|
||||||
{
|
{
|
||||||
deniedReason ??= reason;
|
deniedReason ??= reason;
|
||||||
@@ -487,6 +628,54 @@ public sealed partial class ShipAiService
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ResourceDepositRuntime? ResolveResourceDeposit(SimulationWorld world, string? depositId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(depositId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var node in world.Nodes)
|
||||||
|
{
|
||||||
|
var deposit = node.Deposits.FirstOrDefault(candidate => string.Equals(candidate.Id, depositId, StringComparison.Ordinal));
|
||||||
|
if (deposit is not null)
|
||||||
|
{
|
||||||
|
return deposit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResourceDepositRuntime? SelectMiningDeposit(ResourceNodeRuntime node, string shipId)
|
||||||
|
{
|
||||||
|
return node.Deposits
|
||||||
|
.Where(candidate => candidate.OreRemaining > 0.01f)
|
||||||
|
.OrderByDescending(candidate => candidate.OreRemaining)
|
||||||
|
.ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SyncNodeOreTotals(ResourceNodeRuntime node)
|
||||||
|
{
|
||||||
|
node.OreRemaining = node.Deposits.Sum(candidate => candidate.OreRemaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnchorRuntime? ResolveMiningAnchor(SimulationWorld world, string? anchorId, string? nodeId)
|
||||||
|
{
|
||||||
|
if (anchorId is not null)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, anchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeId is not null && ResolveNode(world, nodeId) is { } node)
|
||||||
|
{
|
||||||
|
return ResolveAnchor(world, node.AnchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static StationRuntime? SelectLocalAutoMineBuyer(SimulationWorld world, ShipRuntime ship, string systemId, string itemId)
|
private static StationRuntime? SelectLocalAutoMineBuyer(SimulationWorld world, ShipRuntime ship, string systemId, string itemId)
|
||||||
{
|
{
|
||||||
var policy = ResolvePolicy(world, ship.PolicySetId);
|
var policy = ResolvePolicy(world, ship.PolicySetId);
|
||||||
@@ -686,9 +875,14 @@ public sealed partial class ShipAiService
|
|||||||
return (celestial.SystemId, celestial.Position);
|
return (celestial.SystemId, celestial.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ResolveAnchor(world, entityId) is { } anchor)
|
||||||
|
{
|
||||||
|
return (anchor.SystemId, anchor.Position);
|
||||||
|
}
|
||||||
|
|
||||||
if (world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == entityId) is { } site)
|
if (world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == entityId) is { } site)
|
||||||
{
|
{
|
||||||
var position = world.Celestials.FirstOrDefault(candidate => candidate.Id == site.CelestialId)?.Position ?? Vector3.Zero;
|
var position = ResolveAnchor(world, site.AnchorId)?.Position ?? Vector3.Zero;
|
||||||
return (site.SystemId, position);
|
return (site.SystemId, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,6 +914,16 @@ public sealed partial class ShipAiService
|
|||||||
private static StationRuntime? ResolveStation(SimulationWorld world, string? stationId) =>
|
private static StationRuntime? ResolveStation(SimulationWorld world, string? stationId) =>
|
||||||
stationId is null ? null : world.Stations.FirstOrDefault(candidate => candidate.Id == stationId);
|
stationId is null ? null : world.Stations.FirstOrDefault(candidate => candidate.Id == stationId);
|
||||||
|
|
||||||
|
private static AnchorRuntime? ResolveAnchor(SimulationWorld world, string? anchorId) =>
|
||||||
|
anchorId is null ? null : world.Anchors.FirstOrDefault(candidate => candidate.Id == anchorId);
|
||||||
|
|
||||||
|
private static CelestialRuntime? ResolveAnchorBackedCelestial(SimulationWorld world, string? anchorId)
|
||||||
|
{
|
||||||
|
var anchor = ResolveAnchor(world, anchorId);
|
||||||
|
var celestialId = SpatialBuilder.ResolveCompatibleCelestialId(anchor);
|
||||||
|
return celestialId is null ? null : world.Celestials.FirstOrDefault(candidate => candidate.Id == celestialId);
|
||||||
|
}
|
||||||
|
|
||||||
private static ResourceNodeRuntime? ResolveNode(SimulationWorld world, string? nodeId) =>
|
private static ResourceNodeRuntime? ResolveNode(SimulationWorld world, string? nodeId) =>
|
||||||
nodeId is null ? null : world.Nodes.FirstOrDefault(candidate => candidate.Id == nodeId);
|
nodeId is null ? null : world.Nodes.FirstOrDefault(candidate => candidate.Id == nodeId);
|
||||||
|
|
||||||
@@ -815,7 +1019,8 @@ public sealed partial class ShipAiService
|
|||||||
|
|
||||||
if (site?.StationId is null && site is not null)
|
if (site?.StationId is null && site is not null)
|
||||||
{
|
{
|
||||||
var anchorPosition = world.Celestials.FirstOrDefault(candidate => candidate.Id == site.CelestialId)?.Position ?? station.Position;
|
var anchorPosition = ResolveAnchor(world, site.AnchorId)?.Position
|
||||||
|
?? station.Position;
|
||||||
return GetResourceHoldPosition(anchorPosition, ship.Id, 78f);
|
return GetResourceHoldPosition(anchorPosition, ship.Id, 78f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,7 +1072,7 @@ public sealed partial class ShipAiService
|
|||||||
|
|
||||||
private static void CompleteStationFoundation(SimulationWorld world, StationRuntime supportStation, ConstructionSiteRuntime site)
|
private static void CompleteStationFoundation(SimulationWorld world, StationRuntime supportStation, ConstructionSiteRuntime site)
|
||||||
{
|
{
|
||||||
var anchor = world.Celestials.FirstOrDefault(candidate => candidate.Id == site.CelestialId);
|
var anchor = ResolveAnchor(world, site.AnchorId);
|
||||||
if (anchor is null || site.BlueprintId is null)
|
if (anchor is null || site.BlueprintId is null)
|
||||||
{
|
{
|
||||||
site.State = ConstructionSiteStateKinds.Destroyed;
|
site.State = ConstructionSiteStateKinds.Destroyed;
|
||||||
@@ -878,13 +1083,13 @@ public sealed partial class ShipAiService
|
|||||||
{
|
{
|
||||||
Id = $"station-{world.Stations.Count + 1}",
|
Id = $"station-{world.Stations.Count + 1}",
|
||||||
SystemId = site.SystemId,
|
SystemId = site.SystemId,
|
||||||
|
AnchorId = site.AnchorId,
|
||||||
Label = BuildFoundedStationLabel(site.TargetDefinitionId),
|
Label = BuildFoundedStationLabel(site.TargetDefinitionId),
|
||||||
Category = "station",
|
Category = "station",
|
||||||
Objective = DetermineFoundationObjective(site.TargetDefinitionId),
|
Objective = DetermineFoundationObjective(site.TargetDefinitionId),
|
||||||
Color = world.Factions.FirstOrDefault(candidate => candidate.Id == site.FactionId)?.Color ?? supportStation.Color,
|
Color = world.Factions.FirstOrDefault(candidate => candidate.Id == site.FactionId)?.Color ?? supportStation.Color,
|
||||||
Position = anchor.Position,
|
Position = Vector3.Zero,
|
||||||
FactionId = site.FactionId,
|
FactionId = site.FactionId,
|
||||||
CelestialId = site.CelestialId,
|
|
||||||
Health = 600f,
|
Health = 600f,
|
||||||
MaxHealth = 600f,
|
MaxHealth = 600f,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -128,11 +128,11 @@ public sealed partial class ShipAiService
|
|||||||
CreateStep("step-construction-deliver", "deliver-materials", $"Deliver materials to {site.Id}",
|
CreateStep("step-construction-deliver", "deliver-materials", $"Deliver materials to {site.Id}",
|
||||||
[
|
[
|
||||||
CreateSubTask("sub-construction-travel", ShipTaskKinds.Travel, $"Travel to support station {supportStation.Label}", supportStation.SystemId, targetPosition, supportStation.Id, MathF.Max(supportStation.Radius + 18f, 18f), 0f),
|
CreateSubTask("sub-construction-travel", ShipTaskKinds.Travel, $"Travel to support station {supportStation.Label}", supportStation.SystemId, targetPosition, supportStation.Id, MathF.Max(supportStation.Radius + 18f, 18f), 0f),
|
||||||
CreateSubTask("sub-construction-deliver", ShipTaskKinds.DeliverConstruction, $"Deliver materials to {site.Id}", site.SystemId, site.CelestialId is null ? supportStation.Position : supportStation.Position, site.Id, 12f, 0f)
|
CreateSubTask("sub-construction-deliver", ShipTaskKinds.DeliverConstruction, $"Deliver materials to {site.Id}", site.SystemId, supportStation.Position, site.Id, 12f, 0f)
|
||||||
]),
|
]),
|
||||||
CreateStep("step-construction-build", "build-site", $"Build {site.Id}",
|
CreateStep("step-construction-build", "build-site", $"Build {site.Id}",
|
||||||
[
|
[
|
||||||
CreateSubTask("sub-construction-build", ShipTaskKinds.BuildConstructionSite, $"Build {site.Id}", site.SystemId, site.CelestialId is null ? supportStation.Position : supportStation.Position, site.Id, 12f, 0f)
|
CreateSubTask("sub-construction-build", ShipTaskKinds.BuildConstructionSite, $"Build {site.Id}", site.SystemId, supportStation.Position, site.Id, 12f, 0f)
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,9 @@ public sealed partial class ShipAiService
|
|||||||
float amount,
|
float amount,
|
||||||
string? itemId = null,
|
string? itemId = null,
|
||||||
string? moduleId = null,
|
string? moduleId = null,
|
||||||
string? targetNodeId = null) =>
|
string? targetAnchorId = null,
|
||||||
|
string? targetResourceNodeId = null,
|
||||||
|
string? targetResourceDepositId = null) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
@@ -310,7 +312,9 @@ public sealed partial class ShipAiService
|
|||||||
TargetSystemId = targetSystemId,
|
TargetSystemId = targetSystemId,
|
||||||
TargetPosition = targetPosition,
|
TargetPosition = targetPosition,
|
||||||
TargetEntityId = targetEntityId,
|
TargetEntityId = targetEntityId,
|
||||||
TargetNodeId = targetNodeId,
|
TargetAnchorId = targetAnchorId,
|
||||||
|
TargetResourceNodeId = targetResourceNodeId,
|
||||||
|
TargetResourceDepositId = targetResourceDepositId,
|
||||||
ItemId = itemId,
|
ItemId = itemId,
|
||||||
ModuleId = moduleId,
|
ModuleId = moduleId,
|
||||||
Threshold = threshold,
|
Threshold = threshold,
|
||||||
|
|||||||
@@ -171,7 +171,8 @@ public sealed partial class ShipAiService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = ResolveNode(world, order.NodeId);
|
var anchor = ResolveMiningAnchor(world, order.AnchorId, order.TargetEntityId);
|
||||||
|
var node = ResolveNode(world, order.TargetEntityId);
|
||||||
if (node is not null)
|
if (node is not null)
|
||||||
{
|
{
|
||||||
if (!string.Equals(node.SystemId, systemId, StringComparison.Ordinal))
|
if (!string.Equals(node.SystemId, systemId, StringComparison.Ordinal))
|
||||||
@@ -188,7 +189,7 @@ public sealed partial class ShipAiService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
node = SelectLocalMiningNode(world, ship, systemId, itemId);
|
node = SelectLocalMiningNode(world, ship, systemId, itemId, anchor?.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node is null)
|
if (node is null)
|
||||||
@@ -197,24 +198,30 @@ public sealed partial class ShipAiService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BuildLocalMiningPlan(ship, AiPlanSourceKind.Order, order.Id, node, order.Label ?? $"Mine {itemId} in {systemId}");
|
return BuildLocalMiningPlan(world, ship, AiPlanSourceKind.Order, order.Id, node, order.Label ?? $"Mine {itemId} in {systemId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShipPlanRuntime? BuildMineLocalOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
|
private ShipPlanRuntime? BuildMineLocalOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
|
||||||
{
|
{
|
||||||
var node = ResolveNode(world, order.NodeId);
|
var anchor = ResolveMiningAnchor(world, order.AnchorId, order.TargetEntityId);
|
||||||
|
var node = ResolveNode(world, order.TargetEntityId)
|
||||||
|
?? SelectLocalMiningNode(world, ship, order.TargetSystemId ?? ship.SystemId, order.ItemId ?? ship.DefaultBehavior.ItemId ?? string.Empty, anchor?.Id);
|
||||||
if (node is null)
|
if (node is null)
|
||||||
{
|
{
|
||||||
order.FailureReason = "mine-order-incomplete";
|
order.FailureReason = "mine-order-incomplete";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BuildLocalMiningPlan(ship, AiPlanSourceKind.Order, order.Id, node, order.Label ?? $"Mine {node.ItemId}");
|
return BuildLocalMiningPlan(world, ship, AiPlanSourceKind.Order, order.Id, node, order.Label ?? $"Mine {node.ItemId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShipPlanRuntime? BuildMineAndDeliverRunOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
|
private ShipPlanRuntime? BuildMineAndDeliverRunOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
|
||||||
{
|
{
|
||||||
var node = ResolveNode(world, order.NodeId);
|
var anchor = ResolveMiningAnchor(world, order.AnchorId, order.TargetEntityId);
|
||||||
|
var node = ResolveNode(world, order.TargetEntityId)
|
||||||
|
?? (string.IsNullOrWhiteSpace(order.ItemId)
|
||||||
|
? null
|
||||||
|
: SelectLocalMiningNode(world, ship, order.TargetSystemId ?? ship.SystemId, order.ItemId, anchor?.Id));
|
||||||
var buyer = ResolveStation(world, order.DestinationStationId);
|
var buyer = ResolveStation(world, order.DestinationStationId);
|
||||||
if (node is null || buyer is null)
|
if (node is null || buyer is null)
|
||||||
{
|
{
|
||||||
@@ -222,7 +229,7 @@ public sealed partial class ShipAiService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BuildMiningPlan(ship, AiPlanSourceKind.Order, order.Id, node, buyer, order.Label ?? $"Mine {node.ItemId} for {buyer.Label}");
|
return BuildMiningPlan(world, ship, AiPlanSourceKind.Order, order.Id, node, buyer, order.Label ?? $"Mine {node.ItemId} for {buyer.Label}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShipPlanRuntime? BuildSellMinedCargoOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
|
private ShipPlanRuntime? BuildSellMinedCargoOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
|
||||||
@@ -396,9 +403,10 @@ public sealed partial class ShipAiService
|
|||||||
return BuildFollowShipPlan(ship, AiPlanSourceKind.Order, order.Id, targetShip, MathF.Max(order.Radius, 18f), MathF.Max(2f, order.WaitSeconds), order.Label ?? $"Follow {targetShip.Definition.Name}");
|
return BuildFollowShipPlan(ship, AiPlanSourceKind.Order, order.Id, targetShip, MathF.Max(order.Radius, 18f), MathF.Max(2f, order.WaitSeconds), order.Label ?? $"Follow {targetShip.Definition.Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShipPlanRuntime BuildMiningPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ResourceNodeRuntime node, StationRuntime homeStation, string summary)
|
private ShipPlanRuntime BuildMiningPlan(SimulationWorld world, ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ResourceNodeRuntime node, StationRuntime homeStation, string summary)
|
||||||
{
|
{
|
||||||
var extractionPosition = GetResourceHoldPosition(node.Position, ship.Id, 20f);
|
var deposit = SelectMiningDeposit(node, ship.Id);
|
||||||
|
var extractionPosition = GetResourceHoldPosition(deposit?.Position ?? Vector3.Zero, ship.Id, 20f);
|
||||||
return CreatePlan(
|
return CreatePlan(
|
||||||
ship,
|
ship,
|
||||||
sourceKind,
|
sourceKind,
|
||||||
@@ -408,8 +416,8 @@ public sealed partial class ShipAiService
|
|||||||
[
|
[
|
||||||
CreateStep("step-mine", "mine", $"Mine {node.ItemId}",
|
CreateStep("step-mine", "mine", $"Mine {node.ItemId}",
|
||||||
[
|
[
|
||||||
CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} node", node.SystemId, extractionPosition, node.Id, 8f, 0f),
|
CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} field", node.SystemId, Vector3.Zero, node.AnchorId, 8f, 0f, targetAnchorId: node.AnchorId),
|
||||||
CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity())
|
CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: node.ItemId, targetAnchorId: node.AnchorId, targetResourceNodeId: node.Id, targetResourceDepositId: deposit?.Id)
|
||||||
]),
|
]),
|
||||||
CreateStep("step-deliver", "deliver", $"Deliver {node.ItemId} to {homeStation.Label}",
|
CreateStep("step-deliver", "deliver", $"Deliver {node.ItemId} to {homeStation.Label}",
|
||||||
[
|
[
|
||||||
@@ -421,9 +429,10 @@ public sealed partial class ShipAiService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShipPlanRuntime BuildLocalMiningPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ResourceNodeRuntime node, string summary)
|
private ShipPlanRuntime BuildLocalMiningPlan(SimulationWorld world, ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ResourceNodeRuntime node, string summary)
|
||||||
{
|
{
|
||||||
var extractionPosition = GetResourceHoldPosition(node.Position, ship.Id, 20f);
|
var deposit = SelectMiningDeposit(node, ship.Id);
|
||||||
|
var extractionPosition = GetResourceHoldPosition(deposit?.Position ?? Vector3.Zero, ship.Id, 20f);
|
||||||
return CreatePlan(
|
return CreatePlan(
|
||||||
ship,
|
ship,
|
||||||
sourceKind,
|
sourceKind,
|
||||||
@@ -433,8 +442,8 @@ public sealed partial class ShipAiService
|
|||||||
[
|
[
|
||||||
CreateStep("step-mine", "mine", $"Mine {node.ItemId}",
|
CreateStep("step-mine", "mine", $"Mine {node.ItemId}",
|
||||||
[
|
[
|
||||||
CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} node", node.SystemId, extractionPosition, node.Id, 8f, 0f),
|
CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} field", node.SystemId, Vector3.Zero, node.AnchorId, 8f, 0f, targetAnchorId: node.AnchorId),
|
||||||
CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: node.ItemId)
|
CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: node.ItemId, targetAnchorId: node.AnchorId, targetResourceNodeId: node.Id, targetResourceDepositId: deposit?.Id)
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed record ShipOrderCommandRequest(
|
|||||||
string? SourceStationId,
|
string? SourceStationId,
|
||||||
string? DestinationStationId,
|
string? DestinationStationId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? NodeId,
|
string? AnchorId,
|
||||||
string? ConstructionSiteId,
|
string? ConstructionSiteId,
|
||||||
string? ModuleId,
|
string? ModuleId,
|
||||||
float? WaitSeconds,
|
float? WaitSeconds,
|
||||||
@@ -28,7 +28,7 @@ public sealed record ShipOrderTemplateCommandRequest(
|
|||||||
string? SourceStationId,
|
string? SourceStationId,
|
||||||
string? DestinationStationId,
|
string? DestinationStationId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? NodeId,
|
string? AnchorId,
|
||||||
string? ConstructionSiteId,
|
string? ConstructionSiteId,
|
||||||
string? ModuleId,
|
string? ModuleId,
|
||||||
float? WaitSeconds,
|
float? WaitSeconds,
|
||||||
@@ -43,7 +43,7 @@ public sealed record ShipDefaultBehaviorCommandRequest(
|
|||||||
string? AreaSystemId,
|
string? AreaSystemId,
|
||||||
string? TargetEntityId,
|
string? TargetEntityId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? PreferredNodeId,
|
string? PreferredAnchorId,
|
||||||
string? PreferredConstructionSiteId,
|
string? PreferredConstructionSiteId,
|
||||||
string? PreferredModuleId,
|
string? PreferredModuleId,
|
||||||
Vector3Dto? TargetPosition,
|
Vector3Dto? TargetPosition,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public sealed record ShipOrderSnapshot(
|
|||||||
string? SourceStationId,
|
string? SourceStationId,
|
||||||
string? DestinationStationId,
|
string? DestinationStationId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? NodeId,
|
string? AnchorId,
|
||||||
string? ConstructionSiteId,
|
string? ConstructionSiteId,
|
||||||
string? ModuleId,
|
string? ModuleId,
|
||||||
float WaitSeconds,
|
float WaitSeconds,
|
||||||
@@ -41,7 +41,7 @@ public sealed record ShipOrderTemplateSnapshot(
|
|||||||
string? SourceStationId,
|
string? SourceStationId,
|
||||||
string? DestinationStationId,
|
string? DestinationStationId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? NodeId,
|
string? AnchorId,
|
||||||
string? ConstructionSiteId,
|
string? ConstructionSiteId,
|
||||||
string? ModuleId,
|
string? ModuleId,
|
||||||
float WaitSeconds,
|
float WaitSeconds,
|
||||||
@@ -56,7 +56,7 @@ public sealed record DefaultBehaviorSnapshot(
|
|||||||
string? AreaSystemId,
|
string? AreaSystemId,
|
||||||
string? TargetEntityId,
|
string? TargetEntityId,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? PreferredNodeId,
|
string? PreferredAnchorId,
|
||||||
string? PreferredConstructionSiteId,
|
string? PreferredConstructionSiteId,
|
||||||
string? PreferredModuleId,
|
string? PreferredModuleId,
|
||||||
Vector3Dto? TargetPosition,
|
Vector3Dto? TargetPosition,
|
||||||
@@ -95,7 +95,9 @@ public sealed record ShipSubTaskSnapshot(
|
|||||||
string Summary,
|
string Summary,
|
||||||
string? TargetEntityId,
|
string? TargetEntityId,
|
||||||
string? TargetSystemId,
|
string? TargetSystemId,
|
||||||
string? TargetNodeId,
|
string? TargetAnchorId,
|
||||||
|
string? TargetResourceNodeId,
|
||||||
|
string? TargetResourceDepositId,
|
||||||
Vector3Dto? TargetPosition,
|
Vector3Dto? TargetPosition,
|
||||||
string? ItemId,
|
string? ItemId,
|
||||||
string? ModuleId,
|
string? ModuleId,
|
||||||
@@ -135,6 +137,7 @@ public sealed record ShipSnapshot(
|
|||||||
string Purpose,
|
string Purpose,
|
||||||
string Type,
|
string Type,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
|
string? AnchorId,
|
||||||
Vector3Dto LocalPosition,
|
Vector3Dto LocalPosition,
|
||||||
Vector3Dto LocalVelocity,
|
Vector3Dto LocalVelocity,
|
||||||
Vector3Dto TargetLocalPosition,
|
Vector3Dto TargetLocalPosition,
|
||||||
@@ -151,11 +154,11 @@ public sealed record ShipSnapshot(
|
|||||||
string? ControlReason,
|
string? ControlReason,
|
||||||
string? LastReplanReason,
|
string? LastReplanReason,
|
||||||
string? LastAccessFailureReason,
|
string? LastAccessFailureReason,
|
||||||
string? CelestialId,
|
|
||||||
string? DockedStationId,
|
string? DockedStationId,
|
||||||
string? CommanderId,
|
string? CommanderId,
|
||||||
string? PolicySetId,
|
string? PolicySetId,
|
||||||
float CargoCapacity,
|
float CargoCapacity,
|
||||||
|
IReadOnlyList<string> CargoTypes,
|
||||||
float TravelSpeed,
|
float TravelSpeed,
|
||||||
string TravelSpeedUnit,
|
string TravelSpeedUnit,
|
||||||
IReadOnlyList<InventoryEntry> Inventory,
|
IReadOnlyList<InventoryEntry> Inventory,
|
||||||
@@ -170,6 +173,7 @@ public sealed record ShipDelta(
|
|||||||
string Purpose,
|
string Purpose,
|
||||||
string Type,
|
string Type,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
|
string? AnchorId,
|
||||||
Vector3Dto LocalPosition,
|
Vector3Dto LocalPosition,
|
||||||
Vector3Dto LocalVelocity,
|
Vector3Dto LocalVelocity,
|
||||||
Vector3Dto TargetLocalPosition,
|
Vector3Dto TargetLocalPosition,
|
||||||
@@ -186,11 +190,11 @@ public sealed record ShipDelta(
|
|||||||
string? ControlReason,
|
string? ControlReason,
|
||||||
string? LastReplanReason,
|
string? LastReplanReason,
|
||||||
string? LastAccessFailureReason,
|
string? LastAccessFailureReason,
|
||||||
string? CelestialId,
|
|
||||||
string? DockedStationId,
|
string? DockedStationId,
|
||||||
string? CommanderId,
|
string? CommanderId,
|
||||||
string? PolicySetId,
|
string? PolicySetId,
|
||||||
float CargoCapacity,
|
float CargoCapacity,
|
||||||
|
IReadOnlyList<string> CargoTypes,
|
||||||
float TravelSpeed,
|
float TravelSpeed,
|
||||||
string TravelSpeedUnit,
|
string TravelSpeedUnit,
|
||||||
IReadOnlyList<InventoryEntry> Inventory,
|
IReadOnlyList<InventoryEntry> Inventory,
|
||||||
@@ -202,17 +206,17 @@ public sealed record ShipDelta(
|
|||||||
public sealed record ShipSpatialStateSnapshot(
|
public sealed record ShipSpatialStateSnapshot(
|
||||||
string SpaceLayer,
|
string SpaceLayer,
|
||||||
string CurrentSystemId,
|
string CurrentSystemId,
|
||||||
string? CurrentCelestialId,
|
string? CurrentAnchorId,
|
||||||
Vector3Dto? LocalPosition,
|
Vector3Dto? LocalPosition,
|
||||||
Vector3Dto? SystemPosition,
|
Vector3Dto? SystemPosition,
|
||||||
string MovementRegime,
|
string MovementRegime,
|
||||||
string? DestinationNodeId,
|
string? DestinationAnchorId,
|
||||||
ShipTransitSnapshot? Transit);
|
ShipTransitSnapshot? Transit);
|
||||||
|
|
||||||
public sealed record ShipTransitSnapshot(
|
public sealed record ShipTransitSnapshot(
|
||||||
string Regime,
|
string Regime,
|
||||||
string? OriginNodeId,
|
string? OriginAnchorId,
|
||||||
string? DestinationNodeId,
|
string? DestinationAnchorId,
|
||||||
DateTimeOffset? StartedAtUtc,
|
DateTimeOffset? StartedAtUtc,
|
||||||
DateTimeOffset? ArrivalDueAtUtc,
|
DateTimeOffset? ArrivalDueAtUtc,
|
||||||
float Progress);
|
float Progress);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public sealed class ShipOrderRuntime
|
|||||||
public string? SourceStationId { get; set; }
|
public string? SourceStationId { get; set; }
|
||||||
public string? DestinationStationId { get; set; }
|
public string? DestinationStationId { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? NodeId { get; set; }
|
public string? AnchorId { get; set; }
|
||||||
public string? ConstructionSiteId { get; set; }
|
public string? ConstructionSiteId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
public float WaitSeconds { get; set; }
|
public float WaitSeconds { get; set; }
|
||||||
@@ -78,7 +78,7 @@ public sealed class DefaultBehaviorRuntime
|
|||||||
public string? AreaSystemId { get; set; }
|
public string? AreaSystemId { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? PreferredNodeId { get; set; }
|
public string? PreferredAnchorId { get; set; }
|
||||||
public string? PreferredConstructionSiteId { get; set; }
|
public string? PreferredConstructionSiteId { get; set; }
|
||||||
public string? PreferredModuleId { get; set; }
|
public string? PreferredModuleId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
@@ -102,7 +102,7 @@ public sealed class ShipOrderTemplateRuntime
|
|||||||
public string? SourceStationId { get; set; }
|
public string? SourceStationId { get; set; }
|
||||||
public string? DestinationStationId { get; set; }
|
public string? DestinationStationId { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? NodeId { get; set; }
|
public string? AnchorId { get; set; }
|
||||||
public string? ConstructionSiteId { get; set; }
|
public string? ConstructionSiteId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
public float WaitSeconds { get; set; }
|
public float WaitSeconds { get; set; }
|
||||||
@@ -146,7 +146,9 @@ public sealed class ShipSubTaskRuntime
|
|||||||
public WorkStatus Status { get; set; } = WorkStatus.Pending;
|
public WorkStatus Status { get; set; } = WorkStatus.Pending;
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public string? TargetNodeId { get; set; }
|
public string? TargetAnchorId { get; set; }
|
||||||
|
public string? TargetResourceNodeId { get; set; }
|
||||||
|
public string? TargetResourceDepositId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ internal sealed class SimulationEngine
|
|||||||
CreateWreck(world, "station", station.Id, station.SystemId, station.Position, station.MaxHealth * 0.12f);
|
CreateWreck(world, "station", station.Id, station.SystemId, station.Position, station.MaxHealth * 0.12f);
|
||||||
world.Stations.Remove(station);
|
world.Stations.Remove(station);
|
||||||
|
|
||||||
if (station.CelestialId is not null && world.Celestials.FirstOrDefault(candidate => candidate.Id == station.CelestialId) is { } celestial)
|
if (station.AnchorId is not null && world.Anchors.FirstOrDefault(candidate => candidate.Id == station.AnchorId) is { } anchor)
|
||||||
{
|
{
|
||||||
celestial.OccupyingStructureId = null;
|
anchor.OccupyingStructureId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var claim in world.Claims.Where(candidate => candidate.CelestialId == station.CelestialId))
|
foreach (var claim in world.Claims.Where(candidate => candidate.AnchorId == station.AnchorId))
|
||||||
{
|
{
|
||||||
claim.Health = 0f;
|
claim.Health = 0f;
|
||||||
claim.State = ClaimStateKinds.Destroyed;
|
claim.State = ClaimStateKinds.Destroyed;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ internal sealed class SimulationProjectionService
|
|||||||
false,
|
false,
|
||||||
events,
|
events,
|
||||||
BuildCelestialDeltas(world),
|
BuildCelestialDeltas(world),
|
||||||
|
BuildAnchorDeltas(world),
|
||||||
BuildNodeDeltas(world),
|
BuildNodeDeltas(world),
|
||||||
BuildStationDeltas(world),
|
BuildStationDeltas(world),
|
||||||
BuildClaimDeltas(world),
|
BuildClaimDeltas(world),
|
||||||
@@ -87,26 +88,37 @@ internal sealed class SimulationProjectionService
|
|||||||
c.Kind,
|
c.Kind,
|
||||||
c.OrbitalAnchor,
|
c.OrbitalAnchor,
|
||||||
c.LocalSpaceRadius,
|
c.LocalSpaceRadius,
|
||||||
c.ParentNodeId,
|
c.ParentAnchorId,
|
||||||
c.OccupyingStructureId,
|
c.OccupyingStructureId,
|
||||||
c.OrbitReferenceId)).ToList(),
|
c.OrbitReferenceId)).ToList(),
|
||||||
|
world.Anchors.Select(ToAnchorDelta).Select(anchor => new AnchorSnapshot(
|
||||||
|
anchor.Id,
|
||||||
|
anchor.SystemId,
|
||||||
|
anchor.Kind,
|
||||||
|
anchor.SystemPosition,
|
||||||
|
anchor.LocalSpaceRadius,
|
||||||
|
anchor.ParentAnchorId,
|
||||||
|
anchor.OccupyingStructureId,
|
||||||
|
anchor.OrbitReferenceId)).ToList(),
|
||||||
world.Nodes.Select(ToNodeDelta).Select(node => new ResourceNodeSnapshot(
|
world.Nodes.Select(ToNodeDelta).Select(node => new ResourceNodeSnapshot(
|
||||||
node.Id,
|
node.Id,
|
||||||
|
node.AnchorId,
|
||||||
node.SystemId,
|
node.SystemId,
|
||||||
node.LocalPosition,
|
node.LocalPosition,
|
||||||
node.CelestialId,
|
node.LocalSpaceRadius,
|
||||||
node.SourceKind,
|
node.SourceKind,
|
||||||
node.OreRemaining,
|
node.OreRemaining,
|
||||||
node.MaxOre,
|
node.MaxOre,
|
||||||
node.ItemId)).ToList(),
|
node.ItemId,
|
||||||
|
node.Deposits)).ToList(),
|
||||||
world.Stations.Select(station => ToStationDelta(world, station)).Select(station => new StationSnapshot(
|
world.Stations.Select(station => ToStationDelta(world, station)).Select(station => new StationSnapshot(
|
||||||
station.Id,
|
station.Id,
|
||||||
station.Label,
|
station.Label,
|
||||||
station.Category,
|
station.Category,
|
||||||
station.Objective,
|
station.Objective,
|
||||||
station.SystemId,
|
station.SystemId,
|
||||||
|
station.AnchorId,
|
||||||
station.LocalPosition,
|
station.LocalPosition,
|
||||||
station.CelestialId,
|
|
||||||
station.Color,
|
station.Color,
|
||||||
station.DockedShips,
|
station.DockedShips,
|
||||||
station.DockedShipIds,
|
station.DockedShipIds,
|
||||||
@@ -127,7 +139,7 @@ internal sealed class SimulationProjectionService
|
|||||||
claim.Id,
|
claim.Id,
|
||||||
claim.FactionId,
|
claim.FactionId,
|
||||||
claim.SystemId,
|
claim.SystemId,
|
||||||
claim.CelestialId,
|
claim.AnchorId,
|
||||||
claim.State,
|
claim.State,
|
||||||
claim.Health,
|
claim.Health,
|
||||||
claim.PlacedAtUtc,
|
claim.PlacedAtUtc,
|
||||||
@@ -136,7 +148,7 @@ internal sealed class SimulationProjectionService
|
|||||||
site.Id,
|
site.Id,
|
||||||
site.FactionId,
|
site.FactionId,
|
||||||
site.SystemId,
|
site.SystemId,
|
||||||
site.CelestialId,
|
site.AnchorId,
|
||||||
site.TargetKind,
|
site.TargetKind,
|
||||||
site.TargetDefinitionId,
|
site.TargetDefinitionId,
|
||||||
site.BlueprintId,
|
site.BlueprintId,
|
||||||
@@ -180,6 +192,7 @@ internal sealed class SimulationProjectionService
|
|||||||
ship.Purpose,
|
ship.Purpose,
|
||||||
ship.Type,
|
ship.Type,
|
||||||
ship.SystemId,
|
ship.SystemId,
|
||||||
|
ship.AnchorId,
|
||||||
ship.LocalPosition,
|
ship.LocalPosition,
|
||||||
ship.LocalVelocity,
|
ship.LocalVelocity,
|
||||||
ship.TargetLocalPosition,
|
ship.TargetLocalPosition,
|
||||||
@@ -196,11 +209,11 @@ internal sealed class SimulationProjectionService
|
|||||||
ship.ControlReason,
|
ship.ControlReason,
|
||||||
ship.LastReplanReason,
|
ship.LastReplanReason,
|
||||||
ship.LastAccessFailureReason,
|
ship.LastAccessFailureReason,
|
||||||
ship.CelestialId,
|
|
||||||
ship.DockedStationId,
|
ship.DockedStationId,
|
||||||
ship.CommanderId,
|
ship.CommanderId,
|
||||||
ship.PolicySetId,
|
ship.PolicySetId,
|
||||||
ship.CargoCapacity,
|
ship.CargoCapacity,
|
||||||
|
ship.CargoTypes,
|
||||||
ship.TravelSpeed,
|
ship.TravelSpeed,
|
||||||
ship.TravelSpeedUnit,
|
ship.TravelSpeedUnit,
|
||||||
ship.Inventory,
|
ship.Inventory,
|
||||||
@@ -239,6 +252,11 @@ internal sealed class SimulationProjectionService
|
|||||||
celestial.LastDeltaSignature = BuildCelestialSignature(celestial);
|
celestial.LastDeltaSignature = BuildCelestialSignature(celestial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var anchor in world.Anchors)
|
||||||
|
{
|
||||||
|
anchor.LastDeltaSignature = BuildAnchorSignature(anchor);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var station in world.Stations)
|
foreach (var station in world.Stations)
|
||||||
{
|
{
|
||||||
station.LastDeltaSignature = BuildStationSignature(world, station);
|
station.LastDeltaSignature = BuildStationSignature(world, station);
|
||||||
@@ -298,6 +316,24 @@ internal sealed class SimulationProjectionService
|
|||||||
return deltas;
|
return deltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<AnchorDelta> BuildAnchorDeltas(SimulationWorld world)
|
||||||
|
{
|
||||||
|
var deltas = new List<AnchorDelta>();
|
||||||
|
foreach (var anchor in world.Anchors)
|
||||||
|
{
|
||||||
|
var signature = BuildAnchorSignature(anchor);
|
||||||
|
if (signature == anchor.LastDeltaSignature)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchor.LastDeltaSignature = signature;
|
||||||
|
deltas.Add(ToAnchorDelta(anchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return deltas;
|
||||||
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<CelestialDelta> BuildCelestialDeltas(SimulationWorld world)
|
private static IReadOnlyList<CelestialDelta> BuildCelestialDeltas(SimulationWorld world)
|
||||||
{
|
{
|
||||||
var deltas = new List<CelestialDelta>();
|
var deltas = new List<CelestialDelta>();
|
||||||
@@ -466,17 +502,30 @@ internal sealed class SimulationProjectionService
|
|||||||
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
|
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
|
||||||
|
|
||||||
private static string BuildNodeSignature(ResourceNodeRuntime node) =>
|
private static string BuildNodeSignature(ResourceNodeRuntime node) =>
|
||||||
$"{node.SystemId}|{node.Position.X:0.###}|{node.Position.Y:0.###}|{node.Position.Z:0.###}|{node.CelestialId}|{node.OreRemaining:0.###}";
|
string.Join("|",
|
||||||
|
node.SystemId,
|
||||||
|
node.AnchorId,
|
||||||
|
$"{node.Position.X:0.###}",
|
||||||
|
$"{node.Position.Y:0.###}",
|
||||||
|
$"{node.Position.Z:0.###}",
|
||||||
|
$"{node.OreRemaining:0.###}",
|
||||||
|
string.Join(",",
|
||||||
|
node.Deposits
|
||||||
|
.OrderBy(deposit => deposit.Id, StringComparer.Ordinal)
|
||||||
|
.Select(deposit => $"{deposit.Id}:{deposit.Position.X:0.###}:{deposit.Position.Y:0.###}:{deposit.Position.Z:0.###}:{deposit.OreRemaining:0.###}")));
|
||||||
|
|
||||||
private static string BuildCelestialSignature(CelestialRuntime celestial) =>
|
private static string BuildCelestialSignature(CelestialRuntime celestial) =>
|
||||||
$"{celestial.SystemId}|{celestial.Kind.ToContractValue()}|{celestial.Position.X:0.###}|{celestial.Position.Y:0.###}|{celestial.Position.Z:0.###}|{celestial.LocalSpaceRadius:0.###}|{celestial.ParentNodeId}|{celestial.OccupyingStructureId}|{celestial.OrbitReferenceId}";
|
$"{celestial.SystemId}|{celestial.Kind.ToContractValue()}|{celestial.Position.X:0.###}|{celestial.Position.Y:0.###}|{celestial.Position.Z:0.###}|{celestial.LocalSpaceRadius:0.###}|{celestial.ParentAnchorId}|{celestial.OccupyingStructureId}|{celestial.OrbitReferenceId}";
|
||||||
|
|
||||||
|
private static string BuildAnchorSignature(AnchorRuntime anchor) =>
|
||||||
|
$"{anchor.SystemId}|{anchor.Kind.ToContractValue()}|{anchor.Position.X:0.###}|{anchor.Position.Y:0.###}|{anchor.Position.Z:0.###}|{anchor.LocalSpaceRadius:0.###}|{anchor.ParentAnchorId}|{anchor.OccupyingStructureId}|{anchor.OrbitReferenceId}|{anchor.SourceEntityKind}|{anchor.SourceEntityId}";
|
||||||
|
|
||||||
private static string BuildStationSignature(SimulationWorld world, StationRuntime station)
|
private static string BuildStationSignature(SimulationWorld world, StationRuntime station)
|
||||||
{
|
{
|
||||||
var processes = ToStationActionProgressSnapshots(world, station);
|
var processes = ToStationActionProgressSnapshots(world, station);
|
||||||
return string.Join("|",
|
return string.Join("|",
|
||||||
station.SystemId,
|
station.SystemId,
|
||||||
station.CelestialId ?? "none",
|
station.AnchorId ?? "none",
|
||||||
station.CommanderId ?? "none",
|
station.CommanderId ?? "none",
|
||||||
station.PolicySetId ?? "none",
|
station.PolicySetId ?? "none",
|
||||||
BuildInventorySignature(station.Inventory),
|
BuildInventorySignature(station.Inventory),
|
||||||
@@ -495,10 +544,10 @@ internal sealed class SimulationProjectionService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildClaimSignature(ClaimRuntime claim) =>
|
private static string BuildClaimSignature(ClaimRuntime claim) =>
|
||||||
$"{claim.FactionId}|{claim.SystemId}|{claim.CelestialId}|{claim.State}|{claim.Health:0.###}|{claim.ActivatesAtUtc:O}";
|
$"{claim.FactionId}|{claim.SystemId}|{claim.AnchorId}|{claim.State}|{claim.Health:0.###}|{claim.ActivatesAtUtc:O}";
|
||||||
|
|
||||||
private static string BuildConstructionSiteSignature(ConstructionSiteRuntime site) =>
|
private static string BuildConstructionSiteSignature(ConstructionSiteRuntime site) =>
|
||||||
$"{site.FactionId}|{site.SystemId}|{site.CelestialId}|{site.TargetKind}|{site.TargetDefinitionId}|{site.BlueprintId}|{site.ClaimId}|{site.StationId}|{site.State}|{site.Progress:0.###}|{BuildInventorySignature(site.Inventory)}|{BuildInventorySignature(site.RequiredItems)}|{BuildInventorySignature(site.DeliveredItems)}|{string.Join(",", site.AssignedConstructorShipIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", site.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal))}";
|
$"{site.FactionId}|{site.SystemId}|{site.AnchorId}|{site.TargetKind}|{site.TargetDefinitionId}|{site.BlueprintId}|{site.ClaimId}|{site.StationId}|{site.State}|{site.Progress:0.###}|{BuildInventorySignature(site.Inventory)}|{BuildInventorySignature(site.RequiredItems)}|{BuildInventorySignature(site.DeliveredItems)}|{string.Join(",", site.AssignedConstructorShipIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", site.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal))}";
|
||||||
|
|
||||||
private static string BuildMarketOrderSignature(MarketOrderRuntime order) =>
|
private static string BuildMarketOrderSignature(MarketOrderRuntime order) =>
|
||||||
$"{order.FactionId}|{order.StationId}|{order.ConstructionSiteId}|{order.Kind}|{order.ItemId}|{order.Amount:0.###}|{order.RemainingAmount:0.###}|{order.Valuation:0.###}|{order.ReserveThreshold?.ToString("0.###") ?? "none"}|{order.PolicySetId}|{order.State}";
|
$"{order.FactionId}|{order.StationId}|{order.ConstructionSiteId}|{order.Kind}|{order.ItemId}|{order.Amount:0.###}|{order.RemainingAmount:0.###}|{order.Valuation:0.###}|{order.ReserveThreshold?.ToString("0.###") ?? "none"}|{order.PolicySetId}|{order.State}";
|
||||||
@@ -552,17 +601,17 @@ internal sealed class SimulationProjectionService
|
|||||||
string.Join(",",
|
string.Join(",",
|
||||||
ToActiveSubTaskSnapshots(ship).Select(subTask =>
|
ToActiveSubTaskSnapshots(ship).Select(subTask =>
|
||||||
$"{subTask.Id}:{subTask.Kind}:{subTask.Status}:{subTask.Progress:0.###}:{subTask.ElapsedSeconds:0.###}:{subTask.BlockingReason ?? "none"}")),
|
$"{subTask.Id}:{subTask.Kind}:{subTask.Status}:{subTask.Progress:0.###}:{subTask.ElapsedSeconds:0.###}:{subTask.BlockingReason ?? "none"}")),
|
||||||
ship.SpatialState.CurrentCelestialId ?? "none",
|
ship.SpatialState.CurrentAnchorId ?? "none",
|
||||||
ship.DockedStationId ?? "none",
|
ship.DockedStationId ?? "none",
|
||||||
ship.CommanderId ?? "none",
|
ship.CommanderId ?? "none",
|
||||||
ship.PolicySetId ?? "none",
|
ship.PolicySetId ?? "none",
|
||||||
ship.SpatialState.SpaceLayer.ToContractValue(),
|
ship.SpatialState.SpaceLayer.ToContractValue(),
|
||||||
ship.SpatialState.CurrentCelestialId ?? "none",
|
ship.SpatialState.CurrentAnchorId ?? "none",
|
||||||
ship.SpatialState.MovementRegime.ToContractValue(),
|
ship.SpatialState.MovementRegime.ToContractValue(),
|
||||||
ship.SpatialState.DestinationNodeId ?? "none",
|
ship.SpatialState.DestinationAnchorId ?? "none",
|
||||||
ship.SpatialState.Transit?.Regime.ToContractValue() ?? "none",
|
ship.SpatialState.Transit?.Regime.ToContractValue() ?? "none",
|
||||||
ship.SpatialState.Transit?.OriginNodeId ?? "none",
|
ship.SpatialState.Transit?.OriginAnchorId ?? "none",
|
||||||
ship.SpatialState.Transit?.DestinationNodeId ?? "none",
|
ship.SpatialState.Transit?.DestinationAnchorId ?? "none",
|
||||||
ship.SpatialState.Transit?.Progress.ToString("0.###") ?? "0",
|
ship.SpatialState.Transit?.Progress.ToString("0.###") ?? "0",
|
||||||
GetShipCargoAmount(ship).ToString("0.###"),
|
GetShipCargoAmount(ship).ToString("0.###"),
|
||||||
ship.Skills.Navigation.ToString(CultureInfo.InvariantCulture),
|
ship.Skills.Navigation.ToString(CultureInfo.InvariantCulture),
|
||||||
@@ -653,13 +702,33 @@ internal sealed class SimulationProjectionService
|
|||||||
|
|
||||||
private static ResourceNodeDelta ToNodeDelta(ResourceNodeRuntime node) => new(
|
private static ResourceNodeDelta ToNodeDelta(ResourceNodeRuntime node) => new(
|
||||||
node.Id,
|
node.Id,
|
||||||
|
node.AnchorId,
|
||||||
node.SystemId,
|
node.SystemId,
|
||||||
ToDto(node.Position),
|
ToDto(node.Position),
|
||||||
node.CelestialId,
|
node.LocalSpaceRadius,
|
||||||
node.SourceKind,
|
node.SourceKind,
|
||||||
node.OreRemaining,
|
node.OreRemaining,
|
||||||
node.MaxOre,
|
node.MaxOre,
|
||||||
node.ItemId);
|
node.ItemId,
|
||||||
|
node.Deposits.Select(ToResourceDepositSnapshot).ToList());
|
||||||
|
|
||||||
|
private static ResourceDepositSnapshot ToResourceDepositSnapshot(ResourceDepositRuntime deposit) => new(
|
||||||
|
deposit.Id,
|
||||||
|
deposit.NodeId,
|
||||||
|
deposit.AnchorId,
|
||||||
|
ToDto(deposit.Position),
|
||||||
|
deposit.OreRemaining,
|
||||||
|
deposit.MaxOre);
|
||||||
|
|
||||||
|
private static AnchorDelta ToAnchorDelta(AnchorRuntime anchor) => new(
|
||||||
|
anchor.Id,
|
||||||
|
anchor.SystemId,
|
||||||
|
anchor.Kind.ToContractValue(),
|
||||||
|
ToDto(anchor.Position),
|
||||||
|
anchor.LocalSpaceRadius,
|
||||||
|
anchor.ParentAnchorId,
|
||||||
|
anchor.OccupyingStructureId,
|
||||||
|
anchor.OrbitReferenceId);
|
||||||
|
|
||||||
private static CelestialDelta ToCelestialDelta(CelestialRuntime celestial) => new(
|
private static CelestialDelta ToCelestialDelta(CelestialRuntime celestial) => new(
|
||||||
celestial.Id,
|
celestial.Id,
|
||||||
@@ -667,7 +736,7 @@ internal sealed class SimulationProjectionService
|
|||||||
celestial.Kind.ToContractValue(),
|
celestial.Kind.ToContractValue(),
|
||||||
ToDto(celestial.Position),
|
ToDto(celestial.Position),
|
||||||
celestial.LocalSpaceRadius,
|
celestial.LocalSpaceRadius,
|
||||||
celestial.ParentNodeId,
|
celestial.ParentAnchorId,
|
||||||
celestial.OccupyingStructureId,
|
celestial.OccupyingStructureId,
|
||||||
celestial.OrbitReferenceId);
|
celestial.OrbitReferenceId);
|
||||||
|
|
||||||
@@ -677,8 +746,8 @@ internal sealed class SimulationProjectionService
|
|||||||
station.Category,
|
station.Category,
|
||||||
station.Objective,
|
station.Objective,
|
||||||
station.SystemId,
|
station.SystemId,
|
||||||
|
station.AnchorId,
|
||||||
ToDto(station.Position),
|
ToDto(station.Position),
|
||||||
station.CelestialId,
|
|
||||||
station.Color,
|
station.Color,
|
||||||
station.DockedShipIds.Count,
|
station.DockedShipIds.Count,
|
||||||
station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||||
@@ -737,7 +806,7 @@ internal sealed class SimulationProjectionService
|
|||||||
claim.Id,
|
claim.Id,
|
||||||
claim.FactionId,
|
claim.FactionId,
|
||||||
claim.SystemId,
|
claim.SystemId,
|
||||||
claim.CelestialId,
|
claim.AnchorId,
|
||||||
claim.State,
|
claim.State,
|
||||||
claim.Health,
|
claim.Health,
|
||||||
claim.PlacedAtUtc,
|
claim.PlacedAtUtc,
|
||||||
@@ -747,7 +816,7 @@ internal sealed class SimulationProjectionService
|
|||||||
site.Id,
|
site.Id,
|
||||||
site.FactionId,
|
site.FactionId,
|
||||||
site.SystemId,
|
site.SystemId,
|
||||||
site.CelestialId,
|
site.AnchorId,
|
||||||
site.TargetKind,
|
site.TargetKind,
|
||||||
site.TargetDefinitionId,
|
site.TargetDefinitionId,
|
||||||
site.BlueprintId,
|
site.BlueprintId,
|
||||||
@@ -811,6 +880,7 @@ internal sealed class SimulationProjectionService
|
|||||||
ship.Definition.Purpose.ToDataValue(),
|
ship.Definition.Purpose.ToDataValue(),
|
||||||
ship.Definition.Type.ToDataValue(),
|
ship.Definition.Type.ToDataValue(),
|
||||||
ship.SystemId,
|
ship.SystemId,
|
||||||
|
ship.SpatialState.CurrentAnchorId,
|
||||||
ToDto(ship.Position),
|
ToDto(ship.Position),
|
||||||
ToDto(ship.Velocity),
|
ToDto(ship.Velocity),
|
||||||
ToDto(ship.TargetPosition),
|
ToDto(ship.TargetPosition),
|
||||||
@@ -827,11 +897,16 @@ internal sealed class SimulationProjectionService
|
|||||||
ship.ControlReason,
|
ship.ControlReason,
|
||||||
ship.LastReplanReason,
|
ship.LastReplanReason,
|
||||||
ship.LastAccessFailureReason,
|
ship.LastAccessFailureReason,
|
||||||
ship.SpatialState.CurrentCelestialId,
|
|
||||||
ship.DockedStationId,
|
ship.DockedStationId,
|
||||||
ship.CommanderId,
|
ship.CommanderId,
|
||||||
ship.PolicySetId,
|
ship.PolicySetId,
|
||||||
ship.Definition.GetTotalCargoCapacity(),
|
ship.Definition.GetTotalCargoCapacity(),
|
||||||
|
ship.Definition.Cargo
|
||||||
|
.SelectMany(entry => entry.Types)
|
||||||
|
.Where(type => !string.IsNullOrWhiteSpace(type))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(type => type, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList(),
|
||||||
|
|
||||||
ToShipTravelSpeed(ship).Speed,
|
ToShipTravelSpeed(ship).Speed,
|
||||||
ToShipTravelSpeed(ship).Unit,
|
ToShipTravelSpeed(ship).Unit,
|
||||||
@@ -880,7 +955,7 @@ internal sealed class SimulationProjectionService
|
|||||||
order.SourceStationId,
|
order.SourceStationId,
|
||||||
order.DestinationStationId,
|
order.DestinationStationId,
|
||||||
order.ItemId,
|
order.ItemId,
|
||||||
order.NodeId,
|
order.AnchorId,
|
||||||
order.ConstructionSiteId,
|
order.ConstructionSiteId,
|
||||||
order.ModuleId,
|
order.ModuleId,
|
||||||
order.WaitSeconds,
|
order.WaitSeconds,
|
||||||
@@ -906,7 +981,7 @@ internal sealed class SimulationProjectionService
|
|||||||
behavior.AreaSystemId,
|
behavior.AreaSystemId,
|
||||||
behavior.TargetEntityId,
|
behavior.TargetEntityId,
|
||||||
behavior.ItemId,
|
behavior.ItemId,
|
||||||
behavior.PreferredNodeId,
|
behavior.PreferredAnchorId,
|
||||||
behavior.PreferredConstructionSiteId,
|
behavior.PreferredConstructionSiteId,
|
||||||
behavior.PreferredModuleId,
|
behavior.PreferredModuleId,
|
||||||
behavior.TargetPosition is null ? null : ToDto(behavior.TargetPosition.Value),
|
behavior.TargetPosition is null ? null : ToDto(behavior.TargetPosition.Value),
|
||||||
@@ -929,7 +1004,7 @@ internal sealed class SimulationProjectionService
|
|||||||
template.SourceStationId,
|
template.SourceStationId,
|
||||||
template.DestinationStationId,
|
template.DestinationStationId,
|
||||||
template.ItemId,
|
template.ItemId,
|
||||||
template.NodeId,
|
template.AnchorId,
|
||||||
template.ConstructionSiteId,
|
template.ConstructionSiteId,
|
||||||
template.ModuleId,
|
template.ModuleId,
|
||||||
template.WaitSeconds,
|
template.WaitSeconds,
|
||||||
@@ -1002,10 +1077,12 @@ internal sealed class SimulationProjectionService
|
|||||||
subTask.Kind,
|
subTask.Kind,
|
||||||
subTask.Status.ToContractValue(),
|
subTask.Status.ToContractValue(),
|
||||||
subTask.Summary,
|
subTask.Summary,
|
||||||
subTask.TargetEntityId,
|
subTask.TargetEntityId,
|
||||||
subTask.TargetSystemId,
|
subTask.TargetSystemId,
|
||||||
subTask.TargetNodeId,
|
subTask.TargetAnchorId,
|
||||||
subTask.TargetPosition is null ? null : ToDto(subTask.TargetPosition.Value),
|
subTask.TargetResourceNodeId,
|
||||||
|
subTask.TargetResourceDepositId,
|
||||||
|
subTask.TargetPosition is null ? null : ToDto(subTask.TargetPosition.Value),
|
||||||
subTask.ItemId,
|
subTask.ItemId,
|
||||||
subTask.ModuleId,
|
subTask.ModuleId,
|
||||||
subTask.Threshold,
|
subTask.Threshold,
|
||||||
@@ -1408,7 +1485,7 @@ internal sealed class SimulationProjectionService
|
|||||||
claim.SourceClaimId,
|
claim.SourceClaimId,
|
||||||
claim.FactionId,
|
claim.FactionId,
|
||||||
claim.SystemId,
|
claim.SystemId,
|
||||||
claim.CelestialId,
|
claim.AnchorId,
|
||||||
claim.Status,
|
claim.Status,
|
||||||
claim.ClaimKind,
|
claim.ClaimKind,
|
||||||
claim.ClaimStrength,
|
claim.ClaimStrength,
|
||||||
@@ -1564,15 +1641,15 @@ internal sealed class SimulationProjectionService
|
|||||||
private static ShipSpatialStateSnapshot ToShipSpatialStateSnapshot(ShipSpatialStateRuntime state) => new(
|
private static ShipSpatialStateSnapshot ToShipSpatialStateSnapshot(ShipSpatialStateRuntime state) => new(
|
||||||
state.SpaceLayer.ToContractValue(),
|
state.SpaceLayer.ToContractValue(),
|
||||||
state.CurrentSystemId,
|
state.CurrentSystemId,
|
||||||
state.CurrentCelestialId,
|
state.CurrentAnchorId,
|
||||||
state.LocalPosition is null ? null : ToDto(state.LocalPosition.Value),
|
state.LocalPosition is null ? null : ToDto(state.LocalPosition.Value),
|
||||||
state.SystemPosition is null ? null : ToDto(state.SystemPosition.Value),
|
state.SystemPosition is null ? null : ToDto(state.SystemPosition.Value),
|
||||||
state.MovementRegime.ToContractValue(),
|
state.MovementRegime.ToContractValue(),
|
||||||
state.DestinationNodeId,
|
state.DestinationAnchorId,
|
||||||
state.Transit is null ? null : new ShipTransitSnapshot(
|
state.Transit is null ? null : new ShipTransitSnapshot(
|
||||||
state.Transit.Regime.ToContractValue(),
|
state.Transit.Regime.ToContractValue(),
|
||||||
state.Transit.OriginNodeId,
|
state.Transit.OriginAnchorId,
|
||||||
state.Transit.DestinationNodeId,
|
state.Transit.DestinationAnchorId,
|
||||||
state.Transit.StartedAtUtc,
|
state.Transit.StartedAtUtc,
|
||||||
state.Transit.ArrivalDueAtUtc,
|
state.Transit.ArrivalDueAtUtc,
|
||||||
state.Transit.Progress));
|
state.Transit.Progress));
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ public sealed record StationSnapshot(
|
|||||||
string Category,
|
string Category,
|
||||||
string Objective,
|
string Objective,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
|
string? AnchorId,
|
||||||
Vector3Dto LocalPosition,
|
Vector3Dto LocalPosition,
|
||||||
string? CelestialId,
|
|
||||||
string Color,
|
string Color,
|
||||||
int DockedShips,
|
int DockedShips,
|
||||||
IReadOnlyList<string> DockedShipIds,
|
IReadOnlyList<string> DockedShipIds,
|
||||||
@@ -35,8 +35,8 @@ public sealed record StationDelta(
|
|||||||
string Category,
|
string Category,
|
||||||
string Objective,
|
string Objective,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
|
string? AnchorId,
|
||||||
Vector3Dto LocalPosition,
|
Vector3Dto LocalPosition,
|
||||||
string? CelestialId,
|
|
||||||
string Color,
|
string Color,
|
||||||
int DockedShips,
|
int DockedShips,
|
||||||
IReadOnlyList<string> DockedShipIds,
|
IReadOnlyList<string> DockedShipIds,
|
||||||
@@ -74,7 +74,7 @@ public sealed record ClaimSnapshot(
|
|||||||
string Id,
|
string Id,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
string CelestialId,
|
string AnchorId,
|
||||||
string State,
|
string State,
|
||||||
float Health,
|
float Health,
|
||||||
DateTimeOffset PlacedAtUtc,
|
DateTimeOffset PlacedAtUtc,
|
||||||
@@ -84,7 +84,7 @@ public sealed record ClaimDelta(
|
|||||||
string Id,
|
string Id,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
string CelestialId,
|
string AnchorId,
|
||||||
string State,
|
string State,
|
||||||
float Health,
|
float Health,
|
||||||
DateTimeOffset PlacedAtUtc,
|
DateTimeOffset PlacedAtUtc,
|
||||||
@@ -94,7 +94,7 @@ public sealed record ConstructionSiteSnapshot(
|
|||||||
string Id,
|
string Id,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
string CelestialId,
|
string AnchorId,
|
||||||
string TargetKind,
|
string TargetKind,
|
||||||
string TargetDefinitionId,
|
string TargetDefinitionId,
|
||||||
string? BlueprintId,
|
string? BlueprintId,
|
||||||
@@ -112,7 +112,7 @@ public sealed record ConstructionSiteDelta(
|
|||||||
string Id,
|
string Id,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
string CelestialId,
|
string AnchorId,
|
||||||
string TargetKind,
|
string TargetKind,
|
||||||
string TargetDefinitionId,
|
string TargetDefinitionId,
|
||||||
string? BlueprintId,
|
string? BlueprintId,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public sealed class ClaimRuntime
|
|||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required string CelestialId { get; init; }
|
public required string AnchorId { get; init; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public DateTimeOffset PlacedAtUtc { get; init; }
|
public DateTimeOffset PlacedAtUtc { get; init; }
|
||||||
public DateTimeOffset ActivatesAtUtc { get; set; }
|
public DateTimeOffset ActivatesAtUtc { get; set; }
|
||||||
@@ -19,7 +19,7 @@ public sealed class ConstructionSiteRuntime
|
|||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required string CelestialId { get; init; }
|
public required string AnchorId { get; init; }
|
||||||
public required string TargetKind { get; init; }
|
public required string TargetKind { get; init; }
|
||||||
public required string TargetDefinitionId { get; init; }
|
public required string TargetDefinitionId { get; init; }
|
||||||
public string? BlueprintId { get; set; }
|
public string? BlueprintId { get; set; }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public sealed class StationRuntime
|
|||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
|
public string? AnchorId { get; set; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Category { get; set; } = "station";
|
public string Category { get; set; } = "station";
|
||||||
public string Objective { get; set; } = "general";
|
public string Objective { get; set; } = "general";
|
||||||
@@ -14,7 +15,6 @@ public sealed class StationRuntime
|
|||||||
public required Vector3 Position { get; set; }
|
public required Vector3 Position { get; set; }
|
||||||
public float Radius { get; set; } = 24f;
|
public float Radius { get; set; } = 24f;
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public string? CelestialId { get; set; }
|
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public List<StationModuleRuntime> Modules { get; } = [];
|
public List<StationModuleRuntime> Modules { get; } = [];
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ internal sealed class StationLifecycleService
|
|||||||
{
|
{
|
||||||
CurrentSystemId = station.SystemId,
|
CurrentSystemId = station.SystemId,
|
||||||
SpaceLayer = SpaceLayerKind.LocalSpace,
|
SpaceLayer = SpaceLayerKind.LocalSpace,
|
||||||
CurrentCelestialId = station.CelestialId,
|
CurrentAnchorId = station.AnchorId,
|
||||||
LocalPosition = position,
|
LocalPosition = position,
|
||||||
SystemPosition = position,
|
SystemPosition = position,
|
||||||
MovementRegime = MovementRegimeKind.LocalFlight,
|
MovementRegime = MovementRegimeKind.LocalFlight,
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ public sealed class StreamWorldHandler(WorldService worldService) : EndpointWith
|
|||||||
}
|
}
|
||||||
|
|
||||||
var systemId = HttpContext.Request.Query["systemId"].ToString();
|
var systemId = HttpContext.Request.Query["systemId"].ToString();
|
||||||
var bubbleId = HttpContext.Request.Query["bubbleId"].ToString();
|
var anchorId = HttpContext.Request.Query["anchorId"].ToString();
|
||||||
var scope = new ObserverScope(
|
var scope = new ObserverScope(
|
||||||
scopeKind,
|
scopeKind,
|
||||||
string.IsNullOrWhiteSpace(systemId) ? null : systemId,
|
string.IsNullOrWhiteSpace(systemId) ? null : systemId,
|
||||||
string.IsNullOrWhiteSpace(bubbleId) ? null : bubbleId);
|
string.IsNullOrWhiteSpace(anchorId) ? null : anchorId);
|
||||||
var stream = worldService.Subscribe(scope, afterSequence, cancellationToken);
|
var stream = worldService.Subscribe(scope, afterSequence, cancellationToken);
|
||||||
|
|
||||||
await HttpContext.Response.WriteAsync(": connected\n\n", cancellationToken);
|
await HttpContext.Response.WriteAsync(": connected\n\n", cancellationToken);
|
||||||
|
|||||||
@@ -42,25 +42,57 @@ public sealed record PlanetSnapshot(
|
|||||||
string Color,
|
string Color,
|
||||||
bool HasRing);
|
bool HasRing);
|
||||||
|
|
||||||
|
public sealed record ResourceDepositSnapshot(
|
||||||
|
string Id,
|
||||||
|
string NodeId,
|
||||||
|
string AnchorId,
|
||||||
|
Vector3Dto LocalPosition,
|
||||||
|
float OreRemaining,
|
||||||
|
float MaxOre);
|
||||||
|
|
||||||
public sealed record ResourceNodeSnapshot(
|
public sealed record ResourceNodeSnapshot(
|
||||||
string Id,
|
string Id,
|
||||||
|
string AnchorId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
Vector3Dto LocalPosition,
|
Vector3Dto LocalPosition,
|
||||||
string? CelestialId,
|
float LocalSpaceRadius,
|
||||||
string SourceKind,
|
string SourceKind,
|
||||||
float OreRemaining,
|
float OreRemaining,
|
||||||
float MaxOre,
|
float MaxOre,
|
||||||
string ItemId);
|
string ItemId,
|
||||||
|
IReadOnlyList<ResourceDepositSnapshot> Deposits);
|
||||||
|
|
||||||
public sealed record ResourceNodeDelta(
|
public sealed record ResourceNodeDelta(
|
||||||
string Id,
|
string Id,
|
||||||
|
string AnchorId,
|
||||||
string SystemId,
|
string SystemId,
|
||||||
Vector3Dto LocalPosition,
|
Vector3Dto LocalPosition,
|
||||||
string? CelestialId,
|
float LocalSpaceRadius,
|
||||||
string SourceKind,
|
string SourceKind,
|
||||||
float OreRemaining,
|
float OreRemaining,
|
||||||
float MaxOre,
|
float MaxOre,
|
||||||
string ItemId);
|
string ItemId,
|
||||||
|
IReadOnlyList<ResourceDepositSnapshot> Deposits);
|
||||||
|
|
||||||
|
public sealed record AnchorSnapshot(
|
||||||
|
string Id,
|
||||||
|
string SystemId,
|
||||||
|
string Kind,
|
||||||
|
Vector3Dto SystemPosition,
|
||||||
|
float LocalSpaceRadius,
|
||||||
|
string? ParentAnchorId,
|
||||||
|
string? OccupyingStructureId,
|
||||||
|
string? OrbitReferenceId);
|
||||||
|
|
||||||
|
public sealed record AnchorDelta(
|
||||||
|
string Id,
|
||||||
|
string SystemId,
|
||||||
|
string Kind,
|
||||||
|
Vector3Dto SystemPosition,
|
||||||
|
float LocalSpaceRadius,
|
||||||
|
string? ParentAnchorId,
|
||||||
|
string? OccupyingStructureId,
|
||||||
|
string? OrbitReferenceId);
|
||||||
|
|
||||||
public sealed record CelestialSnapshot(
|
public sealed record CelestialSnapshot(
|
||||||
string Id,
|
string Id,
|
||||||
@@ -68,7 +100,7 @@ public sealed record CelestialSnapshot(
|
|||||||
string Kind,
|
string Kind,
|
||||||
Vector3Dto OrbitalAnchor,
|
Vector3Dto OrbitalAnchor,
|
||||||
float LocalSpaceRadius,
|
float LocalSpaceRadius,
|
||||||
string? ParentNodeId,
|
string? ParentAnchorId,
|
||||||
string? OccupyingStructureId,
|
string? OccupyingStructureId,
|
||||||
string? OrbitReferenceId);
|
string? OrbitReferenceId);
|
||||||
|
|
||||||
@@ -78,6 +110,6 @@ public sealed record CelestialDelta(
|
|||||||
string Kind,
|
string Kind,
|
||||||
Vector3Dto OrbitalAnchor,
|
Vector3Dto OrbitalAnchor,
|
||||||
float LocalSpaceRadius,
|
float LocalSpaceRadius,
|
||||||
string? ParentNodeId,
|
string? ParentAnchorId,
|
||||||
string? OccupyingStructureId,
|
string? OccupyingStructureId,
|
||||||
string? OrbitReferenceId);
|
string? OrbitReferenceId);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public sealed record WorldSnapshot(
|
|||||||
DateTimeOffset GeneratedAtUtc,
|
DateTimeOffset GeneratedAtUtc,
|
||||||
IReadOnlyList<SystemSnapshot> Systems,
|
IReadOnlyList<SystemSnapshot> Systems,
|
||||||
IReadOnlyList<CelestialSnapshot> Celestials,
|
IReadOnlyList<CelestialSnapshot> Celestials,
|
||||||
|
IReadOnlyList<AnchorSnapshot> Anchors,
|
||||||
IReadOnlyList<ResourceNodeSnapshot> Nodes,
|
IReadOnlyList<ResourceNodeSnapshot> Nodes,
|
||||||
IReadOnlyList<StationSnapshot> Stations,
|
IReadOnlyList<StationSnapshot> Stations,
|
||||||
IReadOnlyList<ClaimSnapshot> Claims,
|
IReadOnlyList<ClaimSnapshot> Claims,
|
||||||
@@ -29,6 +30,7 @@ public sealed record WorldDelta(
|
|||||||
bool RequiresSnapshotRefresh,
|
bool RequiresSnapshotRefresh,
|
||||||
IReadOnlyList<SimulationEventRecord> Events,
|
IReadOnlyList<SimulationEventRecord> Events,
|
||||||
IReadOnlyList<CelestialDelta> Celestials,
|
IReadOnlyList<CelestialDelta> Celestials,
|
||||||
|
IReadOnlyList<AnchorDelta> Anchors,
|
||||||
IReadOnlyList<ResourceNodeDelta> Nodes,
|
IReadOnlyList<ResourceNodeDelta> Nodes,
|
||||||
IReadOnlyList<StationDelta> Stations,
|
IReadOnlyList<StationDelta> Stations,
|
||||||
IReadOnlyList<ClaimDelta> Claims,
|
IReadOnlyList<ClaimDelta> Claims,
|
||||||
@@ -54,7 +56,7 @@ public sealed record SimulationEventRecord(
|
|||||||
public sealed record ObserverScope(
|
public sealed record ObserverScope(
|
||||||
string ScopeKind,
|
string ScopeKind,
|
||||||
string? SystemId = null,
|
string? SystemId = null,
|
||||||
string? CelestialId = null);
|
string? AnchorId = null);
|
||||||
|
|
||||||
public sealed record OrbitalSimulationSnapshot(
|
public sealed record OrbitalSimulationSnapshot(
|
||||||
double SimulatedSecondsPerRealSecond);
|
double SimulatedSecondsPerRealSecond);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public sealed class SimulationWorld
|
|||||||
public required string Label { get; init; }
|
public required string Label { get; init; }
|
||||||
public required int Seed { get; init; }
|
public required int Seed { get; init; }
|
||||||
public required List<SystemRuntime> Systems { get; init; }
|
public required List<SystemRuntime> Systems { get; init; }
|
||||||
|
public required List<AnchorRuntime> Anchors { get; init; }
|
||||||
public required List<ResourceNodeRuntime> Nodes { get; init; }
|
public required List<ResourceNodeRuntime> Nodes { get; init; }
|
||||||
public required List<CelestialRuntime> Celestials { get; init; }
|
public required List<CelestialRuntime> Celestials { get; init; }
|
||||||
public required List<WreckRuntime> Wrecks { get; init; }
|
public required List<WreckRuntime> Wrecks { get; init; }
|
||||||
|
|||||||
@@ -7,22 +7,49 @@ public sealed class SystemRuntime
|
|||||||
public required Vector3 Position { get; init; }
|
public required Vector3 Position { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class AnchorRuntime
|
||||||
|
{
|
||||||
|
public required string Id { get; init; }
|
||||||
|
public required string SystemId { get; init; }
|
||||||
|
public required SpatialNodeKind Kind { get; init; }
|
||||||
|
public required Vector3 Position { get; set; }
|
||||||
|
public float LocalSpaceRadius { get; set; }
|
||||||
|
public string? ParentAnchorId { get; set; }
|
||||||
|
public string? OrbitReferenceId { get; set; }
|
||||||
|
public string? OccupyingStructureId { get; set; }
|
||||||
|
public required string SourceEntityKind { get; init; }
|
||||||
|
public required string SourceEntityId { get; init; }
|
||||||
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ResourceNodeRuntime
|
public sealed class ResourceNodeRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
|
public required string AnchorId { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required Vector3 Position { get; set; }
|
public required Vector3 Position { get; set; }
|
||||||
public required string SourceKind { get; init; }
|
public required string SourceKind { get; init; }
|
||||||
public required string ItemId { get; init; }
|
public required string ItemId { get; init; }
|
||||||
public string? CelestialId { get; set; }
|
public float LocalSpaceRadius { get; init; }
|
||||||
public float OrbitRadius { get; init; }
|
public float OrbitRadius { get; init; }
|
||||||
public float OrbitPhase { get; init; }
|
public float OrbitPhase { get; init; }
|
||||||
public float OrbitInclination { get; init; }
|
public float OrbitInclination { get; init; }
|
||||||
public float OreRemaining { get; set; }
|
public float OreRemaining { get; set; }
|
||||||
public float MaxOre { get; init; }
|
public float MaxOre { get; init; }
|
||||||
|
public List<ResourceDepositRuntime> Deposits { get; } = [];
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ResourceDepositRuntime
|
||||||
|
{
|
||||||
|
public required string Id { get; init; }
|
||||||
|
public required string NodeId { get; init; }
|
||||||
|
public required string AnchorId { get; init; }
|
||||||
|
public required Vector3 Position { get; set; }
|
||||||
|
public float OreRemaining { get; set; }
|
||||||
|
public float MaxOre { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class CelestialRuntime
|
public sealed class CelestialRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
@@ -30,7 +57,7 @@ public sealed class CelestialRuntime
|
|||||||
public required SpatialNodeKind Kind { get; init; }
|
public required SpatialNodeKind Kind { get; init; }
|
||||||
public required Vector3 Position { get; set; }
|
public required Vector3 Position { get; set; }
|
||||||
public float LocalSpaceRadius { get; init; }
|
public float LocalSpaceRadius { get; init; }
|
||||||
public string? ParentNodeId { get; set; }
|
public string? ParentAnchorId { get; set; }
|
||||||
public string? OccupyingStructureId { get; set; }
|
public string? OccupyingStructureId { get; set; }
|
||||||
public string? OrbitReferenceId { get; set; }
|
public string? OrbitReferenceId { get; set; }
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
@@ -52,19 +79,19 @@ public sealed class ShipSpatialStateRuntime
|
|||||||
{
|
{
|
||||||
public SpaceLayerKind SpaceLayer { get; set; } = SpaceLayerKind.LocalSpace;
|
public SpaceLayerKind SpaceLayer { get; set; } = SpaceLayerKind.LocalSpace;
|
||||||
public required string CurrentSystemId { get; set; }
|
public required string CurrentSystemId { get; set; }
|
||||||
public string? CurrentCelestialId { get; set; }
|
public string? CurrentAnchorId { get; set; }
|
||||||
public Vector3? LocalPosition { get; set; }
|
public Vector3? LocalPosition { get; set; }
|
||||||
public Vector3? SystemPosition { get; set; }
|
public Vector3? SystemPosition { get; set; }
|
||||||
public MovementRegimeKind MovementRegime { get; set; } = MovementRegimeKind.LocalFlight;
|
public MovementRegimeKind MovementRegime { get; set; } = MovementRegimeKind.LocalFlight;
|
||||||
public string? DestinationNodeId { get; set; }
|
public string? DestinationAnchorId { get; set; }
|
||||||
public ShipTransitRuntime? Transit { get; set; }
|
public ShipTransitRuntime? Transit { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipTransitRuntime
|
public sealed class ShipTransitRuntime
|
||||||
{
|
{
|
||||||
public required MovementRegimeKind Regime { get; init; }
|
public required MovementRegimeKind Regime { get; init; }
|
||||||
public string? OriginNodeId { get; init; }
|
public string? OriginAnchorId { get; init; }
|
||||||
public string? DestinationNodeId { get; init; }
|
public string? DestinationAnchorId { get; init; }
|
||||||
public DateTimeOffset? StartedAtUtc { get; set; }
|
public DateTimeOffset? StartedAtUtc { get; set; }
|
||||||
public DateTimeOffset? ArrivalDueAtUtc { get; set; }
|
public DateTimeOffset? ArrivalDueAtUtc { get; set; }
|
||||||
public float Progress { get; set; }
|
public float Progress { get; set; }
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ public sealed class ScenarioContentBuilder(
|
|||||||
scenario,
|
scenario,
|
||||||
topology.SystemsById,
|
topology.SystemsById,
|
||||||
topology.SpatialLayout.SystemGraphs,
|
topology.SpatialLayout.SystemGraphs,
|
||||||
topology.SpatialLayout.Celestials);
|
topology.SpatialLayout.Anchors);
|
||||||
|
|
||||||
var patrolRoutes = BuildPatrolRoutes(scenario, topology.SystemsById);
|
var patrolRoutes = BuildPatrolRoutes(scenario, topology.SystemsById);
|
||||||
var ships = CreateShips(
|
var ships = CreateShips(
|
||||||
scenario,
|
scenario,
|
||||||
topology.SystemsById,
|
topology.SystemsById,
|
||||||
topology.SpatialLayout.Celestials,
|
topology.SpatialLayout.Anchors,
|
||||||
patrolRoutes,
|
patrolRoutes,
|
||||||
stations);
|
stations);
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ public sealed class ScenarioContentBuilder(
|
|||||||
ScenarioDefinition scenario,
|
ScenarioDefinition scenario,
|
||||||
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
||||||
IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
|
IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
|
||||||
IReadOnlyCollection<CelestialRuntime> celestials)
|
IReadOnlyCollection<AnchorRuntime> anchors)
|
||||||
{
|
{
|
||||||
var stations = new List<StationRuntime>();
|
var stations = new List<StationRuntime>();
|
||||||
var stationIdCounter = 0;
|
var stationIdCounter = 0;
|
||||||
@@ -47,23 +47,27 @@ public sealed class ScenarioContentBuilder(
|
|||||||
throw new InvalidOperationException($"Scenario station '{plan.Label}' references unknown system '{plan.SystemId}'.");
|
throw new InvalidOperationException($"Scenario station '{plan.Label}' references unknown system '{plan.SystemId}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var placement = SpatialBuilder.ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], celestials);
|
var placement = SpatialBuilder.ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], anchors);
|
||||||
var station = new StationRuntime
|
var station = new StationRuntime
|
||||||
{
|
{
|
||||||
Id = $"station-{++stationIdCounter}",
|
Id = $"station-{++stationIdCounter}",
|
||||||
SystemId = system.Definition.Id,
|
SystemId = system.Definition.Id,
|
||||||
|
AnchorId = placement.Anchor.Id,
|
||||||
Label = plan.Label,
|
Label = plan.Label,
|
||||||
Color = plan.Color,
|
Color = plan.Color,
|
||||||
Objective = StationSimulationService.NormalizeStationObjective(plan.Objective),
|
Objective = StationSimulationService.NormalizeStationObjective(plan.Objective),
|
||||||
Position = placement.Position,
|
Position = Vector3.Zero,
|
||||||
FactionId = GetRequiredFactionId(plan.FactionId, $"station '{plan.Label}'"),
|
FactionId = GetRequiredFactionId(plan.FactionId, $"station '{plan.Label}'"),
|
||||||
CelestialId = placement.AnchorCelestial.Id,
|
|
||||||
Health = 600f,
|
Health = 600f,
|
||||||
MaxHealth = 600f,
|
MaxHealth = 600f,
|
||||||
};
|
};
|
||||||
|
|
||||||
stations.Add(station);
|
stations.Add(station);
|
||||||
placement.AnchorCelestial.OccupyingStructureId = station.Id;
|
placement.Anchor.OccupyingStructureId = station.Id;
|
||||||
|
if (placement.Celestial is not null)
|
||||||
|
{
|
||||||
|
placement.Celestial.OccupyingStructureId = station.Id;
|
||||||
|
}
|
||||||
|
|
||||||
var startingModules = BuildStartingModules(plan);
|
var startingModules = BuildStartingModules(plan);
|
||||||
foreach (var moduleId in startingModules)
|
foreach (var moduleId in startingModules)
|
||||||
@@ -162,7 +166,7 @@ public sealed class ScenarioContentBuilder(
|
|||||||
private List<ShipRuntime> CreateShips(
|
private List<ShipRuntime> CreateShips(
|
||||||
ScenarioDefinition scenario,
|
ScenarioDefinition scenario,
|
||||||
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
||||||
IReadOnlyCollection<CelestialRuntime> celestials,
|
IReadOnlyCollection<AnchorRuntime> anchors,
|
||||||
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
||||||
IReadOnlyCollection<StationRuntime> stations)
|
IReadOnlyCollection<StationRuntime> stations)
|
||||||
{
|
{
|
||||||
@@ -181,6 +185,8 @@ public sealed class ScenarioContentBuilder(
|
|||||||
var offset = new Vector3((index % 3) * 18f, balance.YPlane, (index / 3) * 18f);
|
var offset = new Vector3((index % 3) * 18f, balance.YPlane, (index / 3) * 18f);
|
||||||
var position = Add(NormalizeScenarioPoint(systemsById[formation.SystemId], formation.Center), offset);
|
var position = Add(NormalizeScenarioPoint(systemsById[formation.SystemId], formation.Center), offset);
|
||||||
var factionId = GetRequiredFactionId(formation.FactionId, $"ship formation '{formation.ShipId}' in system '{formation.SystemId}'");
|
var factionId = GetRequiredFactionId(formation.FactionId, $"ship formation '{formation.ShipId}' in system '{formation.SystemId}'");
|
||||||
|
var spatialState = SpatialBuilder.CreateInitialShipSpatialState(formation.SystemId, position, anchors);
|
||||||
|
var localPosition = spatialState.LocalPosition ?? Vector3.Zero;
|
||||||
|
|
||||||
ships.Add(new ShipRuntime
|
ships.Add(new ShipRuntime
|
||||||
{
|
{
|
||||||
@@ -188,9 +194,9 @@ public sealed class ScenarioContentBuilder(
|
|||||||
SystemId = formation.SystemId,
|
SystemId = formation.SystemId,
|
||||||
Definition = definition,
|
Definition = definition,
|
||||||
FactionId = factionId,
|
FactionId = factionId,
|
||||||
Position = position,
|
Position = localPosition,
|
||||||
TargetPosition = position,
|
TargetPosition = localPosition,
|
||||||
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(formation.SystemId, position, celestials),
|
SpatialState = spatialState,
|
||||||
DefaultBehavior = CreateBehavior(
|
DefaultBehavior = CreateBehavior(
|
||||||
definition,
|
definition,
|
||||||
formation.SystemId,
|
formation.SystemId,
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
|
|||||||
|
|
||||||
namespace SpaceGame.Api.Universe.Scenario;
|
namespace SpaceGame.Api.Universe.Scenario;
|
||||||
|
|
||||||
public sealed class SpatialBuilder(IBalanceService balance)
|
public sealed class SpatialBuilder
|
||||||
{
|
{
|
||||||
|
internal static bool IsConstructibleAnchorKind(SpatialNodeKind kind) => kind is SpatialNodeKind.Planet or SpatialNodeKind.Moon or SpatialNodeKind.LagrangePoint;
|
||||||
|
|
||||||
|
internal static string? ResolveCompatibleCelestialId(AnchorRuntime? anchor) =>
|
||||||
|
anchor is not null && string.Equals(anchor.SourceEntityKind, "celestial", StringComparison.Ordinal)
|
||||||
|
? anchor.SourceEntityId
|
||||||
|
: null;
|
||||||
|
|
||||||
internal ScenarioSpatialLayout BuildLayout(IReadOnlyList<SystemRuntime> systems)
|
internal ScenarioSpatialLayout BuildLayout(IReadOnlyList<SystemRuntime> systems)
|
||||||
{
|
{
|
||||||
var systemGraphs = systems.ToDictionary(
|
var systemGraphs = systems.ToDictionary(
|
||||||
@@ -11,6 +18,19 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
BuildSystemSpatialGraph,
|
BuildSystemSpatialGraph,
|
||||||
StringComparer.Ordinal);
|
StringComparer.Ordinal);
|
||||||
var celestials = systemGraphs.Values.SelectMany(graph => graph.Celestials).ToList();
|
var celestials = systemGraphs.Values.SelectMany(graph => graph.Celestials).ToList();
|
||||||
|
var anchors = celestials.Select(celestial => new AnchorRuntime
|
||||||
|
{
|
||||||
|
Id = celestial.Id,
|
||||||
|
SystemId = celestial.SystemId,
|
||||||
|
Kind = celestial.Kind,
|
||||||
|
Position = celestial.Position,
|
||||||
|
LocalSpaceRadius = celestial.LocalSpaceRadius,
|
||||||
|
ParentAnchorId = celestial.ParentAnchorId,
|
||||||
|
OrbitReferenceId = celestial.OrbitReferenceId,
|
||||||
|
OccupyingStructureId = celestial.OccupyingStructureId,
|
||||||
|
SourceEntityKind = "celestial",
|
||||||
|
SourceEntityId = celestial.Id,
|
||||||
|
}).ToList();
|
||||||
var nodes = new List<ResourceNodeRuntime>();
|
var nodes = new List<ResourceNodeRuntime>();
|
||||||
var nodeIdCounter = 0;
|
var nodeIdCounter = 0;
|
||||||
|
|
||||||
@@ -20,24 +40,43 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
foreach (var node in system.Definition.ResourceNodes)
|
foreach (var node in system.Definition.ResourceNodes)
|
||||||
{
|
{
|
||||||
var anchorCelestial = ResolveResourceNodeAnchor(systemGraph, node);
|
var anchorCelestial = ResolveResourceNodeAnchor(systemGraph, node);
|
||||||
|
var nodeId = $"node-{++nodeIdCounter}";
|
||||||
|
var localPosition = ComputeResourceNodeLocalPosition(node);
|
||||||
|
var anchorPosition = anchorCelestial is null
|
||||||
|
? localPosition
|
||||||
|
: Add(anchorCelestial.Position, localPosition);
|
||||||
nodes.Add(new ResourceNodeRuntime
|
nodes.Add(new ResourceNodeRuntime
|
||||||
{
|
{
|
||||||
Id = $"node-{++nodeIdCounter}",
|
Id = nodeId,
|
||||||
|
AnchorId = nodeId,
|
||||||
SystemId = system.Definition.Id,
|
SystemId = system.Definition.Id,
|
||||||
Position = ComputeResourceNodePosition(anchorCelestial, node, balance.YPlane),
|
Position = localPosition,
|
||||||
SourceKind = node.SourceKind,
|
SourceKind = node.SourceKind,
|
||||||
ItemId = node.ItemId,
|
ItemId = node.ItemId,
|
||||||
CelestialId = anchorCelestial?.Id,
|
LocalSpaceRadius = LocalSpaceRadius,
|
||||||
OrbitRadius = node.RadiusOffset,
|
OrbitRadius = node.RadiusOffset,
|
||||||
OrbitPhase = node.Angle,
|
OrbitPhase = node.Angle,
|
||||||
OrbitInclination = DegreesToRadians(node.InclinationDegrees),
|
OrbitInclination = DegreesToRadians(node.InclinationDegrees),
|
||||||
OreRemaining = node.OreAmount,
|
OreRemaining = node.OreAmount,
|
||||||
MaxOre = node.OreAmount,
|
MaxOre = node.OreAmount,
|
||||||
});
|
});
|
||||||
|
nodes[^1].Deposits.AddRange(BuildResourceDeposits(system.Definition.Id, nodeId, node, node.OreAmount));
|
||||||
|
|
||||||
|
anchors.Add(new AnchorRuntime
|
||||||
|
{
|
||||||
|
Id = nodeId,
|
||||||
|
SystemId = system.Definition.Id,
|
||||||
|
Kind = SpatialNodeKind.ResourceNode,
|
||||||
|
Position = anchorPosition,
|
||||||
|
LocalSpaceRadius = LocalSpaceRadius,
|
||||||
|
ParentAnchorId = anchorCelestial?.Id,
|
||||||
|
SourceEntityKind = "resource-node",
|
||||||
|
SourceEntityId = nodeId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ScenarioSpatialLayout(systemGraphs, celestials, nodes);
|
return new ScenarioSpatialLayout(systemGraphs, anchors, celestials, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system)
|
private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system)
|
||||||
@@ -70,7 +109,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
kind: SpatialNodeKind.Planet,
|
kind: SpatialNodeKind.Planet,
|
||||||
position: planetPosition,
|
position: planetPosition,
|
||||||
localSpaceRadius: LocalSpaceRadius,
|
localSpaceRadius: LocalSpaceRadius,
|
||||||
parentNodeId: primaryStarNodeId);
|
parentAnchorId: primaryStarNodeId);
|
||||||
|
|
||||||
var lagrangeNodes = new Dictionary<string, CelestialRuntime>(StringComparer.Ordinal);
|
var lagrangeNodes = new Dictionary<string, CelestialRuntime>(StringComparer.Ordinal);
|
||||||
foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet))
|
foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet))
|
||||||
@@ -82,7 +121,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
kind: SpatialNodeKind.LagrangePoint,
|
kind: SpatialNodeKind.LagrangePoint,
|
||||||
position: point.Position,
|
position: point.Position,
|
||||||
localSpaceRadius: LocalSpaceRadius,
|
localSpaceRadius: LocalSpaceRadius,
|
||||||
parentNodeId: planetCelestial.Id,
|
parentAnchorId: planetCelestial.Id,
|
||||||
orbitReferenceId: point.Designation);
|
orbitReferenceId: point.Designation);
|
||||||
lagrangeNodes[point.Designation] = lagrangeCelestial;
|
lagrangeNodes[point.Designation] = lagrangeCelestial;
|
||||||
}
|
}
|
||||||
@@ -100,7 +139,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
kind: SpatialNodeKind.Moon,
|
kind: SpatialNodeKind.Moon,
|
||||||
position: moonPosition,
|
position: moonPosition,
|
||||||
localSpaceRadius: LocalSpaceRadius,
|
localSpaceRadius: LocalSpaceRadius,
|
||||||
parentNodeId: planetCelestial.Id);
|
parentAnchorId: planetCelestial.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +153,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
SpatialNodeKind kind,
|
SpatialNodeKind kind,
|
||||||
Vector3 position,
|
Vector3 position,
|
||||||
float localSpaceRadius,
|
float localSpaceRadius,
|
||||||
string? parentNodeId = null,
|
string? parentAnchorId = null,
|
||||||
string? orbitReferenceId = null)
|
string? orbitReferenceId = null)
|
||||||
{
|
{
|
||||||
var celestial = new CelestialRuntime
|
var celestial = new CelestialRuntime
|
||||||
@@ -124,7 +163,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
Kind = kind,
|
Kind = kind,
|
||||||
Position = position,
|
Position = position,
|
||||||
LocalSpaceRadius = localSpaceRadius,
|
LocalSpaceRadius = localSpaceRadius,
|
||||||
ParentNodeId = parentNodeId,
|
ParentAnchorId = parentAnchorId,
|
||||||
OrbitReferenceId = orbitReferenceId,
|
OrbitReferenceId = orbitReferenceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,7 +222,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
InitialStationDefinition plan,
|
InitialStationDefinition plan,
|
||||||
SystemRuntime system,
|
SystemRuntime system,
|
||||||
SystemSpatialGraph graph,
|
SystemSpatialGraph graph,
|
||||||
IReadOnlyCollection<CelestialRuntime> existingCelestials)
|
IReadOnlyCollection<AnchorRuntime> existingAnchors)
|
||||||
{
|
{
|
||||||
if (plan.PlanetIndex is int planetIndex &&
|
if (plan.PlanetIndex is int planetIndex &&
|
||||||
graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes))
|
graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes))
|
||||||
@@ -191,28 +230,32 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
var designation = ResolveLagrangeDesignation(plan.LagrangeSide);
|
var designation = ResolveLagrangeDesignation(plan.LagrangeSide);
|
||||||
if (lagrangeNodes.TryGetValue(designation, out var lagrangeCelestial))
|
if (lagrangeNodes.TryGetValue(designation, out var lagrangeCelestial))
|
||||||
{
|
{
|
||||||
return new StationPlacement(lagrangeCelestial, lagrangeCelestial.Position);
|
var lagrangeAnchor = existingAnchors.First(anchor => string.Equals(anchor.Id, lagrangeCelestial.Id, StringComparison.Ordinal));
|
||||||
|
return new StationPlacement(lagrangeAnchor, lagrangeCelestial, lagrangeAnchor.Position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan.Position is { Length: 3 })
|
if (plan.Position is { Length: 3 })
|
||||||
{
|
{
|
||||||
var targetPosition = NormalizeScenarioPoint(system, plan.Position);
|
var targetPosition = NormalizeScenarioPoint(system, plan.Position);
|
||||||
var preferredCelestial = existingCelestials
|
var preferredAnchor = existingAnchors
|
||||||
.Where(c => c.SystemId == system.Definition.Id && c.Kind == SpatialNodeKind.LagrangePoint)
|
.Where(anchor => anchor.SystemId == system.Definition.Id && anchor.Kind == SpatialNodeKind.LagrangePoint)
|
||||||
.OrderBy(c => c.Position.DistanceTo(targetPosition))
|
.OrderBy(anchor => anchor.Position.DistanceTo(targetPosition))
|
||||||
.FirstOrDefault()
|
.FirstOrDefault()
|
||||||
?? existingCelestials
|
?? existingAnchors
|
||||||
.Where(c => c.SystemId == system.Definition.Id)
|
.Where(anchor => anchor.SystemId == system.Definition.Id && IsConstructibleAnchorKind(anchor.Kind))
|
||||||
.OrderBy(c => c.Position.DistanceTo(targetPosition))
|
.OrderBy(anchor => anchor.Position.DistanceTo(targetPosition))
|
||||||
.First();
|
.First();
|
||||||
return new StationPlacement(preferredCelestial, preferredCelestial.Position);
|
var preferredCelestial = graph.Celestials.FirstOrDefault(c => string.Equals(c.Id, ResolveCompatibleCelestialId(preferredAnchor), StringComparison.Ordinal));
|
||||||
|
return new StationPlacement(preferredAnchor, preferredCelestial, preferredAnchor.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackCelestial = graph.Celestials
|
var fallbackAnchor = existingAnchors
|
||||||
.FirstOrDefault(c => c.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(c.OccupyingStructureId))
|
.Where(anchor => anchor.SystemId == system.Definition.Id)
|
||||||
?? graph.Celestials.First(c => c.Kind == SpatialNodeKind.Planet);
|
.FirstOrDefault(anchor => anchor.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(anchor.OccupyingStructureId))
|
||||||
return new StationPlacement(fallbackCelestial, fallbackCelestial.Position);
|
?? existingAnchors.First(anchor => anchor.SystemId == system.Definition.Id && anchor.Kind == SpatialNodeKind.Planet);
|
||||||
|
var fallbackCelestial = graph.Celestials.FirstOrDefault(c => string.Equals(c.Id, ResolveCompatibleCelestialId(fallbackAnchor), StringComparison.Ordinal));
|
||||||
|
return new StationPlacement(fallbackAnchor, fallbackCelestial, fallbackAnchor.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch
|
private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch
|
||||||
@@ -256,20 +299,80 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
return graph.Celestials.FirstOrDefault(c => c.Id == planetNodeId);
|
return graph.Celestials.FirstOrDefault(c => c.Id == planetNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector3 ComputeResourceNodePosition(CelestialRuntime? anchorCelestial, ResourceNodeDefinition definition, float yPlane)
|
private static Vector3 ComputeResourceNodeLocalPosition(ResourceNodeDefinition definition)
|
||||||
{
|
{
|
||||||
var verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.04f, 25000f);
|
var verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.04f, 25000f);
|
||||||
var offset = new Vector3(
|
return new Vector3(
|
||||||
MathF.Cos(definition.Angle) * definition.RadiusOffset,
|
MathF.Cos(definition.Angle) * definition.RadiusOffset,
|
||||||
verticalOffset,
|
verticalOffset,
|
||||||
MathF.Sin(definition.Angle) * definition.RadiusOffset);
|
MathF.Sin(definition.Angle) * definition.RadiusOffset);
|
||||||
|
}
|
||||||
|
|
||||||
if (anchorCelestial is null)
|
private static IReadOnlyList<ResourceDepositRuntime> BuildResourceDeposits(
|
||||||
|
string systemId,
|
||||||
|
string nodeId,
|
||||||
|
ResourceNodeDefinition definition,
|
||||||
|
float oreAmount)
|
||||||
|
{
|
||||||
|
var depositCount = Math.Clamp((int)MathF.Round(MathF.Sqrt(MathF.Max(oreAmount, 1f)) / 18f), 4, 12);
|
||||||
|
var deposits = new List<ResourceDepositRuntime>(depositCount);
|
||||||
|
var weightTotal = 0f;
|
||||||
|
var weights = new float[depositCount];
|
||||||
|
for (var index = 0; index < depositCount; index += 1)
|
||||||
{
|
{
|
||||||
return new Vector3(offset.X, yPlane + offset.Y, offset.Z);
|
var weight = 0.8f + (Hash01(systemId, nodeId, $"weight-{index}") * 1.6f);
|
||||||
|
weights[index] = weight;
|
||||||
|
weightTotal += weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Add(anchorCelestial.Position, offset);
|
var scatterRadius = MathF.Max(140f, LocalSpaceRadius * 0.58f);
|
||||||
|
for (var index = 0; index < depositCount; index += 1)
|
||||||
|
{
|
||||||
|
var angle = Hash01(systemId, nodeId, $"angle-{index}") * MathF.PI * 2f;
|
||||||
|
var radiusFactor = 0.22f + (Hash01(systemId, nodeId, $"radius-{index}") * 0.74f);
|
||||||
|
var radius = scatterRadius * MathF.Sqrt(radiusFactor);
|
||||||
|
var vertical = (Hash01(systemId, nodeId, $"vertical-{index}") - 0.5f) * MathF.Max(60f, scatterRadius * 0.14f);
|
||||||
|
var localPosition = new Vector3(
|
||||||
|
MathF.Cos(angle) * radius,
|
||||||
|
vertical,
|
||||||
|
MathF.Sin(angle) * radius);
|
||||||
|
var maxOre = oreAmount * (weights[index] / MathF.Max(weightTotal, 0.001f));
|
||||||
|
deposits.Add(new ResourceDepositRuntime
|
||||||
|
{
|
||||||
|
Id = $"{nodeId}-deposit-{index + 1}",
|
||||||
|
NodeId = nodeId,
|
||||||
|
AnchorId = nodeId,
|
||||||
|
Position = localPosition,
|
||||||
|
OreRemaining = maxOre,
|
||||||
|
MaxOre = maxOre,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return deposits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float Hash01(string systemId, string nodeId, string salt)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
var hash = 17;
|
||||||
|
foreach (var character in systemId)
|
||||||
|
{
|
||||||
|
hash = (hash * 31) + character;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var character in nodeId)
|
||||||
|
{
|
||||||
|
hash = (hash * 31) + character;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var character in salt)
|
||||||
|
{
|
||||||
|
hash = (hash * 31) + character;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (hash & 0x7fffffff) / (float)int.MaxValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector3 ComputePlanetPosition(PlanetDefinition planet)
|
private static Vector3 ComputePlanetPosition(PlanetDefinition planet)
|
||||||
@@ -286,19 +389,22 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
return Add(planetPosition, local);
|
return Add(planetPosition, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<CelestialRuntime> celestials)
|
internal static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<AnchorRuntime> anchors)
|
||||||
{
|
{
|
||||||
var nearestCelestial = celestials
|
var nearestAnchor = anchors
|
||||||
.Where(c => c.SystemId == systemId)
|
.Where(anchor => anchor.SystemId == systemId)
|
||||||
.OrderBy(c => c.Position.DistanceTo(position))
|
.OrderBy(anchor => anchor.Position.DistanceTo(position))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
var localPosition = nearestAnchor is null
|
||||||
|
? position
|
||||||
|
: position.Subtract(nearestAnchor.Position);
|
||||||
|
|
||||||
return new ShipSpatialStateRuntime
|
return new ShipSpatialStateRuntime
|
||||||
{
|
{
|
||||||
CurrentSystemId = systemId,
|
CurrentSystemId = systemId,
|
||||||
SpaceLayer = SpaceLayerKind.LocalSpace,
|
SpaceLayer = SpaceLayerKind.LocalSpace,
|
||||||
CurrentCelestialId = nearestCelestial?.Id,
|
CurrentAnchorId = nearestAnchor?.Id,
|
||||||
LocalPosition = position,
|
LocalPosition = localPosition,
|
||||||
SystemPosition = position,
|
SystemPosition = position,
|
||||||
MovementRegime = MovementRegimeKind.LocalFlight,
|
MovementRegime = MovementRegimeKind.LocalFlight,
|
||||||
};
|
};
|
||||||
@@ -307,6 +413,7 @@ public sealed class SpatialBuilder(IBalanceService balance)
|
|||||||
|
|
||||||
public sealed record ScenarioSpatialLayout(
|
public sealed record ScenarioSpatialLayout(
|
||||||
IReadOnlyDictionary<string, SystemSpatialGraph> SystemGraphs,
|
IReadOnlyDictionary<string, SystemSpatialGraph> SystemGraphs,
|
||||||
|
List<AnchorRuntime> Anchors,
|
||||||
List<CelestialRuntime> Celestials,
|
List<CelestialRuntime> Celestials,
|
||||||
List<ResourceNodeRuntime> Nodes);
|
List<ResourceNodeRuntime> Nodes);
|
||||||
|
|
||||||
@@ -317,4 +424,4 @@ public sealed record SystemSpatialGraph(
|
|||||||
|
|
||||||
internal sealed record LagrangePointPlacement(string Designation, Vector3 Position);
|
internal sealed record LagrangePointPlacement(string Designation, Vector3 Position);
|
||||||
|
|
||||||
internal sealed record StationPlacement(CelestialRuntime AnchorCelestial, Vector3 Position);
|
internal sealed record StationPlacement(AnchorRuntime Anchor, CelestialRuntime? Celestial, Vector3 Position);
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ public sealed class WorldRuntimeAssembler(
|
|||||||
var policies = seedingService.CreatePolicies(factions);
|
var policies = seedingService.CreatePolicies(factions);
|
||||||
var commanders = seedingService.CreateCommanders(factions, content.Stations, content.Ships);
|
var commanders = seedingService.CreateCommanders(factions, content.Stations, content.Ships);
|
||||||
var nowUtc = DateTimeOffset.UtcNow;
|
var nowUtc = DateTimeOffset.UtcNow;
|
||||||
var claims = seedingService.CreateClaims(content.Stations, topology.SpatialLayout.Celestials, nowUtc);
|
var claims = seedingService.CreateClaims(content.Stations, topology.SpatialLayout.Anchors, nowUtc);
|
||||||
|
|
||||||
var world = new SimulationWorld
|
var world = new SimulationWorld
|
||||||
{
|
{
|
||||||
Label = "Split Viewer / Simulation World",
|
Label = "Split Viewer / Simulation World",
|
||||||
Seed = worldGenerationOptions.Seed,
|
Seed = worldGenerationOptions.Seed,
|
||||||
Systems = topology.SystemRuntimes.ToList(),
|
Systems = topology.SystemRuntimes.ToList(),
|
||||||
|
Anchors = topology.SpatialLayout.Anchors,
|
||||||
Celestials = topology.SpatialLayout.Celestials,
|
Celestials = topology.SpatialLayout.Celestials,
|
||||||
Nodes = topology.SpatialLayout.Nodes,
|
Nodes = topology.SpatialLayout.Nodes,
|
||||||
Wrecks = [],
|
Wrecks = [],
|
||||||
|
|||||||
@@ -74,27 +74,27 @@ public sealed class WorldSeedingService(IStaticDataProvider staticData)
|
|||||||
|
|
||||||
internal List<ClaimRuntime> CreateClaims(
|
internal List<ClaimRuntime> CreateClaims(
|
||||||
IReadOnlyCollection<StationRuntime> stations,
|
IReadOnlyCollection<StationRuntime> stations,
|
||||||
IReadOnlyCollection<CelestialRuntime> celestials,
|
IReadOnlyCollection<AnchorRuntime> anchors,
|
||||||
DateTimeOffset nowUtc)
|
DateTimeOffset nowUtc)
|
||||||
{
|
{
|
||||||
var stationsByCelestialId = stations
|
var stationsByAnchorId = stations
|
||||||
.Where(station => station.CelestialId is not null)
|
.Where(station => !string.IsNullOrWhiteSpace(station.AnchorId))
|
||||||
.ToDictionary(station => station.CelestialId!, StringComparer.Ordinal);
|
.ToDictionary(station => station.AnchorId!, StringComparer.Ordinal);
|
||||||
var claims = new List<ClaimRuntime>();
|
var claims = new List<ClaimRuntime>();
|
||||||
|
|
||||||
foreach (var celestial in celestials.Where(c => c.Kind == SpatialNodeKind.LagrangePoint))
|
foreach (var anchor in anchors.Where(candidate => candidate.Kind == SpatialNodeKind.LagrangePoint))
|
||||||
{
|
{
|
||||||
if (!stationsByCelestialId.TryGetValue(celestial.Id, out var station))
|
if (!stationsByAnchorId.TryGetValue(anchor.Id, out var station))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
claims.Add(new ClaimRuntime
|
claims.Add(new ClaimRuntime
|
||||||
{
|
{
|
||||||
Id = $"claim-{celestial.Id}",
|
Id = $"claim-{anchor.Id}",
|
||||||
FactionId = station.FactionId,
|
FactionId = station.FactionId,
|
||||||
SystemId = celestial.SystemId,
|
SystemId = anchor.SystemId,
|
||||||
CelestialId = celestial.Id,
|
AnchorId = anchor.Id,
|
||||||
PlacedAtUtc = nowUtc,
|
PlacedAtUtc = nowUtc,
|
||||||
ActivatesAtUtc = nowUtc.AddSeconds(8),
|
ActivatesAtUtc = nowUtc.AddSeconds(8),
|
||||||
State = ClaimStateKinds.Activating,
|
State = ClaimStateKinds.Activating,
|
||||||
@@ -119,12 +119,12 @@ public sealed class WorldSeedingService(IStaticDataProvider staticData)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var moduleId = InfrastructureSimulationService.GetNextStationModuleToBuild(station, world);
|
var moduleId = InfrastructureSimulationService.GetNextStationModuleToBuild(station, world);
|
||||||
if (moduleId is null || station.CelestialId is null)
|
if (moduleId is null || station.AnchorId is null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var claim = world.Claims.FirstOrDefault(candidate => candidate.CelestialId == station.CelestialId);
|
var claim = world.Claims.FirstOrDefault(candidate => string.Equals(candidate.AnchorId, station.AnchorId, StringComparison.Ordinal));
|
||||||
if (claim is null || !world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
|
if (claim is null || !world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -135,7 +135,7 @@ public sealed class WorldSeedingService(IStaticDataProvider staticData)
|
|||||||
Id = $"site-{station.Id}",
|
Id = $"site-{station.Id}",
|
||||||
FactionId = station.FactionId,
|
FactionId = station.FactionId,
|
||||||
SystemId = station.SystemId,
|
SystemId = station.SystemId,
|
||||||
CelestialId = station.CelestialId,
|
AnchorId = station.AnchorId,
|
||||||
TargetKind = "station-module",
|
TargetKind = "station-module",
|
||||||
TargetDefinitionId = "station",
|
TargetDefinitionId = "station",
|
||||||
BlueprintId = moduleId,
|
BlueprintId = moduleId,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;
|
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;
|
||||||
|
|
||||||
|
using SpaceGame.Api.Universe.Scenario;
|
||||||
|
|
||||||
namespace SpaceGame.Api.Universe.Simulation;
|
namespace SpaceGame.Api.Universe.Simulation;
|
||||||
|
|
||||||
internal sealed class OrbitalStateUpdater
|
internal sealed class OrbitalStateUpdater
|
||||||
@@ -223,22 +225,47 @@ internal sealed class OrbitalStateUpdater
|
|||||||
|
|
||||||
foreach (var station in world.Stations)
|
foreach (var station in world.Stations)
|
||||||
{
|
{
|
||||||
if (station.CelestialId is null || !celestialsById.TryGetValue(station.CelestialId, out var anchorCelestial))
|
if (station.AnchorId is not null && world.Anchors.Any(candidate => candidate.Id == station.AnchorId))
|
||||||
{
|
{
|
||||||
continue;
|
station.Position = Vector3.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
station.Position = anchorCelestial.Position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var node in world.Nodes)
|
foreach (var node in world.Nodes)
|
||||||
{
|
{
|
||||||
if (node.CelestialId is null || !celestialsById.TryGetValue(node.CelestialId, out var anchorCelestial))
|
node.Position = ComputeResourceNodeOffset(node, worldTimeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeAnchorsById = world.Nodes.ToDictionary(node => node.AnchorId, StringComparer.Ordinal);
|
||||||
|
foreach (var anchor in world.Anchors)
|
||||||
|
{
|
||||||
|
if (string.Equals(anchor.SourceEntityKind, "resource-node", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
|
if (nodeAnchorsById.TryGetValue(anchor.Id, out var node))
|
||||||
|
{
|
||||||
|
if (anchor.ParentAnchorId is not null && celestialsById.TryGetValue(anchor.ParentAnchorId, out var anchorCelestial))
|
||||||
|
{
|
||||||
|
anchor.Position = Add(anchorCelestial.Position, node.Position);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
anchor.Position = node.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchor.LocalSpaceRadius = node.LocalSpaceRadius;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Position = Add(anchorCelestial.Position, ComputeResourceNodeOffset(node, worldTimeSeconds));
|
if (celestialsById.TryGetValue(anchor.Id, out var celestial))
|
||||||
|
{
|
||||||
|
anchor.Position = celestial.Position;
|
||||||
|
anchor.LocalSpaceRadius = celestial.LocalSpaceRadius;
|
||||||
|
anchor.ParentAnchorId = celestial.ParentAnchorId;
|
||||||
|
anchor.OccupyingStructureId = celestial.OccupyingStructureId;
|
||||||
|
anchor.OrbitReferenceId = celestial.OrbitReferenceId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var ship in world.Ships.Where(ship => ship.DockedStationId is not null))
|
foreach (var ship in world.Ships.Where(ship => ship.DockedStationId is not null))
|
||||||
@@ -261,20 +288,29 @@ internal sealed class OrbitalStateUpdater
|
|||||||
{
|
{
|
||||||
ship.SpatialState.CurrentSystemId = ship.SystemId;
|
ship.SpatialState.CurrentSystemId = ship.SystemId;
|
||||||
ship.SpatialState.LocalPosition = ship.Position;
|
ship.SpatialState.LocalPosition = ship.Position;
|
||||||
ship.SpatialState.SystemPosition = ship.Position;
|
|
||||||
if (ship.SpatialState.Transit is not null)
|
if (ship.SpatialState.Transit is not null)
|
||||||
{
|
{
|
||||||
ship.SpatialState.CurrentCelestialId = null;
|
ship.SpatialState.CurrentAnchorId = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||||
var nearestCelestial = world.Celestials
|
var currentAnchor = ship.SpatialState.CurrentAnchorId is not null
|
||||||
.Where(candidate => candidate.SystemId == ship.SystemId)
|
? world.Anchors.FirstOrDefault(candidate => candidate.Id == ship.SpatialState.CurrentAnchorId)
|
||||||
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
: null;
|
||||||
.FirstOrDefault();
|
if (currentAnchor is null || !string.Equals(currentAnchor.SystemId, ship.SystemId, StringComparison.Ordinal))
|
||||||
ship.SpatialState.CurrentCelestialId = nearestCelestial?.Id;
|
{
|
||||||
|
currentAnchor = world.Anchors
|
||||||
|
.Where(candidate => candidate.SystemId == ship.SystemId)
|
||||||
|
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
ship.SpatialState.CurrentAnchorId = currentAnchor?.Id;
|
||||||
|
ship.SpatialState.SystemPosition = currentAnchor is null
|
||||||
|
? ship.Position
|
||||||
|
: Add(currentAnchor.Position, ship.Position);
|
||||||
|
|
||||||
if (ship.DockedStationId is null)
|
if (ship.DockedStationId is null)
|
||||||
{
|
{
|
||||||
@@ -282,9 +318,9 @@ internal sealed class OrbitalStateUpdater
|
|||||||
}
|
}
|
||||||
|
|
||||||
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
|
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
|
||||||
if (station?.CelestialId is not null)
|
if (station is not null)
|
||||||
{
|
{
|
||||||
ship.SpatialState.CurrentCelestialId = station.CelestialId;
|
ship.SpatialState.CurrentAnchorId = station.AnchorId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,6 +315,8 @@ public sealed class WorldService
|
|||||||
string.Equals(candidate.FactionId, faction.Id, StringComparison.Ordinal)
|
string.Equals(candidate.FactionId, faction.Id, StringComparison.Ordinal)
|
||||||
&& string.Equals(candidate.SystemId, system.Definition.Id, StringComparison.Ordinal));
|
&& string.Equals(candidate.SystemId, system.Definition.Id, StringComparison.Ordinal));
|
||||||
var defaultBehavior = CreateSpawnBehavior(request, definition, system.Definition.Id, homeStation);
|
var defaultBehavior = CreateSpawnBehavior(request, definition, system.Definition.Id, homeStation);
|
||||||
|
var spatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Anchors);
|
||||||
|
var localPosition = spatialState.LocalPosition ?? Vector3.Zero;
|
||||||
|
|
||||||
var ship = new ShipRuntime
|
var ship = new ShipRuntime
|
||||||
{
|
{
|
||||||
@@ -322,9 +324,9 @@ public sealed class WorldService
|
|||||||
SystemId = system.Definition.Id,
|
SystemId = system.Definition.Id,
|
||||||
Definition = definition,
|
Definition = definition,
|
||||||
FactionId = faction.Id,
|
FactionId = faction.Id,
|
||||||
Position = spawnPosition,
|
Position = localPosition,
|
||||||
TargetPosition = spawnPosition,
|
TargetPosition = localPosition,
|
||||||
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Celestials),
|
SpatialState = spatialState,
|
||||||
DefaultBehavior = defaultBehavior,
|
DefaultBehavior = defaultBehavior,
|
||||||
Skills = ShipBootstrapPolicy.CreateSkills(definition),
|
Skills = ShipBootstrapPolicy.CreateSkills(definition),
|
||||||
Health = definition.Hull,
|
Health = definition.Hull,
|
||||||
@@ -352,15 +354,18 @@ public sealed class WorldService
|
|||||||
? $"{faction.Label} {ToTitleCaseToken(objective)} {CountFactionStationsInSystem(faction.Id, system.Definition.Id) + 1}"
|
? $"{faction.Label} {ToTitleCaseToken(objective)} {CountFactionStationsInSystem(faction.Id, system.Definition.Id) + 1}"
|
||||||
: request.Label.Trim();
|
: request.Label.Trim();
|
||||||
var stationId = $"station-{faction.Id}-{objective}-{Guid.NewGuid():N}".ToLowerInvariant();
|
var stationId = $"station-{faction.Id}-{objective}-{Guid.NewGuid():N}".ToLowerInvariant();
|
||||||
var position = ResolveStationSpawnPosition(system.Definition.Id);
|
var requestedPosition = ResolveStationSpawnPosition(system.Definition.Id);
|
||||||
|
var anchor = ResolveNearestConstructibleAnchor(system.Definition.Id, requestedPosition)
|
||||||
|
?? throw new InvalidOperationException($"System '{system.Definition.Id}' does not have a valid constructible anchor for station spawning.");
|
||||||
var station = new StationRuntime
|
var station = new StationRuntime
|
||||||
{
|
{
|
||||||
Id = stationId,
|
Id = stationId,
|
||||||
SystemId = system.Definition.Id,
|
SystemId = system.Definition.Id,
|
||||||
|
AnchorId = anchor.Id,
|
||||||
Label = label,
|
Label = label,
|
||||||
Color = faction.Color,
|
Color = faction.Color,
|
||||||
Objective = objective,
|
Objective = objective,
|
||||||
Position = position,
|
Position = Vector3.Zero,
|
||||||
FactionId = faction.Id,
|
FactionId = faction.Id,
|
||||||
PolicySetId = faction.DefaultPolicySetId,
|
PolicySetId = faction.DefaultPolicySetId,
|
||||||
Health = 600f,
|
Health = 600f,
|
||||||
@@ -375,6 +380,7 @@ public sealed class WorldService
|
|||||||
station.PopulationCapacity = GetStationSupportedPopulation(_world.ModuleDefinitions, station);
|
station.PopulationCapacity = GetStationSupportedPopulation(_world.ModuleDefinitions, station);
|
||||||
station.WorkforceRequired = GetStationRequiredWorkforce(_world.ModuleDefinitions, station);
|
station.WorkforceRequired = GetStationRequiredWorkforce(_world.ModuleDefinitions, station);
|
||||||
_world.Stations.Add(station);
|
_world.Stations.Add(station);
|
||||||
|
anchor.OccupyingStructureId = station.Id;
|
||||||
|
|
||||||
new GeopoliticalSimulationService().Update(_world, 0f, []);
|
new GeopoliticalSimulationService().Update(_world, 0f, []);
|
||||||
PublishSnapshotRefreshUnsafe("spawn-station", $"Spawned station {station.Id}", "station", station.Id);
|
PublishSnapshotRefreshUnsafe("spawn-station", $"Spawned station {station.Id}", "station", station.Id);
|
||||||
@@ -490,6 +496,7 @@ public sealed class WorldService
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
|
[],
|
||||||
null);
|
null);
|
||||||
|
|
||||||
_history.Enqueue(worldDelta);
|
_history.Enqueue(worldDelta);
|
||||||
@@ -526,6 +533,7 @@ public sealed class WorldService
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
|
[],
|
||||||
null);
|
null);
|
||||||
|
|
||||||
_history.Enqueue(worldDelta);
|
_history.Enqueue(worldDelta);
|
||||||
@@ -608,6 +616,8 @@ public sealed class WorldService
|
|||||||
var shipId = $"ship-{playerFaction.Id}-{definition.Id}-{Guid.NewGuid():N}".ToLowerInvariant();
|
var shipId = $"ship-{playerFaction.Id}-{definition.Id}-{Guid.NewGuid():N}".ToLowerInvariant();
|
||||||
var spawnPosition = ResolveSpawnPosition(system.Definition.Id);
|
var spawnPosition = ResolveSpawnPosition(system.Definition.Id);
|
||||||
var defaultBehavior = CreateSpawnBehavior(request, definition, system.Definition.Id, null);
|
var defaultBehavior = CreateSpawnBehavior(request, definition, system.Definition.Id, null);
|
||||||
|
var spatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Anchors);
|
||||||
|
var localPosition = spatialState.LocalPosition ?? Vector3.Zero;
|
||||||
|
|
||||||
var ship = new ShipRuntime
|
var ship = new ShipRuntime
|
||||||
{
|
{
|
||||||
@@ -615,9 +625,9 @@ public sealed class WorldService
|
|||||||
SystemId = system.Definition.Id,
|
SystemId = system.Definition.Id,
|
||||||
Definition = definition,
|
Definition = definition,
|
||||||
FactionId = playerFaction.Id,
|
FactionId = playerFaction.Id,
|
||||||
Position = spawnPosition,
|
Position = localPosition,
|
||||||
TargetPosition = spawnPosition,
|
TargetPosition = localPosition,
|
||||||
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Celestials),
|
SpatialState = spatialState,
|
||||||
DefaultBehavior = defaultBehavior,
|
DefaultBehavior = defaultBehavior,
|
||||||
Skills = ShipBootstrapPolicy.CreateSkills(definition),
|
Skills = ShipBootstrapPolicy.CreateSkills(definition),
|
||||||
Health = definition.Hull,
|
Health = definition.Hull,
|
||||||
@@ -712,7 +722,7 @@ public sealed class WorldService
|
|||||||
SourceStationId = request.SourceStationId,
|
SourceStationId = request.SourceStationId,
|
||||||
DestinationStationId = request.DestinationStationId,
|
DestinationStationId = request.DestinationStationId,
|
||||||
ItemId = request.ItemId,
|
ItemId = request.ItemId,
|
||||||
NodeId = request.NodeId,
|
AnchorId = request.AnchorId,
|
||||||
ConstructionSiteId = request.ConstructionSiteId,
|
ConstructionSiteId = request.ConstructionSiteId,
|
||||||
ModuleId = request.ModuleId,
|
ModuleId = request.ModuleId,
|
||||||
WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? 0f),
|
WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? 0f),
|
||||||
@@ -780,7 +790,7 @@ public sealed class WorldService
|
|||||||
ship.DefaultBehavior.AreaSystemId = request.AreaSystemId;
|
ship.DefaultBehavior.AreaSystemId = request.AreaSystemId;
|
||||||
ship.DefaultBehavior.TargetEntityId = request.TargetEntityId;
|
ship.DefaultBehavior.TargetEntityId = request.TargetEntityId;
|
||||||
ship.DefaultBehavior.ItemId = request.ItemId;
|
ship.DefaultBehavior.ItemId = request.ItemId;
|
||||||
ship.DefaultBehavior.PreferredNodeId = request.PreferredNodeId;
|
ship.DefaultBehavior.PreferredAnchorId = request.PreferredAnchorId;
|
||||||
ship.DefaultBehavior.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
ship.DefaultBehavior.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
||||||
ship.DefaultBehavior.PreferredModuleId = request.PreferredModuleId;
|
ship.DefaultBehavior.PreferredModuleId = request.PreferredModuleId;
|
||||||
ship.DefaultBehavior.TargetPosition = request.TargetPosition is null
|
ship.DefaultBehavior.TargetPosition = request.TargetPosition is null
|
||||||
@@ -807,7 +817,7 @@ public sealed class WorldService
|
|||||||
SourceStationId = template.SourceStationId,
|
SourceStationId = template.SourceStationId,
|
||||||
DestinationStationId = template.DestinationStationId,
|
DestinationStationId = template.DestinationStationId,
|
||||||
ItemId = template.ItemId,
|
ItemId = template.ItemId,
|
||||||
NodeId = template.NodeId,
|
AnchorId = template.AnchorId,
|
||||||
ConstructionSiteId = template.ConstructionSiteId,
|
ConstructionSiteId = template.ConstructionSiteId,
|
||||||
ModuleId = template.ModuleId,
|
ModuleId = template.ModuleId,
|
||||||
WaitSeconds = template.WaitSeconds ?? 0f,
|
WaitSeconds = template.WaitSeconds ?? 0f,
|
||||||
@@ -905,6 +915,16 @@ public sealed class WorldService
|
|||||||
return new Vector3(MathF.Cos(angle) * radius, 0f, MathF.Sin(angle) * radius);
|
return new Vector3(MathF.Cos(angle) * radius, 0f, MathF.Sin(angle) * radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AnchorRuntime? ResolveNearestConstructibleAnchor(string systemId, Vector3 position) =>
|
||||||
|
_world.Anchors
|
||||||
|
.Where(candidate => string.Equals(candidate.SystemId, systemId, StringComparison.Ordinal))
|
||||||
|
.Where(candidate => SpatialBuilder.IsConstructibleAnchorKind(candidate.Kind))
|
||||||
|
.OrderBy(candidate => candidate.Position.DistanceTo(position))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
private string? ResolveNearestAnchorId(string systemId, Vector3 position) =>
|
||||||
|
ResolveNearestConstructibleAnchor(systemId, position)?.Id;
|
||||||
|
|
||||||
private IReadOnlyList<string> BuildStarterStationModules(string factionId, string objective)
|
private IReadOnlyList<string> BuildStarterStationModules(string factionId, string objective)
|
||||||
{
|
{
|
||||||
var modules = new List<string>();
|
var modules = new List<string>();
|
||||||
@@ -1079,9 +1099,9 @@ public sealed class WorldService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var systemFilter = scope.SystemId;
|
var systemFilter = scope.SystemId;
|
||||||
if (string.Equals(scope.ScopeKind, "local-celestial", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.CelestialId is not null)
|
if (string.Equals(scope.ScopeKind, "local-anchor", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.AnchorId is not null)
|
||||||
{
|
{
|
||||||
systemFilter = ResolveCelestialSystemId(scope.CelestialId);
|
systemFilter = ResolveAnchorSystemId(scope.AnchorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return delta with
|
return delta with
|
||||||
@@ -1091,6 +1111,7 @@ public sealed class WorldService
|
|||||||
.Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter))
|
.Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter))
|
||||||
.ToList(),
|
.ToList(),
|
||||||
Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(),
|
Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(),
|
||||||
|
Anchors = delta.Anchors.Where((anchor) => systemFilter is null || anchor.SystemId == systemFilter).ToList(),
|
||||||
Nodes = delta.Nodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(),
|
Nodes = delta.Nodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(),
|
||||||
Stations = delta.Stations.Where((station) => systemFilter is null || station.SystemId == systemFilter).ToList(),
|
Stations = delta.Stations.Where((station) => systemFilter is null || station.SystemId == systemFilter).ToList(),
|
||||||
Claims = delta.Claims.Where((claim) => systemFilter is null || claim.SystemId == systemFilter).ToList(),
|
Claims = delta.Claims.Where((claim) => systemFilter is null || claim.SystemId == systemFilter).ToList(),
|
||||||
@@ -1136,8 +1157,8 @@ public sealed class WorldService
|
|||||||
ScopeEntityId = scopeEntityId,
|
ScopeEntityId = scopeEntityId,
|
||||||
};
|
};
|
||||||
|
|
||||||
private string? ResolveCelestialSystemId(string celestialId) =>
|
private string? ResolveAnchorSystemId(string anchorId) =>
|
||||||
_world.Celestials.FirstOrDefault((c) => c.Id == celestialId)?.SystemId;
|
_world.Anchors.FirstOrDefault((anchor) => anchor.Id == anchorId)?.SystemId;
|
||||||
|
|
||||||
private string? ResolveMarketOrderSystemId(string orderId)
|
private string? ResolveMarketOrderSystemId(string orderId)
|
||||||
{
|
{
|
||||||
@@ -1181,7 +1202,7 @@ public sealed class WorldService
|
|||||||
{
|
{
|
||||||
"universe" => true,
|
"universe" => true,
|
||||||
"system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
"system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
||||||
"local-celestial" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
"local-anchor" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
import { GameViewer } from "./GameViewer";
|
import { GameViewer } from "./GameViewer";
|
||||||
import ViewerHistoryLayer from "./components/ViewerHistoryLayer.vue";
|
import ViewerHistoryLayer from "./components/ViewerHistoryLayer.vue";
|
||||||
import ViewerEntityBrowserPanel from "./components/ViewerEntityBrowserPanel.vue";
|
import ViewerEntityBrowserPanel from "./components/ViewerEntityBrowserPanel.vue";
|
||||||
@@ -31,8 +31,8 @@ const authStore = useAuthStore();
|
|||||||
const playerFactionStore = usePlayerFactionStore();
|
const playerFactionStore = usePlayerFactionStore();
|
||||||
const automationCatalogStore = useShipAutomationCatalogStore();
|
const automationCatalogStore = useShipAutomationCatalogStore();
|
||||||
const selectionStore = useViewerSelectionStore();
|
const selectionStore = useViewerSelectionStore();
|
||||||
const { selectedEntityId, selectedEntityLabel } = storeToRefs(selectionStore);
|
const { selectedEntityId } = storeToRefs(selectionStore);
|
||||||
const { canAccessGm, effectivePlayerId } = storeToRefs(authStore);
|
const { canAccessGm, effectivePlayerId, isActingAsAlternateIdentity } = storeToRefs(authStore);
|
||||||
const { playerFaction } = storeToRefs(playerFactionStore);
|
const { playerFaction } = storeToRefs(playerFactionStore);
|
||||||
let viewer: GameViewer | undefined;
|
let viewer: GameViewer | undefined;
|
||||||
|
|
||||||
@@ -42,14 +42,27 @@ const gmSettingsOpen = ref(false);
|
|||||||
const gmMenuOpen = ref(false);
|
const gmMenuOpen = ref(false);
|
||||||
const leftSidebarTab = ref<"player" | "entities">("player");
|
const leftSidebarTab = ref<"player" | "entities">("player");
|
||||||
const playerContextReady = ref(false);
|
const playerContextReady = ref(false);
|
||||||
|
const rightSidebarWidth = ref(380);
|
||||||
|
const rightSidebarResizing = ref(false);
|
||||||
|
const shouldShowOnboarding = computed(() =>
|
||||||
|
!!playerContextReady.value
|
||||||
|
&& !!playerFaction.value?.requiresOnboarding
|
||||||
|
&& (!canAccessGm.value || isActingAsAlternateIdentity.value),
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
window.addEventListener("pointermove", onWindowPointerMove);
|
||||||
|
window.addEventListener("pointerup", stopRightSidebarResize);
|
||||||
|
window.addEventListener("pointercancel", stopRightSidebarResize);
|
||||||
void automationCatalogStore.load();
|
void automationCatalogStore.load();
|
||||||
await refreshPlayerContext();
|
await refreshPlayerContext();
|
||||||
await startViewerIfAuthenticated();
|
await startViewerIfAuthenticated();
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("pointermove", onWindowPointerMove);
|
||||||
|
window.removeEventListener("pointerup", stopRightSidebarResize);
|
||||||
|
window.removeEventListener("pointercancel", stopRightSidebarResize);
|
||||||
viewer?.dispose();
|
viewer?.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +84,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => playerFaction.value?.requiresOnboarding ?? false,
|
() => shouldShowOnboarding.value,
|
||||||
async (requiresOnboarding) => {
|
async (requiresOnboarding) => {
|
||||||
if (requiresOnboarding) {
|
if (requiresOnboarding) {
|
||||||
viewer?.dispose();
|
viewer?.dispose();
|
||||||
@@ -101,8 +114,31 @@ function onFocusSelection(selection: Selectable, cameraMode?: "follow" | "tactic
|
|||||||
viewer?.focusSelection(selection, cameraMode);
|
viewer?.focusSelection(selection, cameraMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startRightSidebarResize(event: PointerEvent) {
|
||||||
|
if (window.innerWidth <= 760 || event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rightSidebarResizing.value = true;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowPointerMove(event: PointerEvent) {
|
||||||
|
if (!rightSidebarResizing.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minWidth = 280;
|
||||||
|
const maxWidth = Math.min(720, Math.max(window.innerWidth - 240, minWidth));
|
||||||
|
rightSidebarWidth.value = Math.min(maxWidth, Math.max(minWidth, window.innerWidth - event.clientX));
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRightSidebarResize() {
|
||||||
|
rightSidebarResizing.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
async function startViewerIfAuthenticated() {
|
async function startViewerIfAuthenticated() {
|
||||||
if (!authStore.isAuthenticated || viewer || !playerContextReady.value || playerFaction.value?.requiresOnboarding) {
|
if (!authStore.isAuthenticated || viewer || !playerContextReady.value || shouldShowOnboarding.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +191,7 @@ async function refreshPlayerContext() {
|
|||||||
<p>Loading your in-universe identity and ownership state.</p>
|
<p>Loading your in-universe identity and ownership state.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PlayerOnboardingPanel v-else-if="playerContextReady && playerFaction?.requiresOnboarding" />
|
<PlayerOnboardingPanel v-else-if="shouldShowOnboarding" />
|
||||||
<div v-else class="viewer-app">
|
<div v-else class="viewer-app">
|
||||||
<div
|
<div
|
||||||
ref="canvasHostEl"
|
ref="canvasHostEl"
|
||||||
@@ -232,27 +268,28 @@ async function refreshPlayerContext() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute right-5 top-5 flex max-h-[calc(100vh-40px)] w-[min(380px,calc(100vw-40px))] flex-col gap-4 overflow-hidden max-[760px]:bottom-[148px] max-[760px]:left-5 max-[760px]:right-5 max-[760px]:top-auto max-[760px]:max-h-[38vh] max-[760px]:w-auto">
|
<div class="viewer-right-sidebar-dock" :style="{ width: `${rightSidebarWidth}px` }">
|
||||||
<ViewerEntityInspectorPanel
|
<section class="viewer-right-sidebar pointer-events-auto">
|
||||||
class="min-h-0 flex-1"
|
<div
|
||||||
:fallback-title="hudState.detailPanel.title"
|
class="viewer-right-sidebar__resize-handle"
|
||||||
:fallback-html="hudState.detailPanel.bodyHtml"
|
:class="rightSidebarResizing ? 'viewer-right-sidebar__resize-handle--active' : ''"
|
||||||
@focus="(selection, cameraMode) => onFocusSelection(selection, cameraMode)"
|
@pointerdown="startRightSidebarResize"
|
||||||
/>
|
/>
|
||||||
<div
|
<div class="viewer-right-sidebar__body">
|
||||||
class="pointer-events-auto rounded-xl bg-[rgba(255,116,88,0.14)] px-3.5 py-3 text-[#ffd8cf]"
|
<ViewerEntityInspectorPanel
|
||||||
:hidden="hudState.error.hidden"
|
class="viewer-right-sidebar__panel"
|
||||||
>
|
:fallback-title="hudState.detailPanel.title"
|
||||||
{{ hudState.error.message }}
|
:fallback-html="hudState.detailPanel.bodyHtml"
|
||||||
</div>
|
@focus="(selection, cameraMode) => onFocusSelection(selection, cameraMode)"
|
||||||
<button
|
/>
|
||||||
v-if="selectedEntityId"
|
</div>
|
||||||
type="button"
|
<div
|
||||||
class="selection-action-button pointer-events-auto self-end rounded-full border border-white/10 bg-white/5 px-3.5 py-2.5 text-sm text-[color:var(--viewer-text)] transition hover:bg-white/10"
|
class="viewer-right-sidebar__error"
|
||||||
@click="selectionStore.clearSelection('ui')"
|
:hidden="hudState.error.hidden"
|
||||||
>
|
>
|
||||||
Clear {{ selectedEntityLabel ?? "Selection" }}
|
{{ hudState.error.message }}
|
||||||
</button>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref="historyLayerHostEl">
|
<div ref="historyLayerHostEl">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { updatePanFromKeyboard } from "./viewerCamera";
|
|||||||
import { setShellReticleOpacity } from "./viewerControls";
|
import { setShellReticleOpacity } from "./viewerControls";
|
||||||
import { renderFrame, resizeViewer, stepCamera } from "./viewerRenderLoop";
|
import { renderFrame, resizeViewer, stepCamera } from "./viewerRenderLoop";
|
||||||
import { updateSystemStarPresentation } from "./viewerPresentation";
|
import { updateSystemStarPresentation } from "./viewerPresentation";
|
||||||
import { resolveFocusedCelestialId } from "./viewerSelection";
|
import { resolveFocusedAnchorId } from "./viewerSelection";
|
||||||
import { describeSelectionParent } from "./viewerPanels";
|
import { describeSelectionParent } from "./viewerPanels";
|
||||||
import {
|
import {
|
||||||
createInitialNetworkStats,
|
createInitialNetworkStats,
|
||||||
@@ -195,6 +195,7 @@ export class ViewerAppController {
|
|||||||
return this.sceneDataController.createWorldPresentationContext({
|
return this.sceneDataController.createWorldPresentationContext({
|
||||||
world: this.world,
|
world: this.world,
|
||||||
activeSystemId: this.activeSystemId,
|
activeSystemId: this.activeSystemId,
|
||||||
|
focusedAnchorId: this.resolveFocusedAnchorId(),
|
||||||
cameraMode: this.cameraMode,
|
cameraMode: this.cameraMode,
|
||||||
povLevel: this.povLevel,
|
povLevel: this.povLevel,
|
||||||
orbitYaw: this.orbitYaw,
|
orbitYaw: this.orbitYaw,
|
||||||
@@ -284,6 +285,7 @@ export class ViewerAppController {
|
|||||||
this.sceneStore.setViewContext(this.activeSystemId ?? null, this.povLevel);
|
this.sceneStore.setViewContext(this.activeSystemId ?? null, this.povLevel);
|
||||||
}
|
}
|
||||||
this.navigationController.updateActiveSystem();
|
this.navigationController.updateActiveSystem();
|
||||||
|
this.navigationController.syncGalaxyAnchorToActiveSystem();
|
||||||
|
|
||||||
if (this.cameraMode === "follow" && this.navigationController.updateFollowCamera(delta)) {
|
if (this.cameraMode === "follow" && this.navigationController.updateFollowCamera(delta)) {
|
||||||
// Follow camera directly controls systemLayer.camera in updateFollowCamera.
|
// Follow camera directly controls systemLayer.camera in updateFollowCamera.
|
||||||
@@ -350,8 +352,8 @@ export class ViewerAppController {
|
|||||||
this.interactionController.refreshHistoryWindows();
|
this.interactionController.refreshHistoryWindows();
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveFocusedCelestialId() {
|
private resolveFocusedAnchorId() {
|
||||||
return resolveFocusedCelestialId(this.world, this.selectedItems);
|
return resolveFocusedAnchorId(this.world, this.selectedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onResize(width: number, height: number) {
|
private onResize(width: number, height: number) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import type {
|
|||||||
export interface WorldStreamScope {
|
export interface WorldStreamScope {
|
||||||
scopeKind?: string;
|
scopeKind?: string;
|
||||||
systemId?: string | null;
|
systemId?: string | null;
|
||||||
bubbleId?: string | null;
|
anchorId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchJson<T>(input: RequestInfo | URL, init?: RequestInit, options?: { skipAuth?: boolean; skipRefresh?: boolean }): Promise<T> {
|
async function fetchJson<T>(input: RequestInfo | URL, init?: RequestInit, options?: { skipAuth?: boolean; skipRefresh?: boolean }): Promise<T> {
|
||||||
@@ -105,8 +105,8 @@ export function openWorldStream(
|
|||||||
if (scope?.systemId) {
|
if (scope?.systemId) {
|
||||||
query.set("systemId", scope.systemId);
|
query.set("systemId", scope.systemId);
|
||||||
}
|
}
|
||||||
if (scope?.bubbleId) {
|
if (scope?.anchorId) {
|
||||||
query.set("bubbleId", scope.bubbleId);
|
query.set("anchorId", scope.anchorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = new EventSource(`/api/world/stream?${query.toString()}`);
|
const stream = new EventSource(`/api/world/stream?${query.toString()}`);
|
||||||
|
|||||||
@@ -115,12 +115,13 @@ function formatShipLocation(ship: ShipSnapshot) {
|
|||||||
return `Docked ${dockedStation.label}`;
|
return `Docked ${dockedStation.label}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ship.spatialState.transit?.destinationNodeId) {
|
const transitAnchorId = ship.spatialState.transit?.destinationAnchorId ?? ship.spatialState.destinationAnchorId;
|
||||||
return `Transit ${ship.systemId}`;
|
if (transitAnchorId) {
|
||||||
|
return `Transit ${titleCase(transitAnchorId)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ship.celestialId) {
|
if (ship.spatialState.currentAnchorId) {
|
||||||
return `Orbit ${titleCase(ship.celestialId)}`;
|
return `Anchor ${compactAnchorId(ship.spatialState.currentAnchorId)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const system = systemById.value.get(ship.systemId);
|
const system = systemById.value.get(ship.systemId);
|
||||||
@@ -129,13 +130,32 @@ function formatShipLocation(ship: ShipSnapshot) {
|
|||||||
|
|
||||||
function formatStationLocation(station: StationSnapshot) {
|
function formatStationLocation(station: StationSnapshot) {
|
||||||
const system = systemById.value.get(station.systemId);
|
const system = systemById.value.get(station.systemId);
|
||||||
if (station.celestialId) {
|
if (station.anchorId) {
|
||||||
return `${system?.label ?? station.systemId} · ${titleCase(station.celestialId)}`;
|
return `${system?.label ?? station.systemId} · ${compactAnchorId(station.anchorId)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return system?.label ?? station.systemId;
|
return system?.label ?? station.systemId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compactAnchorId(value: string) {
|
||||||
|
const lagrangeMatch = value.match(/(l[1-5])$/i);
|
||||||
|
if (lagrangeMatch) {
|
||||||
|
return lagrangeMatch[1].toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const moonMatch = value.match(/moon-(\d+)$/i);
|
||||||
|
if (moonMatch) {
|
||||||
|
return `Moon ${moonMatch[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const planetMatch = value.match(/planet-(\d+)$/i);
|
||||||
|
if (planetMatch) {
|
||||||
|
return `Planet ${planetMatch[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return titleCase(value);
|
||||||
|
}
|
||||||
|
|
||||||
function shipAiStates(ship: ShipSnapshot) {
|
function shipAiStates(ship: ShipSnapshot) {
|
||||||
const travelToken = ship.spatialState.transit ? "TRV" : "";
|
const travelToken = ship.spatialState.transit ? "TRV" : "";
|
||||||
const dockToken = ship.dockedStationId ? "DCK" : "";
|
const dockToken = ship.dockedStationId ? "DCK" : "";
|
||||||
|
|||||||
@@ -80,6 +80,14 @@ function formatPercent(value: number) {
|
|||||||
return `${Math.round(value * 100)}%`;
|
return `${Math.round(value * 100)}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatCargoTypeLabel(types: string[] | null | undefined) {
|
||||||
|
if (!types || types.length === 0) {
|
||||||
|
return "general";
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.join(" / ");
|
||||||
|
}
|
||||||
|
|
||||||
function joinDetail(parts: Array<string | null | undefined>) {
|
function joinDetail(parts: Array<string | null | undefined>) {
|
||||||
return parts.filter((part): part is string => !!part && part.trim().length > 0).join(" · ");
|
return parts.filter((part): part is string => !!part && part.trim().length > 0).join(" · ");
|
||||||
}
|
}
|
||||||
@@ -88,7 +96,7 @@ function describeOrderTarget(order: {
|
|||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
targetEntityId?: string | null;
|
targetEntityId?: string | null;
|
||||||
targetSystemId?: string | null;
|
targetSystemId?: string | null;
|
||||||
nodeId?: string | null;
|
anchorId?: string | null;
|
||||||
constructionSiteId?: string | null;
|
constructionSiteId?: string | null;
|
||||||
sourceStationId?: string | null;
|
sourceStationId?: string | null;
|
||||||
destinationStationId?: string | null;
|
destinationStationId?: string | null;
|
||||||
@@ -97,7 +105,7 @@ function describeOrderTarget(order: {
|
|||||||
return order.itemId
|
return order.itemId
|
||||||
?? order.targetEntityId
|
?? order.targetEntityId
|
||||||
?? order.targetSystemId
|
?? order.targetSystemId
|
||||||
?? order.nodeId
|
?? order.anchorId
|
||||||
?? order.constructionSiteId
|
?? order.constructionSiteId
|
||||||
?? order.destinationStationId
|
?? order.destinationStationId
|
||||||
?? order.sourceStationId
|
?? order.sourceStationId
|
||||||
@@ -109,13 +117,15 @@ function describeSubTaskTarget(subTask: {
|
|||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
targetEntityId?: string | null;
|
targetEntityId?: string | null;
|
||||||
targetSystemId?: string | null;
|
targetSystemId?: string | null;
|
||||||
targetNodeId?: string | null;
|
targetAnchorId?: string | null;
|
||||||
|
targetResourceNodeId?: string | null;
|
||||||
moduleId?: string | null;
|
moduleId?: string | null;
|
||||||
}) {
|
}) {
|
||||||
return subTask.itemId
|
return subTask.itemId
|
||||||
?? subTask.targetEntityId
|
?? subTask.targetEntityId
|
||||||
?? subTask.targetSystemId
|
?? subTask.targetSystemId
|
||||||
?? subTask.targetNodeId
|
?? subTask.targetAnchorId
|
||||||
|
?? subTask.targetResourceNodeId
|
||||||
?? subTask.moduleId
|
?? subTask.moduleId
|
||||||
?? "—";
|
?? "—";
|
||||||
}
|
}
|
||||||
@@ -184,8 +194,14 @@ const shipStatusRows = computed(() => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shipLocation = selectedShip.value.spatialState.currentAnchorId
|
||||||
|
?? selectedShip.value.anchorId
|
||||||
|
?? selectedShip.value.systemId
|
||||||
|
?? "unknown";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ label: "State", value: titleCase(selectedShip.value.state) },
|
{ label: "State", value: titleCase(selectedShip.value.state) },
|
||||||
|
{ label: "Location", value: shipLocation },
|
||||||
{ label: "Behavior", value: getShipBehaviorLabel(selectedShip.value.defaultBehavior.kind) },
|
{ label: "Behavior", value: getShipBehaviorLabel(selectedShip.value.defaultBehavior.kind) },
|
||||||
{ label: "Control", value: titleCase(selectedShip.value.controlSourceKind) },
|
{ label: "Control", value: titleCase(selectedShip.value.controlSourceKind) },
|
||||||
{ label: "Assignment", value: selectedShip.value.assignment?.kind ?? "unassigned" },
|
{ label: "Assignment", value: selectedShip.value.assignment?.kind ?? "unassigned" },
|
||||||
@@ -201,20 +217,21 @@ const shipStatusRows = computed(() => {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
const shipCargoSummaryRows = computed(() => {
|
const shipCargoBarRows = computed(() => {
|
||||||
if (!selectedShip.value) {
|
if (!selectedShip.value) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const usedCargo = selectedShip.value.inventory.reduce((sum, entry) => sum + entry.amount, 0);
|
const usedCargo = selectedShip.value.inventory.reduce((sum, entry) => sum + entry.amount, 0);
|
||||||
return [
|
return [{
|
||||||
{ label: "Used", value: formatAmount(usedCargo) },
|
key: "cargo",
|
||||||
{ label: "Capacity", value: formatAmount(selectedShip.value.cargoCapacity) },
|
label: `${formatCargoTypeLabel(selectedShip.value.cargoTypes)}`,
|
||||||
{ label: "Free", value: formatAmount(Math.max(selectedShip.value.cargoCapacity - usedCargo, 0)) },
|
value: usedCargo,
|
||||||
{ label: "Travel", value: `${formatAmount(selectedShip.value.travelSpeed)} ${selectedShip.value.travelSpeedUnit}` },
|
valueLabel: formatAmount(usedCargo),
|
||||||
{ label: "Hull", value: formatAmount(selectedShip.value.health) },
|
max: selectedShip.value.cargoCapacity,
|
||||||
{ label: "Regime", value: titleCase(selectedShip.value.spatialState.movementRegime) },
|
maxLabel: formatAmount(selectedShip.value.cargoCapacity),
|
||||||
];
|
fillRatio: selectedShip.value.cargoCapacity > 0 ? usedCargo / selectedShip.value.cargoCapacity : 0,
|
||||||
|
}];
|
||||||
});
|
});
|
||||||
|
|
||||||
const shipCargoRows = computed(() =>
|
const shipCargoRows = computed(() =>
|
||||||
@@ -309,8 +326,13 @@ const stationStatusRows = computed(() => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stationLocation = selectedStation.value.anchorId
|
||||||
|
?? selectedStation.value.systemId
|
||||||
|
?? "unknown";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ label: "Category", value: titleCase(selectedStation.value.category) },
|
{ label: "Category", value: titleCase(selectedStation.value.category) },
|
||||||
|
{ label: "Location", value: stationLocation },
|
||||||
{ label: "Objective", value: titleCase(selectedStation.value.objective) },
|
{ label: "Objective", value: titleCase(selectedStation.value.objective) },
|
||||||
{ label: "Docked", value: `${selectedStation.value.dockedShips} / ${selectedStation.value.dockingPads}` },
|
{ label: "Docked", value: `${selectedStation.value.dockedShips} / ${selectedStation.value.dockingPads}` },
|
||||||
{
|
{
|
||||||
@@ -335,10 +357,12 @@ const stationModuleRows = computed(() =>
|
|||||||
const stationStorageRows = computed(() =>
|
const stationStorageRows = computed(() =>
|
||||||
selectedStation.value?.storageUsage.map((entry) => ({
|
selectedStation.value?.storageUsage.map((entry) => ({
|
||||||
key: entry.storageClass,
|
key: entry.storageClass,
|
||||||
storageClass: titleCase(entry.storageClass),
|
label: titleCase(entry.storageClass),
|
||||||
used: formatAmount(entry.used),
|
value: entry.used,
|
||||||
capacity: formatAmount(entry.capacity),
|
valueLabel: formatAmount(entry.used),
|
||||||
fill: entry.capacity > 0 ? formatPercent(entry.used / entry.capacity) : "0%",
|
max: entry.capacity,
|
||||||
|
maxLabel: formatAmount(entry.capacity),
|
||||||
|
fillRatio: entry.capacity > 0 ? entry.used / entry.capacity : 0,
|
||||||
})) ?? [],
|
})) ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -429,7 +453,7 @@ async function saveBehavior() {
|
|||||||
itemId: behaviorForm.kind === "local-auto-mine"
|
itemId: behaviorForm.kind === "local-auto-mine"
|
||||||
? (behaviorForm.itemId.trim() || null)
|
? (behaviorForm.itemId.trim() || null)
|
||||||
: null,
|
: null,
|
||||||
preferredNodeId: null,
|
preferredAnchorId: null,
|
||||||
preferredConstructionSiteId: null,
|
preferredConstructionSiteId: null,
|
||||||
preferredModuleId: null,
|
preferredModuleId: null,
|
||||||
targetPosition: null,
|
targetPosition: null,
|
||||||
@@ -461,7 +485,7 @@ async function queueHoldPositionOrder() {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: null,
|
itemId: null,
|
||||||
nodeId: null,
|
anchorId: null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 0,
|
waitSeconds: 0,
|
||||||
@@ -497,7 +521,7 @@ async function queueMoveOrder() {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: null,
|
itemId: null,
|
||||||
nodeId: null,
|
anchorId: null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 0,
|
waitSeconds: 0,
|
||||||
@@ -540,7 +564,7 @@ async function queueMineResourceOrder() {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId,
|
itemId,
|
||||||
nodeId: null,
|
anchorId: null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 0,
|
waitSeconds: 0,
|
||||||
@@ -609,15 +633,20 @@ async function clearOrders() {
|
|||||||
|
|
||||||
<div class="entity-inspector-section">
|
<div class="entity-inspector-section">
|
||||||
<h4>Cargo</h4>
|
<h4>Cargo</h4>
|
||||||
<div class="entity-inspector-table-wrap">
|
<div class="entity-inspector-capacity-list">
|
||||||
<table class="entity-inspector-table entity-inspector-table--kv">
|
<div v-for="row in shipCargoBarRows" :key="row.key" class="entity-inspector-capacity">
|
||||||
<tbody>
|
<div class="entity-inspector-capacity__header">
|
||||||
<tr v-for="row in shipCargoSummaryRows" :key="row.label">
|
<span class="entity-inspector-capacity__label">{{ row.label }}</span>
|
||||||
<th scope="row">{{ row.label }}</th>
|
<span class="entity-inspector-capacity__value">{{ row.valueLabel }} / {{ row.maxLabel }}</span>
|
||||||
<td>{{ row.value }}</td>
|
</div>
|
||||||
</tr>
|
<div class="entity-inspector-capacity__scale">
|
||||||
</tbody>
|
<span>0</span>
|
||||||
</table>
|
<div class="entity-inspector-capacity__track">
|
||||||
|
<div class="entity-inspector-capacity__fill" :style="{ width: `${Math.max(0, Math.min(100, row.fillRatio * 100))}%` }"></div>
|
||||||
|
</div>
|
||||||
|
<span>{{ row.maxLabel }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shipCargoRows.length > 0" class="entity-inspector-table-wrap">
|
<div v-if="shipCargoRows.length > 0" class="entity-inspector-table-wrap">
|
||||||
<table class="entity-inspector-table">
|
<table class="entity-inspector-table">
|
||||||
@@ -635,7 +664,7 @@ async function clearOrders() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="entity-inspector-empty">No cargo.</div>
|
<div v-else class="entity-inspector-empty">No wares loaded.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="entity-inspector-section">
|
<div class="entity-inspector-section">
|
||||||
@@ -856,25 +885,20 @@ async function clearOrders() {
|
|||||||
|
|
||||||
<div class="entity-inspector-section">
|
<div class="entity-inspector-section">
|
||||||
<h4>Storage</h4>
|
<h4>Storage</h4>
|
||||||
<div v-if="stationStorageRows.length > 0" class="entity-inspector-table-wrap">
|
<div v-if="stationStorageRows.length > 0" class="entity-inspector-capacity-list">
|
||||||
<table class="entity-inspector-table">
|
<div v-for="row in stationStorageRows" :key="row.key" class="entity-inspector-capacity">
|
||||||
<thead>
|
<div class="entity-inspector-capacity__header">
|
||||||
<tr>
|
<span class="entity-inspector-capacity__label">{{ row.label }}</span>
|
||||||
<th scope="col">Class</th>
|
<span class="entity-inspector-capacity__value">{{ row.valueLabel }} / {{ row.maxLabel }}</span>
|
||||||
<th scope="col" class="entity-inspector-table__numeric">Used</th>
|
</div>
|
||||||
<th scope="col" class="entity-inspector-table__numeric">Capacity</th>
|
<div class="entity-inspector-capacity__scale">
|
||||||
<th scope="col" class="entity-inspector-table__numeric">Fill</th>
|
<span>0</span>
|
||||||
</tr>
|
<div class="entity-inspector-capacity__track">
|
||||||
</thead>
|
<div class="entity-inspector-capacity__fill" :style="{ width: `${Math.max(0, Math.min(100, row.fillRatio * 100))}%` }"></div>
|
||||||
<tbody>
|
</div>
|
||||||
<tr v-for="row in stationStorageRows" :key="row.key">
|
<span>{{ row.maxLabel }}</span>
|
||||||
<td>{{ row.storageClass }}</td>
|
</div>
|
||||||
<td class="entity-inspector-table__numeric">{{ row.used }}</td>
|
</div>
|
||||||
<td class="entity-inspector-table__numeric">{{ row.capacity }}</td>
|
|
||||||
<td class="entity-inspector-table__numeric">{{ row.fill }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="stationInventoryRows.length > 0" class="entity-inspector-table-wrap">
|
<div v-if="stationInventoryRows.length > 0" class="entity-inspector-table-wrap">
|
||||||
<table class="entity-inspector-table">
|
<table class="entity-inspector-table">
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ async function runAction(action: MenuAction) {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId,
|
itemId,
|
||||||
nodeId: target.value.selection.kind === "node" ? target.value.selection.id : null,
|
anchorId: target.value.anchorId ?? null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 0,
|
waitSeconds: 0,
|
||||||
@@ -182,7 +182,7 @@ async function runAction(action: MenuAction) {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: null,
|
itemId: null,
|
||||||
nodeId: null,
|
anchorId: null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 8,
|
waitSeconds: 8,
|
||||||
@@ -207,7 +207,7 @@ async function runAction(action: MenuAction) {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: null,
|
itemId: null,
|
||||||
nodeId: null,
|
anchorId: null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 6,
|
waitSeconds: 6,
|
||||||
@@ -232,7 +232,7 @@ async function runAction(action: MenuAction) {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: null,
|
itemId: null,
|
||||||
nodeId: null,
|
anchorId: null,
|
||||||
constructionSiteId: null,
|
constructionSiteId: null,
|
||||||
moduleId: null,
|
moduleId: null,
|
||||||
waitSeconds: 0,
|
waitSeconds: 0,
|
||||||
|
|||||||
@@ -149,13 +149,6 @@ function compactRate(value: number | null | undefined) {
|
|||||||
return `${sign}${value.toFixed(2)}/s`;
|
return `${sign}${value.toFixed(2)}/s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCargoAmount(value: number | null | undefined) {
|
|
||||||
if (value == null || Number.isNaN(value)) return "—";
|
|
||||||
const rounded = Math.round(value);
|
|
||||||
if (Math.abs(value - rounded) < 0.005) return String(rounded);
|
|
||||||
return value.toFixed(2).replace(/\.?0+$/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatPercent(value: number | null | undefined) {
|
function formatPercent(value: number | null | undefined) {
|
||||||
if (value == null || Number.isNaN(value)) return "—";
|
if (value == null || Number.isNaN(value)) return "—";
|
||||||
return `${Math.round(value * 100)}%`;
|
return `${Math.round(value * 100)}%`;
|
||||||
@@ -281,8 +274,6 @@ type ShipRow = {
|
|||||||
plan: string;
|
plan: string;
|
||||||
step: string;
|
step: string;
|
||||||
subtask: string;
|
subtask: string;
|
||||||
cargo: number;
|
|
||||||
health: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const shipRows = computed<ShipRow[]>(() =>
|
const shipRows = computed<ShipRow[]>(() =>
|
||||||
@@ -305,8 +296,6 @@ const shipRows = computed<ShipRow[]>(() =>
|
|||||||
plan: s.activePlan ? `${titleCaseToken(s.activePlan.kind)} · ${titleCaseToken(s.activePlan.status)}` : "—",
|
plan: s.activePlan ? `${titleCaseToken(s.activePlan.kind)} · ${titleCaseToken(s.activePlan.status)}` : "—",
|
||||||
step: currentStep ? `${titleCaseToken(currentStep.kind)} · ${titleCaseToken(currentStep.status)}` : "—",
|
step: currentStep ? `${titleCaseToken(currentStep.kind)} · ${titleCaseToken(currentStep.status)}` : "—",
|
||||||
subtask: currentSubTask ? `${titleCaseToken(currentSubTask.kind)} ${Math.round(currentSubTask.progress * 100)}%` : "—",
|
subtask: currentSubTask ? `${titleCaseToken(currentSubTask.kind)} ${Math.round(currentSubTask.progress * 100)}%` : "—",
|
||||||
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
|
||||||
health: Math.round(s.health),
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -329,16 +318,11 @@ const shipColumns = [
|
|||||||
shipColumnHelper.accessor("plan", { header: "Plan" }),
|
shipColumnHelper.accessor("plan", { header: "Plan" }),
|
||||||
shipColumnHelper.accessor("step", { header: "Current Step" }),
|
shipColumnHelper.accessor("step", { header: "Current Step" }),
|
||||||
shipColumnHelper.accessor("subtask", { header: "SubTask" }),
|
shipColumnHelper.accessor("subtask", { header: "SubTask" }),
|
||||||
shipColumnHelper.accessor("cargo", {
|
|
||||||
header: "Cargo",
|
|
||||||
cell: (info) => formatCargoAmount(info.getValue()),
|
|
||||||
}),
|
|
||||||
shipColumnHelper.accessor("health", { header: "HP" }),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const shipFilter = ref("");
|
const shipFilter = ref("");
|
||||||
const shipSorting = ref<SortingState>([]);
|
const shipSorting = ref<SortingState>([]);
|
||||||
const shipOrder = useColumnOrder(["label", "class", "factionColor", "faction", "system", "state", "assignment", "behavior", "orders", "plan", "step", "subtask", "cargo", "health"]);
|
const shipOrder = useColumnOrder(["label", "class", "factionColor", "faction", "system", "state", "assignment", "behavior", "orders", "plan", "step", "subtask"]);
|
||||||
|
|
||||||
const shipTable = useVueTable({
|
const shipTable = useVueTable({
|
||||||
get data() { return shipRows.value; },
|
get data() { return shipRows.value; },
|
||||||
@@ -373,7 +357,6 @@ type StationRow = {
|
|||||||
docked: string;
|
docked: string;
|
||||||
orders: number;
|
orders: number;
|
||||||
orderDetails: MarketOrderSnapshot[];
|
orderDetails: MarketOrderSnapshot[];
|
||||||
cargo: number;
|
|
||||||
modules: number;
|
modules: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,7 +383,6 @@ const stationRows = computed<StationRow[]>(() =>
|
|||||||
const order = marketOrderMap.value.get(id);
|
const order = marketOrderMap.value.get(id);
|
||||||
return order ? [order] : [];
|
return order ? [order] : [];
|
||||||
}),
|
}),
|
||||||
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
|
||||||
modules: s.installedModules.length,
|
modules: s.installedModules.length,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
@@ -421,16 +403,12 @@ const stationColumns = [
|
|||||||
stationColumnHelper.accessor("workforce", { header: "Workforce" }),
|
stationColumnHelper.accessor("workforce", { header: "Workforce" }),
|
||||||
stationColumnHelper.accessor("docked", { header: "Docked" }),
|
stationColumnHelper.accessor("docked", { header: "Docked" }),
|
||||||
stationColumnHelper.accessor("orders", { header: "Orders" }),
|
stationColumnHelper.accessor("orders", { header: "Orders" }),
|
||||||
stationColumnHelper.accessor("cargo", {
|
|
||||||
header: "Cargo",
|
|
||||||
cell: (info) => formatCargoAmount(info.getValue()),
|
|
||||||
}),
|
|
||||||
stationColumnHelper.accessor("modules", { header: "Modules" }),
|
stationColumnHelper.accessor("modules", { header: "Modules" }),
|
||||||
];
|
];
|
||||||
|
|
||||||
const stationFilter = ref("");
|
const stationFilter = ref("");
|
||||||
const stationSorting = ref<SortingState>([]);
|
const stationSorting = ref<SortingState>([]);
|
||||||
const stationOrder = useColumnOrder(["label", "category", "objective", "factionColor", "faction", "system", "process", "workforce", "docked", "orders", "cargo", "modules"]);
|
const stationOrder = useColumnOrder(["label", "category", "objective", "factionColor", "faction", "system", "process", "workforce", "docked", "orders", "modules"]);
|
||||||
|
|
||||||
const stationTable = useVueTable({
|
const stationTable = useVueTable({
|
||||||
get data() { return stationRows.value; },
|
get data() { return stationRows.value; },
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ const behaviorForm = reactive({
|
|||||||
areaSystemId: "",
|
areaSystemId: "",
|
||||||
targetEntityId: "",
|
targetEntityId: "",
|
||||||
itemId: "",
|
itemId: "",
|
||||||
preferredNodeId: "",
|
preferredAnchorId: "",
|
||||||
preferredConstructionSiteId: "",
|
preferredConstructionSiteId: "",
|
||||||
preferredModuleId: "",
|
preferredModuleId: "",
|
||||||
waitSeconds: 3,
|
waitSeconds: 3,
|
||||||
@@ -268,7 +268,7 @@ const orderForm = reactive({
|
|||||||
targetEntityId: "",
|
targetEntityId: "",
|
||||||
targetSystemId: "",
|
targetSystemId: "",
|
||||||
itemId: "",
|
itemId: "",
|
||||||
nodeId: "",
|
anchorId: "",
|
||||||
constructionSiteId: "",
|
constructionSiteId: "",
|
||||||
moduleId: "",
|
moduleId: "",
|
||||||
waitSeconds: 3,
|
waitSeconds: 3,
|
||||||
@@ -344,7 +344,7 @@ watch(selectedShip, (ship) => {
|
|||||||
behaviorForm.areaSystemId = ship.defaultBehavior.areaSystemId ?? ship.systemId;
|
behaviorForm.areaSystemId = ship.defaultBehavior.areaSystemId ?? ship.systemId;
|
||||||
behaviorForm.targetEntityId = ship.defaultBehavior.targetEntityId ?? "";
|
behaviorForm.targetEntityId = ship.defaultBehavior.targetEntityId ?? "";
|
||||||
behaviorForm.itemId = ship.defaultBehavior.itemId ?? "";
|
behaviorForm.itemId = ship.defaultBehavior.itemId ?? "";
|
||||||
behaviorForm.preferredNodeId = ship.defaultBehavior.preferredNodeId ?? "";
|
behaviorForm.preferredAnchorId = ship.defaultBehavior.preferredAnchorId ?? "";
|
||||||
behaviorForm.preferredConstructionSiteId = ship.defaultBehavior.preferredConstructionSiteId ?? "";
|
behaviorForm.preferredConstructionSiteId = ship.defaultBehavior.preferredConstructionSiteId ?? "";
|
||||||
behaviorForm.preferredModuleId = ship.defaultBehavior.preferredModuleId ?? "";
|
behaviorForm.preferredModuleId = ship.defaultBehavior.preferredModuleId ?? "";
|
||||||
behaviorForm.waitSeconds = ship.defaultBehavior.waitSeconds;
|
behaviorForm.waitSeconds = ship.defaultBehavior.waitSeconds;
|
||||||
@@ -484,7 +484,7 @@ async function submitDirective() {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: directiveForm.itemId || null,
|
itemId: directiveForm.itemId || null,
|
||||||
preferredNodeId: null,
|
preferredAnchorId: null,
|
||||||
preferredConstructionSiteId: null,
|
preferredConstructionSiteId: null,
|
||||||
preferredModuleId: null,
|
preferredModuleId: null,
|
||||||
priority: directiveForm.priority,
|
priority: directiveForm.priority,
|
||||||
@@ -612,7 +612,7 @@ async function submitDirectBehavior() {
|
|||||||
areaSystemId: behaviorForm.areaSystemId || null,
|
areaSystemId: behaviorForm.areaSystemId || null,
|
||||||
targetEntityId: behaviorForm.targetEntityId || null,
|
targetEntityId: behaviorForm.targetEntityId || null,
|
||||||
itemId: behaviorForm.itemId || null,
|
itemId: behaviorForm.itemId || null,
|
||||||
preferredNodeId: behaviorForm.preferredNodeId || null,
|
preferredAnchorId: behaviorForm.preferredAnchorId || null,
|
||||||
preferredConstructionSiteId: behaviorForm.preferredConstructionSiteId || null,
|
preferredConstructionSiteId: behaviorForm.preferredConstructionSiteId || null,
|
||||||
preferredModuleId: behaviorForm.preferredModuleId || null,
|
preferredModuleId: behaviorForm.preferredModuleId || null,
|
||||||
targetPosition: null,
|
targetPosition: null,
|
||||||
@@ -646,7 +646,7 @@ async function submitDirectOrder() {
|
|||||||
sourceStationId: null,
|
sourceStationId: null,
|
||||||
destinationStationId: null,
|
destinationStationId: null,
|
||||||
itemId: orderForm.itemId || null,
|
itemId: orderForm.itemId || null,
|
||||||
nodeId: orderForm.nodeId || null,
|
anchorId: orderForm.anchorId || null,
|
||||||
constructionSiteId: orderForm.constructionSiteId || null,
|
constructionSiteId: orderForm.constructionSiteId || null,
|
||||||
moduleId: orderForm.moduleId || null,
|
moduleId: orderForm.moduleId || null,
|
||||||
waitSeconds: orderForm.waitSeconds,
|
waitSeconds: orderForm.waitSeconds,
|
||||||
@@ -706,7 +706,7 @@ async function submitDirectOrder() {
|
|||||||
<label><span>Area System</span><input v-model="behaviorForm.areaSystemId" type="text"></label>
|
<label><span>Area System</span><input v-model="behaviorForm.areaSystemId" type="text"></label>
|
||||||
<label><span>Target Entity</span><input v-model="behaviorForm.targetEntityId" type="text"></label>
|
<label><span>Target Entity</span><input v-model="behaviorForm.targetEntityId" type="text"></label>
|
||||||
<label><span>Item</span><input v-model="behaviorForm.itemId" type="text"></label>
|
<label><span>Item</span><input v-model="behaviorForm.itemId" type="text"></label>
|
||||||
<label><span>Preferred Node</span><input v-model="behaviorForm.preferredNodeId" type="text"></label>
|
<label><span>Preferred Anchor</span><input v-model="behaviorForm.preferredAnchorId" type="text"></label>
|
||||||
<label><span>Construction Site</span><input v-model="behaviorForm.preferredConstructionSiteId" type="text"></label>
|
<label><span>Construction Site</span><input v-model="behaviorForm.preferredConstructionSiteId" type="text"></label>
|
||||||
<label><span>Module</span><input v-model="behaviorForm.preferredModuleId" type="text"></label>
|
<label><span>Module</span><input v-model="behaviorForm.preferredModuleId" type="text"></label>
|
||||||
<label><span>Radius</span><input v-model.number="behaviorForm.radius" type="number" min="0" step="1"></label>
|
<label><span>Radius</span><input v-model.number="behaviorForm.radius" type="number" min="0" step="1"></label>
|
||||||
@@ -723,7 +723,7 @@ async function submitDirectOrder() {
|
|||||||
<label><span>Target System</span><input v-model="orderForm.targetSystemId" type="text"></label>
|
<label><span>Target System</span><input v-model="orderForm.targetSystemId" type="text"></label>
|
||||||
<label><span>Target Entity</span><input v-model="orderForm.targetEntityId" type="text"></label>
|
<label><span>Target Entity</span><input v-model="orderForm.targetEntityId" type="text"></label>
|
||||||
<label><span>Item</span><input v-model="orderForm.itemId" type="text"></label>
|
<label><span>Item</span><input v-model="orderForm.itemId" type="text"></label>
|
||||||
<label><span>Node</span><input v-model="orderForm.nodeId" type="text"></label>
|
<label><span>Anchor</span><input v-model="orderForm.anchorId" type="text"></label>
|
||||||
<label><span>Construction Site</span><input v-model="orderForm.constructionSiteId" type="text"></label>
|
<label><span>Construction Site</span><input v-model="orderForm.constructionSiteId" type="text"></label>
|
||||||
<label><span>Module</span><input v-model="orderForm.moduleId" type="text"></label>
|
<label><span>Module</span><input v-model="orderForm.moduleId" type="text"></label>
|
||||||
<label><span>Priority</span><input v-model.number="orderForm.priority" type="number" min="0" step="1"></label>
|
<label><span>Priority</span><input v-model.number="orderForm.priority" type="number" min="0" step="1"></label>
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ export type {
|
|||||||
OrbitalSimulationSnapshot,
|
OrbitalSimulationSnapshot,
|
||||||
} from "./contractsWorld";
|
} from "./contractsWorld";
|
||||||
export type {
|
export type {
|
||||||
|
AnchorSnapshot,
|
||||||
|
AnchorDelta,
|
||||||
StarSnapshot,
|
StarSnapshot,
|
||||||
MoonSnapshot,
|
MoonSnapshot,
|
||||||
SystemSnapshot,
|
SystemSnapshot,
|
||||||
PlanetSnapshot,
|
PlanetSnapshot,
|
||||||
|
ResourceDepositSnapshot,
|
||||||
ResourceNodeSnapshot,
|
ResourceNodeSnapshot,
|
||||||
ResourceNodeDelta,
|
ResourceNodeDelta,
|
||||||
CelestialSnapshot,
|
CelestialSnapshot,
|
||||||
|
|||||||
@@ -46,26 +46,50 @@ export interface PlanetSnapshot {
|
|||||||
hasRing: boolean;
|
hasRing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResourceDepositSnapshot {
|
||||||
|
id: string;
|
||||||
|
nodeId: string;
|
||||||
|
anchorId: string;
|
||||||
|
localPosition: Vector3Dto;
|
||||||
|
oreRemaining: number;
|
||||||
|
maxOre: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResourceNodeSnapshot {
|
export interface ResourceNodeSnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
|
anchorId: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
localPosition: Vector3Dto;
|
localPosition: Vector3Dto;
|
||||||
celestialId?: string | null;
|
localSpaceRadius: number;
|
||||||
sourceKind: string;
|
sourceKind: string;
|
||||||
oreRemaining: number;
|
oreRemaining: number;
|
||||||
maxOre: number;
|
maxOre: number;
|
||||||
itemId: string;
|
itemId: string;
|
||||||
|
deposits: ResourceDepositSnapshot[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceNodeDelta extends ResourceNodeSnapshot {}
|
export interface ResourceNodeDelta extends ResourceNodeSnapshot {}
|
||||||
|
|
||||||
|
export interface AnchorSnapshot {
|
||||||
|
id: string;
|
||||||
|
systemId: string;
|
||||||
|
kind: string;
|
||||||
|
systemPosition: Vector3Dto;
|
||||||
|
localSpaceRadius: number;
|
||||||
|
parentAnchorId?: string | null;
|
||||||
|
occupyingStructureId?: string | null;
|
||||||
|
orbitReferenceId?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnchorDelta extends AnchorSnapshot {}
|
||||||
|
|
||||||
export interface CelestialSnapshot {
|
export interface CelestialSnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
orbitalAnchor: Vector3Dto;
|
orbitalAnchor: Vector3Dto;
|
||||||
localSpaceRadius: number;
|
localSpaceRadius: number;
|
||||||
parentNodeId?: string | null;
|
parentAnchorId?: string | null;
|
||||||
occupyingStructureId?: string | null;
|
occupyingStructureId?: string | null;
|
||||||
orbitReferenceId?: string | null;
|
orbitReferenceId?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export interface TerritoryClaimSnapshot {
|
|||||||
sourceClaimId?: string | null;
|
sourceClaimId?: string | null;
|
||||||
factionId: string;
|
factionId: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
celestialId?: string | null;
|
anchorId: string;
|
||||||
status: string;
|
status: string;
|
||||||
claimKind: string;
|
claimKind: string;
|
||||||
claimStrength: number;
|
claimStrength: number;
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export interface StationSnapshot {
|
|||||||
category: string;
|
category: string;
|
||||||
objective: string;
|
objective: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
|
anchorId?: string | null;
|
||||||
localPosition: Vector3Dto;
|
localPosition: Vector3Dto;
|
||||||
celestialId?: string | null;
|
|
||||||
color: string;
|
color: string;
|
||||||
dockedShips: number;
|
dockedShips: number;
|
||||||
dockedShipIds: string[];
|
dockedShipIds: string[];
|
||||||
@@ -53,7 +53,7 @@ export interface ClaimSnapshot {
|
|||||||
id: string;
|
id: string;
|
||||||
factionId: string;
|
factionId: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
celestialId: string;
|
anchorId: string;
|
||||||
state: string;
|
state: string;
|
||||||
health: number;
|
health: number;
|
||||||
placedAtUtc: string;
|
placedAtUtc: string;
|
||||||
@@ -66,7 +66,7 @@ export interface ConstructionSiteSnapshot {
|
|||||||
id: string;
|
id: string;
|
||||||
factionId: string;
|
factionId: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
celestialId: string;
|
anchorId: string;
|
||||||
targetKind: string;
|
targetKind: string;
|
||||||
targetDefinitionId: string;
|
targetDefinitionId: string;
|
||||||
blueprintId?: string | null;
|
blueprintId?: string | null;
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export interface PlayerDirectiveSnapshot {
|
|||||||
useOrders: boolean;
|
useOrders: boolean;
|
||||||
stagingOrderKind?: string | null;
|
stagingOrderKind?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
preferredNodeId?: string | null;
|
preferredAnchorId?: string | null;
|
||||||
preferredConstructionSiteId?: string | null;
|
preferredConstructionSiteId?: string | null;
|
||||||
preferredModuleId?: string | null;
|
preferredModuleId?: string | null;
|
||||||
priority: number;
|
priority: number;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface ShipOrderSnapshot {
|
|||||||
sourceStationId?: string | null;
|
sourceStationId?: string | null;
|
||||||
destinationStationId?: string | null;
|
destinationStationId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
nodeId?: string | null;
|
anchorId?: string | null;
|
||||||
constructionSiteId?: string | null;
|
constructionSiteId?: string | null;
|
||||||
moduleId?: string | null;
|
moduleId?: string | null;
|
||||||
waitSeconds: number;
|
waitSeconds: number;
|
||||||
@@ -43,7 +43,7 @@ export interface ShipOrderTemplateSnapshot {
|
|||||||
sourceStationId?: string | null;
|
sourceStationId?: string | null;
|
||||||
destinationStationId?: string | null;
|
destinationStationId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
nodeId?: string | null;
|
anchorId?: string | null;
|
||||||
constructionSiteId?: string | null;
|
constructionSiteId?: string | null;
|
||||||
moduleId?: string | null;
|
moduleId?: string | null;
|
||||||
waitSeconds: number;
|
waitSeconds: number;
|
||||||
@@ -59,7 +59,7 @@ export interface DefaultBehaviorSnapshot {
|
|||||||
areaSystemId?: string | null;
|
areaSystemId?: string | null;
|
||||||
targetEntityId?: string | null;
|
targetEntityId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
preferredNodeId?: string | null;
|
preferredAnchorId?: string | null;
|
||||||
preferredConstructionSiteId?: string | null;
|
preferredConstructionSiteId?: string | null;
|
||||||
preferredModuleId?: string | null;
|
preferredModuleId?: string | null;
|
||||||
targetPosition?: Vector3Dto | null;
|
targetPosition?: Vector3Dto | null;
|
||||||
@@ -100,7 +100,9 @@ export interface ShipSubTaskSnapshot {
|
|||||||
summary: string;
|
summary: string;
|
||||||
targetEntityId?: string | null;
|
targetEntityId?: string | null;
|
||||||
targetSystemId?: string | null;
|
targetSystemId?: string | null;
|
||||||
targetNodeId?: string | null;
|
targetAnchorId?: string | null;
|
||||||
|
targetResourceNodeId?: string | null;
|
||||||
|
targetResourceDepositId?: string | null;
|
||||||
targetPosition?: Vector3Dto | null;
|
targetPosition?: Vector3Dto | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
moduleId?: string | null;
|
moduleId?: string | null;
|
||||||
@@ -143,6 +145,7 @@ export interface ShipSnapshot {
|
|||||||
purpose: string;
|
purpose: string;
|
||||||
type: string;
|
type: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
|
anchorId?: string | null;
|
||||||
localPosition: Vector3Dto;
|
localPosition: Vector3Dto;
|
||||||
localVelocity: Vector3Dto;
|
localVelocity: Vector3Dto;
|
||||||
targetLocalPosition: Vector3Dto;
|
targetLocalPosition: Vector3Dto;
|
||||||
@@ -159,11 +162,11 @@ export interface ShipSnapshot {
|
|||||||
controlReason?: string | null;
|
controlReason?: string | null;
|
||||||
lastReplanReason?: string | null;
|
lastReplanReason?: string | null;
|
||||||
lastAccessFailureReason?: string | null;
|
lastAccessFailureReason?: string | null;
|
||||||
celestialId?: string | null;
|
|
||||||
dockedStationId?: string | null;
|
dockedStationId?: string | null;
|
||||||
commanderId?: string | null;
|
commanderId?: string | null;
|
||||||
policySetId?: string | null;
|
policySetId?: string | null;
|
||||||
cargoCapacity: number;
|
cargoCapacity: number;
|
||||||
|
cargoTypes: string[];
|
||||||
travelSpeed: number;
|
travelSpeed: number;
|
||||||
travelSpeedUnit: string;
|
travelSpeedUnit: string;
|
||||||
inventory: InventoryEntry[];
|
inventory: InventoryEntry[];
|
||||||
@@ -178,18 +181,18 @@ export interface ShipDelta extends ShipSnapshot {}
|
|||||||
export interface ShipSpatialStateSnapshot {
|
export interface ShipSpatialStateSnapshot {
|
||||||
spaceLayer: string;
|
spaceLayer: string;
|
||||||
currentSystemId: string;
|
currentSystemId: string;
|
||||||
currentCelestialId?: string | null;
|
currentAnchorId?: string | null;
|
||||||
localPosition?: Vector3Dto | null;
|
localPosition?: Vector3Dto | null;
|
||||||
systemPosition?: Vector3Dto | null;
|
systemPosition?: Vector3Dto | null;
|
||||||
movementRegime: string;
|
movementRegime: string;
|
||||||
destinationNodeId?: string | null;
|
destinationAnchorId?: string | null;
|
||||||
transit?: ShipTransitSnapshot | null;
|
transit?: ShipTransitSnapshot | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShipTransitSnapshot {
|
export interface ShipTransitSnapshot {
|
||||||
regime: string;
|
regime: string;
|
||||||
originNodeId?: string | null;
|
originAnchorId?: string | null;
|
||||||
destinationNodeId?: string | null;
|
destinationAnchorId?: string | null;
|
||||||
startedAtUtc?: string | null;
|
startedAtUtc?: string | null;
|
||||||
arrivalDueAtUtc?: string | null;
|
arrivalDueAtUtc?: string | null;
|
||||||
progress: number;
|
progress: number;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import type {
|
|||||||
FactionSnapshot,
|
FactionSnapshot,
|
||||||
} from "./contractsFactions";
|
} from "./contractsFactions";
|
||||||
import type {
|
import type {
|
||||||
|
AnchorDelta,
|
||||||
|
AnchorSnapshot,
|
||||||
CelestialDelta,
|
CelestialDelta,
|
||||||
CelestialSnapshot,
|
CelestialSnapshot,
|
||||||
ResourceNodeDelta,
|
ResourceNodeDelta,
|
||||||
@@ -37,6 +39,7 @@ export interface WorldSnapshot {
|
|||||||
generatedAtUtc: string;
|
generatedAtUtc: string;
|
||||||
systems: SystemSnapshot[];
|
systems: SystemSnapshot[];
|
||||||
celestials: CelestialSnapshot[];
|
celestials: CelestialSnapshot[];
|
||||||
|
anchors: AnchorSnapshot[];
|
||||||
nodes: ResourceNodeSnapshot[];
|
nodes: ResourceNodeSnapshot[];
|
||||||
stations: import("./contractsInfrastructure").StationSnapshot[];
|
stations: import("./contractsInfrastructure").StationSnapshot[];
|
||||||
claims: ClaimSnapshot[];
|
claims: ClaimSnapshot[];
|
||||||
@@ -57,6 +60,7 @@ export interface WorldDelta {
|
|||||||
requiresSnapshotRefresh: boolean;
|
requiresSnapshotRefresh: boolean;
|
||||||
events: SimulationEventRecord[];
|
events: SimulationEventRecord[];
|
||||||
celestials: CelestialDelta[];
|
celestials: CelestialDelta[];
|
||||||
|
anchors: AnchorDelta[];
|
||||||
nodes: ResourceNodeDelta[];
|
nodes: ResourceNodeDelta[];
|
||||||
stations: import("./contractsInfrastructure").StationDelta[];
|
stations: import("./contractsInfrastructure").StationDelta[];
|
||||||
claims: ClaimDelta[];
|
claims: ClaimDelta[];
|
||||||
@@ -84,7 +88,7 @@ export interface SimulationEventRecord {
|
|||||||
export interface ObserverScope {
|
export interface ObserverScope {
|
||||||
scopeKind: string;
|
scopeKind: string;
|
||||||
systemId?: string | null;
|
systemId?: string | null;
|
||||||
celestialId?: string | null;
|
anchorId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrbitalSimulationSnapshot {
|
export interface OrbitalSimulationSnapshot {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export interface PlayerDirectiveCommandRequest {
|
|||||||
sourceStationId?: string | null;
|
sourceStationId?: string | null;
|
||||||
destinationStationId?: string | null;
|
destinationStationId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
preferredNodeId?: string | null;
|
preferredAnchorId?: string | null;
|
||||||
preferredConstructionSiteId?: string | null;
|
preferredConstructionSiteId?: string | null;
|
||||||
preferredModuleId?: string | null;
|
preferredModuleId?: string | null;
|
||||||
priority: number;
|
priority: number;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface ShipOrderCommandRequest {
|
|||||||
sourceStationId?: string | null;
|
sourceStationId?: string | null;
|
||||||
destinationStationId?: string | null;
|
destinationStationId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
nodeId?: string | null;
|
anchorId?: string | null;
|
||||||
constructionSiteId?: string | null;
|
constructionSiteId?: string | null;
|
||||||
moduleId?: string | null;
|
moduleId?: string | null;
|
||||||
waitSeconds?: number | null;
|
waitSeconds?: number | null;
|
||||||
@@ -28,7 +28,7 @@ export interface ShipDefaultBehaviorCommandRequest {
|
|||||||
areaSystemId?: string | null;
|
areaSystemId?: string | null;
|
||||||
targetEntityId?: string | null;
|
targetEntityId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
preferredNodeId?: string | null;
|
preferredAnchorId?: string | null;
|
||||||
preferredConstructionSiteId?: string | null;
|
preferredConstructionSiteId?: string | null;
|
||||||
preferredModuleId?: string | null;
|
preferredModuleId?: string | null;
|
||||||
targetPosition?: Vector3Dto | null;
|
targetPosition?: Vector3Dto | null;
|
||||||
|
|||||||
@@ -366,6 +366,74 @@ canvas {
|
|||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar-dock {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 0 auto;
|
||||||
|
width: min(380px, 100vw);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 16px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(7, 14, 27, 0.9), rgba(7, 14, 27, 0.78)),
|
||||||
|
radial-gradient(circle at top right, rgba(127, 214, 255, 0.08), transparent 34%);
|
||||||
|
border-left: 1px solid rgba(132, 196, 255, 0.14);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
box-shadow: -18px 0 42px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 -8px;
|
||||||
|
width: 16px;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__resize-handle::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 6px;
|
||||||
|
background: rgba(132, 196, 255, 0.06);
|
||||||
|
transition: background 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__resize-handle:hover::before,
|
||||||
|
.viewer-right-sidebar__resize-handle--active::before {
|
||||||
|
background: rgba(132, 196, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__panel.entity-inspector-panel {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__error {
|
||||||
|
margin-top: 0.9rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: rgba(255, 116, 88, 0.14);
|
||||||
|
padding: 0.85rem 0.95rem;
|
||||||
|
color: #ffd8cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.viewer-stats-overlay {
|
.viewer-stats-overlay {
|
||||||
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -1705,6 +1773,62 @@ canvas {
|
|||||||
color: rgba(173, 220, 255, 0.58);
|
color: rgba(173, 220, 255, 0.58);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
padding: 0.8rem 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity__header,
|
||||||
|
.entity-inspector-capacity__scale {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity__header {
|
||||||
|
margin-bottom: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity__label {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(173, 220, 255, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity__value,
|
||||||
|
.entity-inspector-capacity__scale span {
|
||||||
|
font-family: var(--viewer-mono-font);
|
||||||
|
font-size: 0.76rem;
|
||||||
|
color: rgba(255, 255, 255, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity__track {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
height: 0.65rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-inspector-capacity__fill {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: linear-gradient(90deg, rgba(116, 196, 255, 0.5), rgba(116, 196, 255, 0.9));
|
||||||
|
}
|
||||||
|
|
||||||
.entity-inspector-panel__fallback {
|
.entity-inspector-panel__fallback {
|
||||||
margin-top: 0.9rem;
|
margin-top: 0.9rem;
|
||||||
font-size: 0.83rem;
|
font-size: 0.83rem;
|
||||||
@@ -1910,6 +2034,23 @@ canvas {
|
|||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar-dock {
|
||||||
|
inset: auto 20px 148px 20px;
|
||||||
|
width: auto;
|
||||||
|
max-height: 38vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar {
|
||||||
|
padding: 14px;
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid rgba(132, 196, 255, 0.14);
|
||||||
|
box-shadow: 0 -18px 42px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-right-sidebar__resize-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.viewer-stats-overlay-dock {
|
.viewer-stats-overlay-dock {
|
||||||
top: 96px;
|
top: 96px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface ViewerOrderContextMenuTarget {
|
|||||||
selection: Selectable;
|
selection: Selectable;
|
||||||
label: string;
|
label: string;
|
||||||
systemId?: string | null;
|
systemId?: string | null;
|
||||||
|
anchorId?: string | null;
|
||||||
itemId?: string | null;
|
itemId?: string | null;
|
||||||
targetPosition?: Vector3Dto | null;
|
targetPosition?: Vector3Dto | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface ResolveSelectionPositionParams {
|
|||||||
nodeVisuals: Map<string, NodeVisual>;
|
nodeVisuals: Map<string, NodeVisual>;
|
||||||
planetVisuals: PlanetVisual[];
|
planetVisuals: PlanetVisual[];
|
||||||
computeNodeLocalPosition: (visual: NodeVisual, timeSeconds: number) => THREE.Vector3;
|
computeNodeLocalPosition: (visual: NodeVisual, timeSeconds: number) => THREE.Vector3;
|
||||||
resolvePointPosition: (systemId: string, celestialId?: string | null) => THREE.Vector3;
|
resolvePointPosition: (systemId: string, celestialId?: string | null, anchorId?: string | null) => THREE.Vector3;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FocusOnSelectionParams extends ResolveSelectionPositionParams {
|
interface FocusOnSelectionParams extends ResolveSelectionPositionParams {
|
||||||
@@ -47,7 +47,7 @@ interface SeedSystemFocusParams {
|
|||||||
nodeVisuals: Map<string, NodeVisual>;
|
nodeVisuals: Map<string, NodeVisual>;
|
||||||
planetVisuals: PlanetVisual[];
|
planetVisuals: PlanetVisual[];
|
||||||
computeNodeLocalPosition: (visual: NodeVisual, timeSeconds: number) => THREE.Vector3;
|
computeNodeLocalPosition: (visual: NodeVisual, timeSeconds: number) => THREE.Vector3;
|
||||||
resolvePointPosition: (systemId: string, celestialId?: string | null) => THREE.Vector3;
|
resolvePointPosition: (systemId: string, celestialId?: string | null, anchorId?: string | null) => THREE.Vector3;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CameraFocusParams {
|
interface CameraFocusParams {
|
||||||
@@ -107,6 +107,7 @@ export function applyPanFromScreenDelta(
|
|||||||
delta: THREE.Vector2,
|
delta: THREE.Vector2,
|
||||||
orbitYaw: number,
|
orbitYaw: number,
|
||||||
currentDistance: number,
|
currentDistance: number,
|
||||||
|
cameraFovDegrees: number,
|
||||||
povLevel: PovLevel,
|
povLevel: PovLevel,
|
||||||
activeSystemId: string | undefined,
|
activeSystemId: string | undefined,
|
||||||
systemAnchor: THREE.Vector3,
|
systemAnchor: THREE.Vector3,
|
||||||
@@ -125,18 +126,19 @@ export function applyPanFromScreenDelta(
|
|||||||
|
|
||||||
const forward = new THREE.Vector3(Math.cos(orbitYaw), 0, Math.sin(orbitYaw));
|
const forward = new THREE.Vector3(Math.cos(orbitYaw), 0, Math.sin(orbitYaw));
|
||||||
const right = new THREE.Vector3(-forward.z, 0, forward.x);
|
const right = new THREE.Vector3(-forward.z, 0, forward.x);
|
||||||
const pan = right.multiplyScalar(-normalized.x).add(forward.multiplyScalar(-normalized.y));
|
const visibleHeight = 2 * Math.tan(THREE.MathUtils.degToRad(cameraFovDegrees) * 0.5) * currentDistance;
|
||||||
|
const visibleWidth = visibleHeight * (safeWidth / safeHeight);
|
||||||
|
const horizontalDistance = normalized.x * visibleWidth;
|
||||||
|
const verticalDistance = -normalized.y * visibleHeight;
|
||||||
|
const pan = right.multiplyScalar(horizontalDistance).add(forward.multiplyScalar(verticalDistance));
|
||||||
|
|
||||||
if (activeSystemId) {
|
if (activeSystemId) {
|
||||||
const scale = povLevel === "system"
|
const systemDisplayToKilometers = 1 / (DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE);
|
||||||
? THREE.MathUtils.mapLinear(currentDistance, 80, 4000, KILOMETERS_PER_AU * 0.35, KILOMETERS_PER_AU * 6.5)
|
systemAnchor.addScaledVector(pan, systemDisplayToKilometers);
|
||||||
: THREE.MathUtils.mapLinear(currentDistance, minimumDistance, 120, 1200, 180000);
|
|
||||||
systemAnchor.addScaledVector(pan, scale);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const galaxyScale = THREE.MathUtils.mapLinear(currentDistance, minimumDistance, maximumDistance, 1800, 22000);
|
galaxyAnchor.add(pan);
|
||||||
galaxyAnchor.addScaledVector(pan, galaxyScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function determineActiveSystemId(params: DetermineActiveSystemParams): string | undefined {
|
export function determineActiveSystemId(params: DetermineActiveSystemParams): string | undefined {
|
||||||
@@ -235,11 +237,11 @@ export function resolveSelectionPosition(params: ResolveSelectionPositionParams)
|
|||||||
}
|
}
|
||||||
if (selection.kind === "claim") {
|
if (selection.kind === "claim") {
|
||||||
const claim = world.claims.get(selection.id);
|
const claim = world.claims.get(selection.id);
|
||||||
return claim ? resolvePointPosition(claim.systemId, claim.celestialId) : undefined;
|
return claim ? resolvePointPosition(claim.systemId, null, claim.anchorId) : undefined;
|
||||||
}
|
}
|
||||||
if (selection.kind === "construction-site") {
|
if (selection.kind === "construction-site") {
|
||||||
const site = world.constructionSites.get(selection.id);
|
const site = world.constructionSites.get(selection.id);
|
||||||
return site ? resolvePointPosition(site.systemId, site.celestialId) : undefined;
|
return site ? resolvePointPosition(site.systemId, null, site.anchorId) : undefined;
|
||||||
}
|
}
|
||||||
if (selection.kind === "planet") {
|
if (selection.kind === "planet") {
|
||||||
const system = world.systems.get(selection.systemId);
|
const system = world.systems.get(selection.systemId);
|
||||||
|
|||||||
@@ -30,8 +30,14 @@ export function createViewerControllers(host: any) {
|
|||||||
claimGroup: host.systemLayer.claimGroup,
|
claimGroup: host.systemLayer.claimGroup,
|
||||||
constructionSiteGroup: host.systemLayer.constructionSiteGroup,
|
constructionSiteGroup: host.systemLayer.constructionSiteGroup,
|
||||||
shipGroup: host.systemLayer.shipGroup,
|
shipGroup: host.systemLayer.shipGroup,
|
||||||
|
localNodeGroup: host.localLayer.nodeGroup,
|
||||||
|
localStationGroup: host.localLayer.stationGroup,
|
||||||
|
localClaimGroup: host.localLayer.claimGroup,
|
||||||
|
localConstructionSiteGroup: host.localLayer.constructionSiteGroup,
|
||||||
|
localShipGroup: host.localLayer.shipGroup,
|
||||||
galaxySelectableTargets: host.galaxyLayer.selectableTargets,
|
galaxySelectableTargets: host.galaxyLayer.selectableTargets,
|
||||||
systemSelectableTargets: host.systemLayer.selectableTargets,
|
systemSelectableTargets: host.systemLayer.selectableTargets,
|
||||||
|
localSelectableTargets: host.localLayer.selectableTargets,
|
||||||
systemVisuals: host.galaxyLayer.systemVisuals,
|
systemVisuals: host.galaxyLayer.systemVisuals,
|
||||||
planetVisuals: host.systemLayer.planetVisuals,
|
planetVisuals: host.systemLayer.planetVisuals,
|
||||||
celestialVisuals: host.systemLayer.celestialVisuals,
|
celestialVisuals: host.systemLayer.celestialVisuals,
|
||||||
@@ -40,6 +46,11 @@ export function createViewerControllers(host: any) {
|
|||||||
claimVisuals: host.systemLayer.claimVisuals,
|
claimVisuals: host.systemLayer.claimVisuals,
|
||||||
constructionSiteVisuals: host.systemLayer.constructionSiteVisuals,
|
constructionSiteVisuals: host.systemLayer.constructionSiteVisuals,
|
||||||
shipVisuals: host.systemLayer.shipVisuals,
|
shipVisuals: host.systemLayer.shipVisuals,
|
||||||
|
localNodeVisuals: host.localLayer.nodeVisuals,
|
||||||
|
localStationVisuals: host.localLayer.stationVisuals,
|
||||||
|
localClaimVisuals: host.localLayer.claimVisuals,
|
||||||
|
localConstructionSiteVisuals: host.localLayer.constructionSiteVisuals,
|
||||||
|
localShipVisuals: host.localLayer.shipVisuals,
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigationController = new ViewerNavigationController({
|
const navigationController = new ViewerNavigationController({
|
||||||
@@ -152,8 +163,9 @@ export function createViewerControllers(host: any) {
|
|||||||
applyClaimDeltas: (claims) => sceneDataController.applyClaimDeltas(claims),
|
applyClaimDeltas: (claims) => sceneDataController.applyClaimDeltas(claims),
|
||||||
applyConstructionSiteDeltas: (sites) => sceneDataController.applyConstructionSiteDeltas(sites),
|
applyConstructionSiteDeltas: (sites) => sceneDataController.applyConstructionSiteDeltas(sites),
|
||||||
applyShipDeltas: (ships, tickIntervalMs) => sceneDataController.applyShipDeltas(ships, tickIntervalMs),
|
applyShipDeltas: (ships, tickIntervalMs) => sceneDataController.applyShipDeltas(ships, tickIntervalMs),
|
||||||
|
refreshLocalLayer: () => sceneDataController.refreshLocalLayer(host.world, host.resolveFocusedAnchorId()),
|
||||||
refreshHistoryWindows: () => host.refreshHistoryWindows(),
|
refreshHistoryWindows: () => host.refreshHistoryWindows(),
|
||||||
resolveFocusedCelestialId: () => host.resolveFocusedCelestialId(),
|
resolveFocusedAnchorId: () => host.resolveFocusedAnchorId(),
|
||||||
updateSystemSummaries: () => host.updateSystemSummaries(),
|
updateSystemSummaries: () => host.updateSystemSummaries(),
|
||||||
applyZoomPresentation: () => presentationController.applyZoomPresentation(),
|
applyZoomPresentation: () => presentationController.applyZoomPresentation(),
|
||||||
updateNetworkPanel: () => presentationController.updateNetworkPanel(),
|
updateNetworkPanel: () => presentationController.updateNetworkPanel(),
|
||||||
@@ -191,8 +203,10 @@ export function createViewerControllers(host: any) {
|
|||||||
mouse: host.mouse,
|
mouse: host.mouse,
|
||||||
galaxyCamera: host.galaxyLayer.camera,
|
galaxyCamera: host.galaxyLayer.camera,
|
||||||
systemCamera: host.systemLayer.camera,
|
systemCamera: host.systemLayer.camera,
|
||||||
|
localCamera: host.localLayer.camera,
|
||||||
galaxySelectableTargets: host.galaxyLayer.selectableTargets,
|
galaxySelectableTargets: host.galaxyLayer.selectableTargets,
|
||||||
systemSelectableTargets: host.systemLayer.selectableTargets,
|
systemSelectableTargets: host.systemLayer.selectableTargets,
|
||||||
|
localSelectableTargets: host.localLayer.selectableTargets,
|
||||||
hoverLabelEl: host.hoverLabelEl,
|
hoverLabelEl: host.hoverLabelEl,
|
||||||
hoverConnectorLineEl: host.hoverConnectorLineEl,
|
hoverConnectorLineEl: host.hoverConnectorLineEl,
|
||||||
marqueeEl: host.marqueeEl,
|
marqueeEl: host.marqueeEl,
|
||||||
@@ -244,6 +258,9 @@ export function createViewerControllers(host: any) {
|
|||||||
delta,
|
delta,
|
||||||
host.orbitYaw,
|
host.orbitYaw,
|
||||||
host.currentDistance,
|
host.currentDistance,
|
||||||
|
host.activeSystemId
|
||||||
|
? (host.povLevel === "local" ? host.localLayer.camera.fov : host.systemLayer.camera.fov)
|
||||||
|
: host.galaxyLayer.camera.fov,
|
||||||
host.povLevel,
|
host.povLevel,
|
||||||
host.activeSystemId,
|
host.activeSystemId,
|
||||||
host.systemAnchor,
|
host.systemAnchor,
|
||||||
|
|||||||
@@ -148,9 +148,11 @@ export function updateFollowCamera(params: {
|
|||||||
|
|
||||||
if (ship.spatialState.movementRegime === "ftl-transit") {
|
if (ship.spatialState.movementRegime === "ftl-transit") {
|
||||||
systemAnchor.set(0, 0, 0);
|
systemAnchor.set(0, 0, 0);
|
||||||
const destinationNodeId = ship.spatialState.transit?.destinationNodeId;
|
const destinationAnchorId = ship.spatialState.transit?.destinationAnchorId ?? ship.spatialState.destinationAnchorId;
|
||||||
const destinationCelestial = destinationNodeId ? world.celestials.get(destinationNodeId) : undefined;
|
const destinationAnchor = destinationAnchorId ? world.anchors.get(destinationAnchorId) : undefined;
|
||||||
const destinationSystem = destinationCelestial ? world.systems.get(destinationCelestial.systemId) : undefined;
|
const destinationSystem = destinationAnchor
|
||||||
|
? world.systems.get(destinationAnchor.systemId)
|
||||||
|
: undefined;
|
||||||
const originSystem = world.systems.get(ship.systemId);
|
const originSystem = world.systems.get(ship.systemId);
|
||||||
if (originSystem && destinationSystem) {
|
if (originSystem && destinationSystem) {
|
||||||
followCameraDesiredDirection
|
followCameraDesiredDirection
|
||||||
|
|||||||
@@ -36,14 +36,17 @@ export function pickSelectableAtClientPosition(
|
|||||||
renderer: THREE.WebGLRenderer,
|
renderer: THREE.WebGLRenderer,
|
||||||
raycaster: THREE.Raycaster,
|
raycaster: THREE.Raycaster,
|
||||||
mouse: THREE.Vector2,
|
mouse: THREE.Vector2,
|
||||||
|
povLevel: PovLevel,
|
||||||
galaxyCamera: THREE.Camera,
|
galaxyCamera: THREE.Camera,
|
||||||
galaxySelectableTargets: Map<THREE.Object3D, Selectable>,
|
galaxySelectableTargets: Map<THREE.Object3D, Selectable>,
|
||||||
systemCamera: THREE.Camera,
|
systemCamera: THREE.Camera,
|
||||||
systemSelectableTargets: Map<THREE.Object3D, Selectable>,
|
systemSelectableTargets: Map<THREE.Object3D, Selectable>,
|
||||||
|
localCamera: THREE.Camera,
|
||||||
|
localSelectableTargets: Map<THREE.Object3D, Selectable>,
|
||||||
clientX: number,
|
clientX: number,
|
||||||
clientY: number,
|
clientY: number,
|
||||||
) {
|
) {
|
||||||
const hit = pickSelectableHitAtClientPosition(renderer, raycaster, mouse, galaxyCamera, galaxySelectableTargets, systemCamera, systemSelectableTargets, clientX, clientY);
|
const hit = pickSelectableHitAtClientPosition(renderer, raycaster, mouse, povLevel, galaxyCamera, galaxySelectableTargets, systemCamera, systemSelectableTargets, localCamera, localSelectableTargets, clientX, clientY);
|
||||||
return hit?.selection;
|
return hit?.selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +54,23 @@ export function pickSelectableHitAtClientPosition(
|
|||||||
renderer: THREE.WebGLRenderer,
|
renderer: THREE.WebGLRenderer,
|
||||||
raycaster: THREE.Raycaster,
|
raycaster: THREE.Raycaster,
|
||||||
mouse: THREE.Vector2,
|
mouse: THREE.Vector2,
|
||||||
|
povLevel: PovLevel,
|
||||||
galaxyCamera: THREE.Camera,
|
galaxyCamera: THREE.Camera,
|
||||||
galaxySelectableTargets: Map<THREE.Object3D, Selectable>,
|
galaxySelectableTargets: Map<THREE.Object3D, Selectable>,
|
||||||
systemCamera: THREE.Camera,
|
systemCamera: THREE.Camera,
|
||||||
systemSelectableTargets: Map<THREE.Object3D, Selectable>,
|
systemSelectableTargets: Map<THREE.Object3D, Selectable>,
|
||||||
|
localCamera: THREE.Camera,
|
||||||
|
localSelectableTargets: Map<THREE.Object3D, Selectable>,
|
||||||
clientX: number,
|
clientX: number,
|
||||||
clientY: number,
|
clientY: number,
|
||||||
): HoverPickResult | undefined {
|
): HoverPickResult | undefined {
|
||||||
|
if (povLevel === "local") {
|
||||||
|
const localHit = pickOneCamera(renderer, raycaster, mouse, localCamera, localSelectableTargets, clientX, clientY);
|
||||||
|
if (localHit) {
|
||||||
|
return localHit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try system camera first (higher priority when in a system)
|
// Try system camera first (higher priority when in a system)
|
||||||
const systemHit = pickOneCamera(renderer, raycaster, mouse, systemCamera, systemSelectableTargets, clientX, clientY);
|
const systemHit = pickOneCamera(renderer, raycaster, mouse, systemCamera, systemSelectableTargets, clientX, clientY);
|
||||||
if (systemHit) {
|
if (systemHit) {
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ export interface ViewerInteractionContext {
|
|||||||
mouse: THREE.Vector2;
|
mouse: THREE.Vector2;
|
||||||
galaxyCamera: THREE.PerspectiveCamera;
|
galaxyCamera: THREE.PerspectiveCamera;
|
||||||
systemCamera: THREE.PerspectiveCamera;
|
systemCamera: THREE.PerspectiveCamera;
|
||||||
|
localCamera: THREE.PerspectiveCamera;
|
||||||
galaxySelectableTargets: Map<THREE.Object3D, Selectable>;
|
galaxySelectableTargets: Map<THREE.Object3D, Selectable>;
|
||||||
systemSelectableTargets: Map<THREE.Object3D, Selectable>;
|
systemSelectableTargets: Map<THREE.Object3D, Selectable>;
|
||||||
|
localSelectableTargets: Map<THREE.Object3D, Selectable>;
|
||||||
hoverLabelEl: HTMLDivElement;
|
hoverLabelEl: HTMLDivElement;
|
||||||
hoverConnectorLineEl: SVGLineElement;
|
hoverConnectorLineEl: SVGLineElement;
|
||||||
marqueeEl: HTMLDivElement;
|
marqueeEl: HTMLDivElement;
|
||||||
@@ -391,10 +393,13 @@ export class ViewerInteractionController {
|
|||||||
this.context.renderer,
|
this.context.renderer,
|
||||||
this.context.raycaster,
|
this.context.raycaster,
|
||||||
this.context.mouse,
|
this.context.mouse,
|
||||||
|
this.context.getPovLevel(),
|
||||||
this.context.galaxyCamera,
|
this.context.galaxyCamera,
|
||||||
this.context.galaxySelectableTargets,
|
this.context.galaxySelectableTargets,
|
||||||
this.context.systemCamera,
|
this.context.systemCamera,
|
||||||
this.context.systemSelectableTargets,
|
this.context.systemSelectableTargets,
|
||||||
|
this.context.localCamera,
|
||||||
|
this.context.localSelectableTargets,
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
);
|
);
|
||||||
@@ -405,10 +410,13 @@ export class ViewerInteractionController {
|
|||||||
this.context.renderer,
|
this.context.renderer,
|
||||||
this.context.raycaster,
|
this.context.raycaster,
|
||||||
this.context.mouse,
|
this.context.mouse,
|
||||||
|
this.context.getPovLevel(),
|
||||||
this.context.galaxyCamera,
|
this.context.galaxyCamera,
|
||||||
this.context.galaxySelectableTargets,
|
this.context.galaxySelectableTargets,
|
||||||
this.context.systemCamera,
|
this.context.systemCamera,
|
||||||
this.context.systemSelectableTargets,
|
this.context.systemSelectableTargets,
|
||||||
|
this.context.localCamera,
|
||||||
|
this.context.localSelectableTargets,
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
);
|
);
|
||||||
@@ -466,6 +474,7 @@ export class ViewerInteractionController {
|
|||||||
selection,
|
selection,
|
||||||
label: node.itemId,
|
label: node.itemId,
|
||||||
systemId: node.systemId,
|
systemId: node.systemId,
|
||||||
|
anchorId: node.anchorId,
|
||||||
itemId: node.itemId,
|
itemId: node.itemId,
|
||||||
targetPosition: node.localPosition,
|
targetPosition: node.localPosition,
|
||||||
} : null;
|
} : null;
|
||||||
|
|||||||
@@ -1,17 +1,50 @@
|
|||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
import type {
|
||||||
|
ClaimVisual,
|
||||||
|
ConstructionSiteVisual,
|
||||||
|
NodeVisual,
|
||||||
|
Selectable,
|
||||||
|
ShipVisual,
|
||||||
|
StructureVisual,
|
||||||
|
} from "./viewerTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local rendering layer.
|
* Local rendering layer.
|
||||||
* Scene coordinate unit: reserved for future close-up detail.
|
* Scene coordinate unit: reserved for future close-up detail.
|
||||||
* Camera far plane covers immediate surroundings.
|
* Camera far plane covers immediate surroundings.
|
||||||
* Currently empty — populated when local-space objects are introduced.
|
|
||||||
*/
|
*/
|
||||||
export class LocalLayer {
|
export class LocalLayer {
|
||||||
readonly scene = new THREE.Scene();
|
readonly scene = new THREE.Scene();
|
||||||
readonly camera = new THREE.PerspectiveCamera(50, 1, 0.1, 2000);
|
readonly camera = new THREE.PerspectiveCamera(50, 1, 0.1, 2000);
|
||||||
|
readonly nodeGroup = new THREE.Group();
|
||||||
|
readonly stationGroup = new THREE.Group();
|
||||||
|
readonly claimGroup = new THREE.Group();
|
||||||
|
readonly constructionSiteGroup = new THREE.Group();
|
||||||
|
readonly shipGroup = new THREE.Group();
|
||||||
|
|
||||||
|
readonly selectableTargets = new Map<THREE.Object3D, Selectable>();
|
||||||
|
readonly shipVisuals = new Map<string, ShipVisual>();
|
||||||
|
readonly nodeVisuals = new Map<string, NodeVisual>();
|
||||||
|
readonly stationVisuals = new Map<string, StructureVisual>();
|
||||||
|
readonly claimVisuals = new Map<string, ClaimVisual>();
|
||||||
|
readonly constructionSiteVisuals = new Map<string, ConstructionSiteVisual>();
|
||||||
|
|
||||||
private static readonly ORIGIN = new THREE.Vector3(0, 0, 0);
|
private static readonly ORIGIN = new THREE.Vector3(0, 0, 0);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scene.add(new THREE.AmbientLight(0x90a6c0, 0.8));
|
||||||
|
const keyLight = new THREE.DirectionalLight(0xdcecff, 1.4);
|
||||||
|
keyLight.position.set(180, 220, 140);
|
||||||
|
this.scene.add(keyLight);
|
||||||
|
this.scene.add(
|
||||||
|
this.nodeGroup,
|
||||||
|
this.stationGroup,
|
||||||
|
this.claimGroup,
|
||||||
|
this.constructionSiteGroup,
|
||||||
|
this.shipGroup,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updateCamera(orbitOffset: THREE.Vector3) {
|
updateCamera(orbitOffset: THREE.Vector3) {
|
||||||
this.camera.position.copy(orbitOffset);
|
this.camera.position.copy(orbitOffset);
|
||||||
this.camera.lookAt(LocalLayer.ORIGIN);
|
this.camera.lookAt(LocalLayer.ORIGIN);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
|
import { scaleGalaxyVector, toThreeVector } from "./viewerMath";
|
||||||
import {
|
import {
|
||||||
determineActiveSystemId,
|
determineActiveSystemId,
|
||||||
focusOnSelection,
|
focusOnSelection,
|
||||||
@@ -62,6 +63,22 @@ export interface ViewerNavigationContext {
|
|||||||
export class ViewerNavigationController {
|
export class ViewerNavigationController {
|
||||||
constructor(private readonly context: ViewerNavigationContext) {}
|
constructor(private readonly context: ViewerNavigationContext) {}
|
||||||
|
|
||||||
|
syncGalaxyAnchorToActiveSystem() {
|
||||||
|
const world = this.context.getWorld();
|
||||||
|
const activeSystemId = this.context.getActiveSystemId();
|
||||||
|
const povLevel = this.context.getPovLevel();
|
||||||
|
if (!world || !activeSystemId || povLevel === "galaxy") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const system = world.systems.get(activeSystemId);
|
||||||
|
if (!system) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.context.galaxyAnchor.copy(scaleGalaxyVector(toThreeVector(system.galaxyPosition)));
|
||||||
|
}
|
||||||
|
|
||||||
focusOnSelection(selection: Selectable) {
|
focusOnSelection(selection: Selectable) {
|
||||||
focusOnSelection({
|
focusOnSelection({
|
||||||
world: this.context.getWorld(),
|
world: this.context.getWorld(),
|
||||||
@@ -70,7 +87,7 @@ export class ViewerNavigationController {
|
|||||||
nodeVisuals: this.context.nodeVisuals,
|
nodeVisuals: this.context.nodeVisuals,
|
||||||
planetVisuals: this.context.planetVisuals,
|
planetVisuals: this.context.planetVisuals,
|
||||||
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
||||||
resolvePointPosition: (systemId, celestialId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId),
|
resolvePointPosition: (systemId, celestialId, anchorId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId, anchorId),
|
||||||
activeSystemId: this.context.getActiveSystemId(),
|
activeSystemId: this.context.getActiveSystemId(),
|
||||||
galaxyAnchor: this.context.galaxyAnchor,
|
galaxyAnchor: this.context.galaxyAnchor,
|
||||||
systemAnchor: this.context.systemAnchor,
|
systemAnchor: this.context.systemAnchor,
|
||||||
@@ -85,7 +102,7 @@ export class ViewerNavigationController {
|
|||||||
nodeVisuals: this.context.nodeVisuals,
|
nodeVisuals: this.context.nodeVisuals,
|
||||||
planetVisuals: this.context.planetVisuals,
|
planetVisuals: this.context.planetVisuals,
|
||||||
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
||||||
resolvePointPosition: (systemId, celestialId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId),
|
resolvePointPosition: (systemId, celestialId, anchorId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId, anchorId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +188,7 @@ export class ViewerNavigationController {
|
|||||||
nodeVisuals: this.context.nodeVisuals,
|
nodeVisuals: this.context.nodeVisuals,
|
||||||
planetVisuals: this.context.planetVisuals,
|
planetVisuals: this.context.planetVisuals,
|
||||||
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
|
||||||
resolvePointPosition: (systemIdValue, celestialId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemIdValue, celestialId),
|
resolvePointPosition: (systemIdValue, celestialId, anchorId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemIdValue, celestialId, anchorId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const moduleProductionById = new Map<string, {
|
|||||||
const itemTransportById = new Map<string, string>(
|
const itemTransportById = new Map<string, string>(
|
||||||
(itemsData as { id: string; transport: string }[]).map((item) => [item.id, item.transport]),
|
(itemsData as { id: string; transport: string }[]).map((item) => [item.id, item.transport]),
|
||||||
);
|
);
|
||||||
import { describeCelestialPathWithinSystem, describeOrbitalParent, describeSelectable, describeShipBehavior, describeShipCurrentAction, describeShipOrder, describeShipState, getSelectionGroup, renderSystemDetails } from "./viewerSelection";
|
import { describeAnchorPathWithinSystem, describeCelestialPathWithinSystem, describeOrbitalParent, describeSelectable, describeShipBehavior, describeShipCurrentAction, describeShipOrder, describeShipState, getSelectionGroup, renderSystemDetails } from "./viewerSelection";
|
||||||
import type {
|
import type {
|
||||||
CameraMode,
|
CameraMode,
|
||||||
NodeVisual,
|
NodeVisual,
|
||||||
@@ -461,7 +461,7 @@ export function buildDetailPanelState(params: DetailPanelParams) {
|
|||||||
title: `${celestial.kind} celestial`,
|
title: `${celestial.kind} celestial`,
|
||||||
bodyHtml: `
|
bodyHtml: `
|
||||||
<p>${celestial.systemId}</p>
|
<p>${celestial.systemId}</p>
|
||||||
<p>Parent ${celestial.parentNodeId ?? "none"}<br>Orbit ref ${celestial.orbitReferenceId ?? "none"}</p>
|
<p>Parent ${celestial.parentAnchorId ?? "none"}<br>Orbit ref ${celestial.orbitReferenceId ?? "none"}</p>
|
||||||
<p>Occupying structure ${celestial.occupyingStructureId ?? "none"}</p>
|
<p>Occupying structure ${celestial.occupyingStructureId ?? "none"}</p>
|
||||||
<p>Local space radius ${celestial.localSpaceRadius.toFixed(0)} km</p>
|
<p>Local space radius ${celestial.localSpaceRadius.toFixed(0)} km</p>
|
||||||
`,
|
`,
|
||||||
@@ -477,7 +477,7 @@ export function buildDetailPanelState(params: DetailPanelParams) {
|
|||||||
title: `Claim ${claim.id}`,
|
title: `Claim ${claim.id}`,
|
||||||
bodyHtml: `
|
bodyHtml: `
|
||||||
<p>${claim.systemId}</p>
|
<p>${claim.systemId}</p>
|
||||||
<p>Celestial ${claim.celestialId}</p>
|
<p>Anchor ${describeAnchorPathWithinSystem(world, claim.systemId, claim.anchorId) ?? claim.anchorId}</p>
|
||||||
<p>State ${claim.state}<br>Health ${claim.health.toFixed(0)}</p>
|
<p>State ${claim.state}<br>Health ${claim.health.toFixed(0)}</p>
|
||||||
<p>Activates ${new Date(claim.activatesAtUtc).toLocaleTimeString()}</p>
|
<p>Activates ${new Date(claim.activatesAtUtc).toLocaleTimeString()}</p>
|
||||||
`,
|
`,
|
||||||
@@ -494,7 +494,7 @@ export function buildDetailPanelState(params: DetailPanelParams) {
|
|||||||
title: `Construction ${site.id}`,
|
title: `Construction ${site.id}`,
|
||||||
bodyHtml: `
|
bodyHtml: `
|
||||||
<p>${site.systemId}</p>
|
<p>${site.systemId}</p>
|
||||||
<p>Celestial ${site.celestialId}</p>
|
<p>Anchor ${describeAnchorPathWithinSystem(world, site.systemId, site.anchorId) ?? site.anchorId}</p>
|
||||||
<p>${site.targetKind} ${site.targetDefinitionId}</p>
|
<p>${site.targetKind} ${site.targetDefinitionId}</p>
|
||||||
<p>State ${site.state}<br>Progress ${(site.progress * 100).toFixed(0)}%</p>
|
<p>State ${site.state}<br>Progress ${(site.progress * 100).toFixed(0)}%</p>
|
||||||
<p>Orders ${orderCount}<br>Assigned constructors ${site.assignedConstructorShipIds.length}</p>
|
<p>Orders ${orderCount}<br>Assigned constructors ${site.assignedConstructorShipIds.length}</p>
|
||||||
@@ -608,24 +608,33 @@ export function describeSelectionParent(
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
return station.celestialId
|
return station.anchorId
|
||||||
? describeCelestialPathWithinSystem(world, station.systemId, station.celestialId) ?? `${station.systemId} network`
|
? describeAnchorPathWithinSystem(world, station.systemId, station.anchorId) ?? `${station.systemId} network`
|
||||||
: "unknown";
|
: "unknown";
|
||||||
}
|
}
|
||||||
if (selection.kind === "node") {
|
if (selection.kind === "node") {
|
||||||
const node = world.nodes.get(selection.id);
|
const node = world.nodes.get(selection.id);
|
||||||
const visual = node ? nodeVisuals.get(selection.id) : undefined;
|
return node
|
||||||
return describeOrbitalParent(world, node?.systemId, visual?.anchor);
|
? describeAnchorPathWithinSystem(world, node.systemId, node.anchorId) ?? node.anchorId
|
||||||
|
: "unknown";
|
||||||
}
|
}
|
||||||
if (selection.kind === "celestial") {
|
if (selection.kind === "celestial") {
|
||||||
const celestial = world.celestials.get(selection.id);
|
const celestial = world.celestials.get(selection.id);
|
||||||
return celestial?.parentNodeId ?? `${celestial?.systemId ?? "unknown"} network`;
|
return celestial
|
||||||
|
? describeCelestialPathWithinSystem(world, celestial.systemId, celestial.id) ?? `${celestial.systemId} network`
|
||||||
|
: "unknown";
|
||||||
}
|
}
|
||||||
if (selection.kind === "claim") {
|
if (selection.kind === "claim") {
|
||||||
return world.claims.get(selection.id)?.celestialId ?? "unknown";
|
const claim = world.claims.get(selection.id);
|
||||||
|
return claim
|
||||||
|
? describeAnchorPathWithinSystem(world, claim.systemId, claim.anchorId) ?? claim.anchorId
|
||||||
|
: "unknown";
|
||||||
}
|
}
|
||||||
if (selection.kind === "construction-site") {
|
if (selection.kind === "construction-site") {
|
||||||
return world.constructionSites.get(selection.id)?.celestialId ?? "unknown";
|
const site = world.constructionSites.get(selection.id);
|
||||||
|
return site
|
||||||
|
? describeAnchorPathWithinSystem(world, site.systemId, site.anchorId) ?? site.anchorId
|
||||||
|
: "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ import {
|
|||||||
syncShips as syncShipScene,
|
syncShips as syncShipScene,
|
||||||
syncStations as syncStationScene,
|
syncStations as syncStationScene,
|
||||||
} from "./viewerSceneSync";
|
} from "./viewerSceneSync";
|
||||||
|
import {
|
||||||
|
createClaimMesh,
|
||||||
|
createConstructionSiteMesh,
|
||||||
|
createNodeMesh,
|
||||||
|
createResourceDepositMesh,
|
||||||
|
createShipMesh,
|
||||||
|
createShipTacticalIcon,
|
||||||
|
createStationMesh,
|
||||||
|
createTacticalIcon,
|
||||||
|
} from "./viewerSceneFactory";
|
||||||
import {
|
import {
|
||||||
deriveNodeOrbital,
|
deriveNodeOrbital,
|
||||||
deriveOrbitalFromLocalPosition,
|
deriveOrbitalFromLocalPosition,
|
||||||
@@ -43,7 +53,7 @@ import type {
|
|||||||
SystemSnapshot,
|
SystemSnapshot,
|
||||||
} from "./contracts";
|
} from "./contracts";
|
||||||
import type { OrbitalAnchor, Selectable } from "./viewerTypes";
|
import type { OrbitalAnchor, Selectable } from "./viewerTypes";
|
||||||
import { rawObject } from "./viewerScenePrimitives";
|
import { rawObject, registerSelectableTarget } from "./viewerScenePrimitives";
|
||||||
|
|
||||||
export interface ViewerSceneDataContext {
|
export interface ViewerSceneDataContext {
|
||||||
documentRef: Document;
|
documentRef: Document;
|
||||||
@@ -61,8 +71,14 @@ export interface ViewerSceneDataContext {
|
|||||||
claimGroup: THREE.Group;
|
claimGroup: THREE.Group;
|
||||||
constructionSiteGroup: THREE.Group;
|
constructionSiteGroup: THREE.Group;
|
||||||
shipGroup: THREE.Group;
|
shipGroup: THREE.Group;
|
||||||
|
localNodeGroup: THREE.Group;
|
||||||
|
localStationGroup: THREE.Group;
|
||||||
|
localClaimGroup: THREE.Group;
|
||||||
|
localConstructionSiteGroup: THREE.Group;
|
||||||
|
localShipGroup: THREE.Group;
|
||||||
galaxySelectableTargets: Map<THREE.Object3D, Selectable>;
|
galaxySelectableTargets: Map<THREE.Object3D, Selectable>;
|
||||||
systemSelectableTargets: Map<THREE.Object3D, Selectable>;
|
systemSelectableTargets: Map<THREE.Object3D, Selectable>;
|
||||||
|
localSelectableTargets: Map<THREE.Object3D, Selectable>;
|
||||||
systemVisuals: Map<any, any>;
|
systemVisuals: Map<any, any>;
|
||||||
planetVisuals: any[];
|
planetVisuals: any[];
|
||||||
celestialVisuals: Map<any, any>;
|
celestialVisuals: Map<any, any>;
|
||||||
@@ -71,6 +87,11 @@ export interface ViewerSceneDataContext {
|
|||||||
claimVisuals: Map<any, any>;
|
claimVisuals: Map<any, any>;
|
||||||
constructionSiteVisuals: Map<any, any>;
|
constructionSiteVisuals: Map<any, any>;
|
||||||
shipVisuals: Map<any, any>;
|
shipVisuals: Map<any, any>;
|
||||||
|
localNodeVisuals: Map<any, any>;
|
||||||
|
localStationVisuals: Map<any, any>;
|
||||||
|
localClaimVisuals: Map<any, any>;
|
||||||
|
localConstructionSiteVisuals: Map<any, any>;
|
||||||
|
localShipVisuals: Map<any, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ViewerSceneDataController {
|
export class ViewerSceneDataController {
|
||||||
@@ -136,6 +157,162 @@ export class ViewerSceneDataController {
|
|||||||
applyShipDeltaUpdates(this.createSceneSyncContext(), ships, tickIntervalMs, this.context.getActiveSystemId());
|
applyShipDeltaUpdates(this.createSceneSyncContext(), ships, tickIntervalMs, this.context.getActiveSystemId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshLocalLayer(world: any, focusedAnchorId?: string) {
|
||||||
|
this.context.localNodeGroup.clear();
|
||||||
|
this.context.localStationGroup.clear();
|
||||||
|
this.context.localClaimGroup.clear();
|
||||||
|
this.context.localConstructionSiteGroup.clear();
|
||||||
|
this.context.localShipGroup.clear();
|
||||||
|
this.context.localSelectableTargets.clear();
|
||||||
|
this.context.localNodeVisuals.clear();
|
||||||
|
this.context.localStationVisuals.clear();
|
||||||
|
this.context.localClaimVisuals.clear();
|
||||||
|
this.context.localConstructionSiteVisuals.clear();
|
||||||
|
this.context.localShipVisuals.clear();
|
||||||
|
|
||||||
|
if (!world || !focusedAnchorId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeSystemId = this.context.getActiveSystemId();
|
||||||
|
const inFocusedSystem = (systemId: string) => !activeSystemId || systemId === activeSystemId;
|
||||||
|
|
||||||
|
for (const node of world.nodes.values()) {
|
||||||
|
if (node.anchorId !== focusedAnchorId || !inFocusedSystem(node.systemId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = createNodeMesh(node);
|
||||||
|
const icon = createTacticalIcon(this.context.documentRef, node.sourceKind === "gas-cloud" ? "#7fd6ff" : "#d2b07a", 100);
|
||||||
|
const localPosition = new THREE.Vector3(0, 0, 0);
|
||||||
|
mesh.setPosition(localPosition);
|
||||||
|
icon.setPosition(localPosition);
|
||||||
|
this.context.localNodeVisuals.set(node.id, {
|
||||||
|
systemId: node.systemId,
|
||||||
|
anchorId: node.anchorId,
|
||||||
|
mesh,
|
||||||
|
icon,
|
||||||
|
sourceKind: node.sourceKind,
|
||||||
|
anchor: { kind: "star" as const },
|
||||||
|
localPosition,
|
||||||
|
orbitRadius: 0,
|
||||||
|
orbitPhase: 0,
|
||||||
|
orbitInclination: 0,
|
||||||
|
});
|
||||||
|
this.context.localNodeGroup.add(rawObject(mesh), rawObject(icon));
|
||||||
|
for (const deposit of node.deposits) {
|
||||||
|
const depositMesh = createResourceDepositMesh(deposit, node);
|
||||||
|
this.context.localNodeGroup.add(rawObject(depositMesh));
|
||||||
|
}
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, mesh, { kind: "node", id: node.id });
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, icon, { kind: "node", id: node.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const station of world.stations.values()) {
|
||||||
|
if (station.anchorId !== focusedAnchorId || !inFocusedSystem(station.systemId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = createStationMesh(station);
|
||||||
|
const icon = createTacticalIcon(this.context.documentRef, station.color, 130);
|
||||||
|
const localPosition = new THREE.Vector3(station.localPosition.x, station.localPosition.y, station.localPosition.z);
|
||||||
|
mesh.setPosition(localPosition);
|
||||||
|
icon.setPosition(localPosition);
|
||||||
|
this.context.localStationVisuals.set(station.id, {
|
||||||
|
id: station.id,
|
||||||
|
systemId: station.systemId,
|
||||||
|
anchorId: station.anchorId,
|
||||||
|
mesh,
|
||||||
|
icon,
|
||||||
|
anchor: { kind: "star" as const },
|
||||||
|
orbitRadius: 0,
|
||||||
|
orbitPhase: 0,
|
||||||
|
orbitInclination: 0,
|
||||||
|
localPosition,
|
||||||
|
});
|
||||||
|
this.context.localStationGroup.add(rawObject(mesh), rawObject(icon));
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, mesh, { kind: "station", id: station.id });
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, icon, { kind: "station", id: station.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const claim of world.claims.values()) {
|
||||||
|
if (claim.anchorId !== focusedAnchorId || !inFocusedSystem(claim.systemId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = createClaimMesh(claim);
|
||||||
|
const icon = createTacticalIcon(this.context.documentRef, "#ff5b5b", 90);
|
||||||
|
const localPosition = new THREE.Vector3(0, 0, 0);
|
||||||
|
mesh.setPosition(localPosition);
|
||||||
|
icon.setPosition(localPosition);
|
||||||
|
this.context.localClaimVisuals.set(claim.id, {
|
||||||
|
id: claim.id,
|
||||||
|
anchorId: claim.anchorId,
|
||||||
|
systemId: claim.systemId,
|
||||||
|
mesh,
|
||||||
|
icon,
|
||||||
|
localPosition,
|
||||||
|
});
|
||||||
|
this.context.localClaimGroup.add(rawObject(mesh), rawObject(icon));
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, mesh, { kind: "claim", id: claim.id });
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, icon, { kind: "claim", id: claim.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const site of world.constructionSites.values()) {
|
||||||
|
if (site.anchorId !== focusedAnchorId || !inFocusedSystem(site.systemId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = createConstructionSiteMesh(site);
|
||||||
|
const icon = createTacticalIcon(this.context.documentRef, "#9df29c", 90);
|
||||||
|
const localPosition = new THREE.Vector3(0, 0, 0);
|
||||||
|
mesh.setPosition(localPosition);
|
||||||
|
icon.setPosition(localPosition);
|
||||||
|
this.context.localConstructionSiteVisuals.set(site.id, {
|
||||||
|
id: site.id,
|
||||||
|
anchorId: site.anchorId,
|
||||||
|
systemId: site.systemId,
|
||||||
|
mesh,
|
||||||
|
icon,
|
||||||
|
localPosition,
|
||||||
|
});
|
||||||
|
this.context.localConstructionSiteGroup.add(rawObject(mesh), rawObject(icon));
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, mesh, { kind: "construction-site", id: site.id });
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, icon, { kind: "construction-site", id: site.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ship of world.ships.values()) {
|
||||||
|
const shipAnchorId = ship.spatialState.currentAnchorId ?? ship.anchorId;
|
||||||
|
if (shipAnchorId !== focusedAnchorId || !inFocusedSystem(ship.systemId) || ship.spatialState.spaceLayer !== "local-space") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shipColor = shipPresentationColor(ship);
|
||||||
|
const mesh = createShipMesh(ship, shipSize(ship), shipLength(ship), shipColor);
|
||||||
|
const icon = createShipTacticalIcon(this.context.documentRef, shipColor, 78);
|
||||||
|
const localPosition = new THREE.Vector3(ship.localPosition.x, ship.localPosition.y, ship.localPosition.z);
|
||||||
|
mesh.setPosition(localPosition);
|
||||||
|
icon.setPosition(localPosition);
|
||||||
|
icon.setColor(shipColor);
|
||||||
|
this.context.localShipVisuals.set(ship.id, {
|
||||||
|
systemId: ship.systemId,
|
||||||
|
anchorId: shipAnchorId,
|
||||||
|
mesh,
|
||||||
|
icon,
|
||||||
|
iconBaseScale: 78,
|
||||||
|
startPosition: localPosition.clone(),
|
||||||
|
authoritativePosition: localPosition.clone(),
|
||||||
|
targetPosition: new THREE.Vector3(ship.targetLocalPosition.x, ship.targetLocalPosition.y, ship.targetLocalPosition.z),
|
||||||
|
velocity: new THREE.Vector3(ship.localVelocity.x, ship.localVelocity.y, ship.localVelocity.z),
|
||||||
|
receivedAtMs: performance.now(),
|
||||||
|
blendDurationMs: Math.max(world.tickIntervalMs ?? 80, 80),
|
||||||
|
});
|
||||||
|
this.context.localShipGroup.add(rawObject(mesh), rawObject(icon));
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, mesh, { kind: "ship", id: ship.id });
|
||||||
|
registerSelectableTarget(this.context.localSelectableTargets, icon, { kind: "ship", id: ship.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the active system changes. Swaps which system's root is in systemScene
|
* Called when the active system changes. Swaps which system's root is in systemScene
|
||||||
* and updates visibility of all system-filtered objects.
|
* and updates visibility of all system-filtered objects.
|
||||||
@@ -202,6 +379,7 @@ export class ViewerSceneDataController {
|
|||||||
createWorldPresentationContext(overrides: {
|
createWorldPresentationContext(overrides: {
|
||||||
world: any;
|
world: any;
|
||||||
activeSystemId?: string;
|
activeSystemId?: string;
|
||||||
|
focusedAnchorId?: string;
|
||||||
cameraMode: any;
|
cameraMode: any;
|
||||||
povLevel: any;
|
povLevel: any;
|
||||||
orbitYaw: number;
|
orbitYaw: number;
|
||||||
@@ -215,17 +393,23 @@ export class ViewerSceneDataController {
|
|||||||
worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
|
worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
|
||||||
worldSeed: this.context.getWorldSeed(),
|
worldSeed: this.context.getWorldSeed(),
|
||||||
activeSystemId: overrides.activeSystemId,
|
activeSystemId: overrides.activeSystemId,
|
||||||
|
focusedAnchorId: overrides.focusedAnchorId,
|
||||||
cameraMode: overrides.cameraMode,
|
cameraMode: overrides.cameraMode,
|
||||||
povLevel: overrides.povLevel,
|
povLevel: overrides.povLevel,
|
||||||
orbitYaw: overrides.orbitYaw,
|
orbitYaw: overrides.orbitYaw,
|
||||||
camera: overrides.systemCamera,
|
camera: overrides.systemCamera,
|
||||||
systemAnchor: overrides.systemAnchor,
|
systemAnchor: overrides.systemAnchor,
|
||||||
shipVisuals: this.context.shipVisuals,
|
shipVisuals: this.context.shipVisuals,
|
||||||
|
localShipVisuals: this.context.localShipVisuals,
|
||||||
nodeVisuals: this.context.nodeVisuals,
|
nodeVisuals: this.context.nodeVisuals,
|
||||||
|
localNodeVisuals: this.context.localNodeVisuals,
|
||||||
celestialVisuals: this.context.celestialVisuals,
|
celestialVisuals: this.context.celestialVisuals,
|
||||||
stationVisuals: this.context.stationVisuals,
|
stationVisuals: this.context.stationVisuals,
|
||||||
|
localStationVisuals: this.context.localStationVisuals,
|
||||||
claimVisuals: this.context.claimVisuals,
|
claimVisuals: this.context.claimVisuals,
|
||||||
|
localClaimVisuals: this.context.localClaimVisuals,
|
||||||
constructionSiteVisuals: this.context.constructionSiteVisuals,
|
constructionSiteVisuals: this.context.constructionSiteVisuals,
|
||||||
|
localConstructionSiteVisuals: this.context.localConstructionSiteVisuals,
|
||||||
systemVisuals: this.context.systemVisuals,
|
systemVisuals: this.context.systemVisuals,
|
||||||
systemSummaryVisuals: new Map(),
|
systemSummaryVisuals: new Map(),
|
||||||
toDisplayLocalPosition: overrides.toDisplayLocalPosition,
|
toDisplayLocalPosition: overrides.toDisplayLocalPosition,
|
||||||
@@ -248,8 +432,14 @@ export class ViewerSceneDataController {
|
|||||||
claimGroup: this.context.claimGroup,
|
claimGroup: this.context.claimGroup,
|
||||||
constructionSiteGroup: this.context.constructionSiteGroup,
|
constructionSiteGroup: this.context.constructionSiteGroup,
|
||||||
shipGroup: this.context.shipGroup,
|
shipGroup: this.context.shipGroup,
|
||||||
|
localNodeGroup: this.context.localNodeGroup,
|
||||||
|
localStationGroup: this.context.localStationGroup,
|
||||||
|
localClaimGroup: this.context.localClaimGroup,
|
||||||
|
localConstructionSiteGroup: this.context.localConstructionSiteGroup,
|
||||||
|
localShipGroup: this.context.localShipGroup,
|
||||||
galaxySelectableTargets: this.context.galaxySelectableTargets,
|
galaxySelectableTargets: this.context.galaxySelectableTargets,
|
||||||
systemSelectableTargets: this.context.systemSelectableTargets,
|
systemSelectableTargets: this.context.systemSelectableTargets,
|
||||||
|
localSelectableTargets: this.context.localSelectableTargets,
|
||||||
systemVisuals: this.context.systemVisuals,
|
systemVisuals: this.context.systemVisuals,
|
||||||
planetVisuals: this.context.planetVisuals,
|
planetVisuals: this.context.planetVisuals,
|
||||||
celestialVisuals: this.context.celestialVisuals,
|
celestialVisuals: this.context.celestialVisuals,
|
||||||
@@ -258,12 +448,17 @@ export class ViewerSceneDataController {
|
|||||||
claimVisuals: this.context.claimVisuals,
|
claimVisuals: this.context.claimVisuals,
|
||||||
constructionSiteVisuals: this.context.constructionSiteVisuals,
|
constructionSiteVisuals: this.context.constructionSiteVisuals,
|
||||||
shipVisuals: this.context.shipVisuals,
|
shipVisuals: this.context.shipVisuals,
|
||||||
|
localNodeVisuals: this.context.localNodeVisuals,
|
||||||
|
localStationVisuals: this.context.localStationVisuals,
|
||||||
|
localClaimVisuals: this.context.localClaimVisuals,
|
||||||
|
localConstructionSiteVisuals: this.context.localConstructionSiteVisuals,
|
||||||
|
localShipVisuals: this.context.localShipVisuals,
|
||||||
shipSize,
|
shipSize,
|
||||||
shipLength,
|
shipLength,
|
||||||
shipPresentationColor,
|
shipPresentationColor,
|
||||||
celestialColor,
|
celestialColor,
|
||||||
createCirclePoints,
|
createCirclePoints,
|
||||||
resolvePointPosition: (systemId: string, celestialId?: string | null) => resolvePointPosition(this.context.getWorldPresentationContext(), systemId, celestialId),
|
resolvePointPosition: (systemId: string, celestialId?: string | null, anchorId?: string | null) => resolvePointPosition(this.context.getWorldPresentationContext(), systemId, celestialId, anchorId),
|
||||||
resolveOrbitalAnchor: (systemId: string, localPosition: THREE.Vector3) => resolveOrbitalAnchor(this.context.getWorldPresentationContext(), systemId, localPosition),
|
resolveOrbitalAnchor: (systemId: string, localPosition: THREE.Vector3) => resolveOrbitalAnchor(this.context.getWorldPresentationContext(), systemId, localPosition),
|
||||||
deriveNodeOrbital: (node: ResourceNodeSnapshot | ResourceNodeDelta, anchor: OrbitalAnchor) => deriveNodeOrbital(this.context.getWorldPresentationContext(), node, anchor),
|
deriveNodeOrbital: (node: ResourceNodeSnapshot | ResourceNodeDelta, anchor: OrbitalAnchor) => deriveNodeOrbital(this.context.getWorldPresentationContext(), node, anchor),
|
||||||
deriveOrbitalFromLocalPosition: (localPosition: THREE.Vector3, systemId: string, anchor: OrbitalAnchor) => deriveOrbitalFromLocalPosition(this.context.getWorldPresentationContext(), localPosition, systemId, anchor),
|
deriveOrbitalFromLocalPosition: (localPosition: THREE.Vector3, systemId: string, anchor: OrbitalAnchor) => deriveOrbitalFromLocalPosition(this.context.getWorldPresentationContext(), localPosition, systemId, anchor),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
ConstructionSiteSnapshot,
|
ConstructionSiteSnapshot,
|
||||||
MoonSnapshot,
|
MoonSnapshot,
|
||||||
PlanetSnapshot,
|
PlanetSnapshot,
|
||||||
|
ResourceDepositSnapshot,
|
||||||
ResourceNodeSnapshot,
|
ResourceNodeSnapshot,
|
||||||
ShipSnapshot,
|
ShipSnapshot,
|
||||||
StationSnapshot,
|
StationSnapshot,
|
||||||
@@ -46,6 +47,23 @@ export function createNodeMesh(node: ResourceNodeSnapshot): SceneNode {
|
|||||||
return createSceneNode(mesh);
|
return createSceneNode(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createResourceDepositMesh(deposit: ResourceDepositSnapshot, node: ResourceNodeSnapshot): SceneNode {
|
||||||
|
const isGas = node.sourceKind === "gas-cloud" || node.itemId === "gas";
|
||||||
|
const oreRatio = deposit.maxOre <= 0.01 ? 0 : deposit.oreRemaining / deposit.maxOre;
|
||||||
|
const mesh = new THREE.Mesh(
|
||||||
|
isGas ? new THREE.SphereGeometry(10, 12, 12) : new THREE.DodecahedronGeometry(8 + (oreRatio * 5), 0),
|
||||||
|
new THREE.MeshStandardMaterial({
|
||||||
|
color: isGas ? 0x7fd6ff : 0xc9a165,
|
||||||
|
flatShading: !isGas,
|
||||||
|
transparent: isGas,
|
||||||
|
opacity: isGas ? 0.55 : 1,
|
||||||
|
emissive: new THREE.Color(isGas ? 0x7fd6ff : 0xc9a165).multiplyScalar(isGas ? 0.18 : 0.05),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mesh.position.copy(toThreeVector(deposit.localPosition));
|
||||||
|
return createSceneNode(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
export function createCelestialMesh(node: CelestialSnapshot, celestialColor: (kind: string) => string): SceneNode {
|
export function createCelestialMesh(node: CelestialSnapshot, celestialColor: (kind: string) => string): SceneNode {
|
||||||
const color = celestialColor(node.kind);
|
const color = celestialColor(node.kind);
|
||||||
return createSceneNode(new THREE.Mesh(
|
return createSceneNode(new THREE.Mesh(
|
||||||
|
|||||||
@@ -70,6 +70,18 @@ function toSystemPos(localPosition: THREE.Vector3): THREE.Vector3 {
|
|||||||
return localPosition.clone().multiplyScalar(DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE);
|
return localPosition.clone().multiplyScalar(DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveShipSystemPosition(ship: ShipSnapshot | ShipDelta, context: SceneSyncContext) {
|
||||||
|
if (ship.spatialState.systemPosition) {
|
||||||
|
return toThreeVector(ship.spatialState.systemPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ship.anchorId) {
|
||||||
|
return context.resolvePointPosition(ship.systemId, null, ship.anchorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toThreeVector(ship.localPosition);
|
||||||
|
}
|
||||||
|
|
||||||
interface SceneSyncContext {
|
interface SceneSyncContext {
|
||||||
documentRef: Document;
|
documentRef: Document;
|
||||||
worldOrbitalTimeSeconds?: number;
|
worldOrbitalTimeSeconds?: number;
|
||||||
@@ -98,7 +110,7 @@ interface SceneSyncContext {
|
|||||||
shipPresentationColor: (ship: ShipSnapshot) => string;
|
shipPresentationColor: (ship: ShipSnapshot) => string;
|
||||||
celestialColor: (kind: string) => string;
|
celestialColor: (kind: string) => string;
|
||||||
createCirclePoints: (radius: number, segments: number) => THREE.Vector3[];
|
createCirclePoints: (radius: number, segments: number) => THREE.Vector3[];
|
||||||
resolvePointPosition: (systemId: string, celestialId?: string | null) => THREE.Vector3;
|
resolvePointPosition: (systemId: string, celestialId?: string | null, anchorId?: string | null) => THREE.Vector3;
|
||||||
resolveOrbitalAnchor: (systemId: string, localPosition: THREE.Vector3) => NodeVisual["anchor"];
|
resolveOrbitalAnchor: (systemId: string, localPosition: THREE.Vector3) => NodeVisual["anchor"];
|
||||||
deriveNodeOrbital: (node: ResourceNodeSnapshot | ResourceNodeDelta, anchor: NodeVisual["anchor"]) => {
|
deriveNodeOrbital: (node: ResourceNodeSnapshot | ResourceNodeDelta, anchor: NodeVisual["anchor"]) => {
|
||||||
radius: number;
|
radius: number;
|
||||||
@@ -250,7 +262,8 @@ export function syncNodes(context: SceneSyncContext, nodes: ResourceNodeSnapshot
|
|||||||
const mesh = createNodeMesh(node);
|
const mesh = createNodeMesh(node);
|
||||||
const icon = createTacticalIcon(context.documentRef, node.sourceKind === "gas-cloud" ? "#7fd6ff" : "#d2b07a", 100);
|
const icon = createTacticalIcon(context.documentRef, node.sourceKind === "gas-cloud" ? "#7fd6ff" : "#d2b07a", 100);
|
||||||
const localPosition = toThreeVector(node.localPosition);
|
const localPosition = toThreeVector(node.localPosition);
|
||||||
const displayPos = toSystemPos(localPosition);
|
const systemPosition = context.resolvePointPosition(node.systemId, null, node.anchorId);
|
||||||
|
const displayPos = toSystemPos(systemPosition);
|
||||||
mesh.setPosition(displayPos);
|
mesh.setPosition(displayPos);
|
||||||
icon.setPosition(displayPos);
|
icon.setPosition(displayPos);
|
||||||
const isActive = node.systemId === activeSystemId;
|
const isActive = node.systemId === activeSystemId;
|
||||||
@@ -260,6 +273,7 @@ export function syncNodes(context: SceneSyncContext, nodes: ResourceNodeSnapshot
|
|||||||
const orbital = context.deriveNodeOrbital(node, anchor);
|
const orbital = context.deriveNodeOrbital(node, anchor);
|
||||||
context.nodeVisuals.set(node.id, {
|
context.nodeVisuals.set(node.id, {
|
||||||
systemId: node.systemId,
|
systemId: node.systemId,
|
||||||
|
anchorId: node.anchorId,
|
||||||
mesh,
|
mesh,
|
||||||
icon,
|
icon,
|
||||||
sourceKind: node.sourceKind,
|
sourceKind: node.sourceKind,
|
||||||
@@ -283,7 +297,8 @@ export function syncStations(context: SceneSyncContext, stations: StationSnapsho
|
|||||||
const mesh = createStationMesh(station);
|
const mesh = createStationMesh(station);
|
||||||
const icon = createTacticalIcon(context.documentRef, station.color, 130);
|
const icon = createTacticalIcon(context.documentRef, station.color, 130);
|
||||||
const localPosition = toThreeVector(station.localPosition);
|
const localPosition = toThreeVector(station.localPosition);
|
||||||
const displayPos = toSystemPos(localPosition);
|
const systemPosition = context.resolvePointPosition(station.systemId, null, station.anchorId);
|
||||||
|
const displayPos = toSystemPos(systemPosition);
|
||||||
mesh.setPosition(displayPos);
|
mesh.setPosition(displayPos);
|
||||||
icon.setPosition(displayPos);
|
icon.setPosition(displayPos);
|
||||||
const isActive = station.systemId === activeSystemId;
|
const isActive = station.systemId === activeSystemId;
|
||||||
@@ -294,6 +309,7 @@ export function syncStations(context: SceneSyncContext, stations: StationSnapsho
|
|||||||
context.stationVisuals.set(station.id, {
|
context.stationVisuals.set(station.id, {
|
||||||
id: station.id,
|
id: station.id,
|
||||||
systemId: station.systemId,
|
systemId: station.systemId,
|
||||||
|
anchorId: station.anchorId,
|
||||||
mesh,
|
mesh,
|
||||||
icon,
|
icon,
|
||||||
anchor,
|
anchor,
|
||||||
@@ -313,7 +329,7 @@ export function syncClaims(context: SceneSyncContext, claims: ClaimSnapshot[], a
|
|||||||
context.claimVisuals.clear();
|
context.claimVisuals.clear();
|
||||||
|
|
||||||
for (const claim of claims) {
|
for (const claim of claims) {
|
||||||
const localPosition = context.resolvePointPosition(claim.systemId, claim.celestialId);
|
const localPosition = context.resolvePointPosition(claim.systemId, null, claim.anchorId);
|
||||||
const displayPos = toSystemPos(localPosition);
|
const displayPos = toSystemPos(localPosition);
|
||||||
const mesh = createClaimMesh(claim);
|
const mesh = createClaimMesh(claim);
|
||||||
const icon = createTacticalIcon(context.documentRef, "#ff5b5b", 90);
|
const icon = createTacticalIcon(context.documentRef, "#ff5b5b", 90);
|
||||||
@@ -324,7 +340,7 @@ export function syncClaims(context: SceneSyncContext, claims: ClaimSnapshot[], a
|
|||||||
icon.setVisible(isActive);
|
icon.setVisible(isActive);
|
||||||
context.claimVisuals.set(claim.id, {
|
context.claimVisuals.set(claim.id, {
|
||||||
id: claim.id,
|
id: claim.id,
|
||||||
celestialId: claim.celestialId,
|
anchorId: claim.anchorId,
|
||||||
systemId: claim.systemId,
|
systemId: claim.systemId,
|
||||||
mesh,
|
mesh,
|
||||||
icon,
|
icon,
|
||||||
@@ -341,7 +357,7 @@ export function syncConstructionSites(context: SceneSyncContext, sites: Construc
|
|||||||
context.constructionSiteVisuals.clear();
|
context.constructionSiteVisuals.clear();
|
||||||
|
|
||||||
for (const site of sites) {
|
for (const site of sites) {
|
||||||
const localPosition = context.resolvePointPosition(site.systemId, site.celestialId);
|
const localPosition = context.resolvePointPosition(site.systemId, null, site.anchorId);
|
||||||
const displayPos = toSystemPos(localPosition);
|
const displayPos = toSystemPos(localPosition);
|
||||||
const mesh = createConstructionSiteMesh(site);
|
const mesh = createConstructionSiteMesh(site);
|
||||||
const icon = createTacticalIcon(context.documentRef, "#9df29c", 90);
|
const icon = createTacticalIcon(context.documentRef, "#9df29c", 90);
|
||||||
@@ -352,7 +368,7 @@ export function syncConstructionSites(context: SceneSyncContext, sites: Construc
|
|||||||
icon.setVisible(isActive);
|
icon.setVisible(isActive);
|
||||||
context.constructionSiteVisuals.set(site.id, {
|
context.constructionSiteVisuals.set(site.id, {
|
||||||
id: site.id,
|
id: site.id,
|
||||||
celestialId: site.celestialId,
|
anchorId: site.anchorId,
|
||||||
systemId: site.systemId,
|
systemId: site.systemId,
|
||||||
mesh,
|
mesh,
|
||||||
icon,
|
icon,
|
||||||
@@ -374,7 +390,7 @@ export function syncShips(context: SceneSyncContext, ships: ShipSnapshot[], tick
|
|||||||
const iconBaseScale = 78;
|
const iconBaseScale = 78;
|
||||||
const icon = createShipTacticalIcon(context.documentRef, shipColor, iconBaseScale);
|
const icon = createShipTacticalIcon(context.documentRef, shipColor, iconBaseScale);
|
||||||
const localPosition = toThreeVector(ship.localPosition);
|
const localPosition = toThreeVector(ship.localPosition);
|
||||||
const displayPos = toSystemPos(localPosition);
|
const displayPos = toSystemPos(resolveShipSystemPosition(ship, context));
|
||||||
mesh.setPosition(displayPos);
|
mesh.setPosition(displayPos);
|
||||||
icon.setPosition(displayPos);
|
icon.setPosition(displayPos);
|
||||||
icon.setColor(shipColor);
|
icon.setColor(shipColor);
|
||||||
@@ -386,6 +402,7 @@ export function syncShips(context: SceneSyncContext, ships: ShipSnapshot[], tick
|
|||||||
registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "ship", id: ship.id });
|
registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "ship", id: ship.id });
|
||||||
context.shipVisuals.set(ship.id, {
|
context.shipVisuals.set(ship.id, {
|
||||||
systemId: ship.systemId,
|
systemId: ship.systemId,
|
||||||
|
anchorId: ship.anchorId ?? ship.spatialState.currentAnchorId ?? undefined,
|
||||||
mesh,
|
mesh,
|
||||||
icon,
|
icon,
|
||||||
iconBaseScale,
|
iconBaseScale,
|
||||||
@@ -430,6 +447,7 @@ export function applyNodeDeltas(context: SceneSyncContext, nodes: ResourceNodeDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
visual.systemId = node.systemId;
|
visual.systemId = node.systemId;
|
||||||
|
visual.anchorId = node.anchorId;
|
||||||
visual.sourceKind = node.sourceKind;
|
visual.sourceKind = node.sourceKind;
|
||||||
visual.localPosition.copy(toThreeVector(node.localPosition));
|
visual.localPosition.copy(toThreeVector(node.localPosition));
|
||||||
visual.anchor = context.resolveOrbitalAnchor(node.systemId, visual.localPosition);
|
visual.anchor = context.resolveOrbitalAnchor(node.systemId, visual.localPosition);
|
||||||
@@ -452,6 +470,7 @@ export function applyStationDeltas(context: SceneSyncContext, stations: StationD
|
|||||||
}
|
}
|
||||||
|
|
||||||
visual.systemId = station.systemId;
|
visual.systemId = station.systemId;
|
||||||
|
visual.anchorId = station.anchorId;
|
||||||
visual.localPosition.copy(toThreeVector(station.localPosition));
|
visual.localPosition.copy(toThreeVector(station.localPosition));
|
||||||
visual.anchor = context.resolveOrbitalAnchor(station.systemId, visual.localPosition);
|
visual.anchor = context.resolveOrbitalAnchor(station.systemId, visual.localPosition);
|
||||||
const orbital = context.deriveOrbitalFromLocalPosition(visual.localPosition, station.systemId, visual.anchor);
|
const orbital = context.deriveOrbitalFromLocalPosition(visual.localPosition, station.systemId, visual.anchor);
|
||||||
@@ -474,7 +493,8 @@ export function applyClaimDeltas(context: SceneSyncContext, claims: ClaimDelta[]
|
|||||||
}
|
}
|
||||||
|
|
||||||
visual.systemId = claim.systemId;
|
visual.systemId = claim.systemId;
|
||||||
visual.localPosition.copy(context.resolvePointPosition(claim.systemId, claim.celestialId));
|
visual.anchorId = claim.anchorId;
|
||||||
|
visual.localPosition.copy(context.resolvePointPosition(claim.systemId, null, claim.anchorId));
|
||||||
const displayPos = toSystemPos(visual.localPosition);
|
const displayPos = toSystemPos(visual.localPosition);
|
||||||
visual.mesh.setPosition(displayPos);
|
visual.mesh.setPosition(displayPos);
|
||||||
visual.icon.setPosition(displayPos);
|
visual.icon.setPosition(displayPos);
|
||||||
@@ -494,7 +514,8 @@ export function applyConstructionSiteDeltas(context: SceneSyncContext, sites: Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
visual.systemId = site.systemId;
|
visual.systemId = site.systemId;
|
||||||
visual.localPosition.copy(context.resolvePointPosition(site.systemId, site.celestialId));
|
visual.anchorId = site.anchorId;
|
||||||
|
visual.localPosition.copy(context.resolvePointPosition(site.systemId, null, site.anchorId));
|
||||||
const displayPos = toSystemPos(visual.localPosition);
|
const displayPos = toSystemPos(visual.localPosition);
|
||||||
visual.mesh.setPosition(displayPos);
|
visual.mesh.setPosition(displayPos);
|
||||||
visual.icon.setPosition(displayPos);
|
visual.icon.setPosition(displayPos);
|
||||||
@@ -514,6 +535,7 @@ export function applyShipDeltas(context: SceneSyncContext, ships: ShipDelta[], t
|
|||||||
}
|
}
|
||||||
|
|
||||||
visual.systemId = ship.systemId;
|
visual.systemId = ship.systemId;
|
||||||
|
visual.anchorId = ship.anchorId ?? ship.spatialState.currentAnchorId ?? undefined;
|
||||||
visual.startPosition.copy(getAnimatedShipLocalPosition(visual));
|
visual.startPosition.copy(getAnimatedShipLocalPosition(visual));
|
||||||
visual.authoritativePosition.copy(toThreeVector(ship.localPosition));
|
visual.authoritativePosition.copy(toThreeVector(ship.localPosition));
|
||||||
visual.targetPosition.copy(toThreeVector(ship.targetLocalPosition));
|
visual.targetPosition.copy(toThreeVector(ship.targetLocalPosition));
|
||||||
|
|||||||
@@ -20,10 +20,17 @@ export function describeSelectable(world: WorldState | undefined, item: Selectab
|
|||||||
return world.stations.get(item.id)?.label ?? item.id;
|
return world.stations.get(item.id)?.label ?? item.id;
|
||||||
}
|
}
|
||||||
if (item.kind === "node") {
|
if (item.kind === "node") {
|
||||||
return item.id;
|
const node = world.nodes.get(item.id);
|
||||||
|
return node ? `${node.itemId} source` : item.id;
|
||||||
}
|
}
|
||||||
if (item.kind === "celestial") {
|
if (item.kind === "celestial") {
|
||||||
return `${world.celestials.get(item.id)?.kind ?? "celestial"} ${item.id}`;
|
const celestial = world.celestials.get(item.id);
|
||||||
|
if (!celestial) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return describeCelestialPathWithinSystem(world, celestial.systemId, celestial.id)
|
||||||
|
?? `${world.systems.get(celestial.systemId)?.label ?? celestial.systemId} / ${celestial.kind}`;
|
||||||
}
|
}
|
||||||
if (item.kind === "claim") {
|
if (item.kind === "claim") {
|
||||||
return `claim ${item.id}`;
|
return `claim ${item.id}`;
|
||||||
@@ -113,9 +120,7 @@ export function describeHoverLabel(world: WorldState | undefined, item: Selectab
|
|||||||
return item.id;
|
return item.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorPath = node.celestialId
|
const anchorPath = describeAnchorPathWithinSystem(world, node.systemId, node.anchorId);
|
||||||
? describeCelestialPathWithinSystem(world, node.systemId, node.celestialId)
|
|
||||||
: undefined;
|
|
||||||
return anchorPath ? `${anchorPath} / ${node.itemId}` : `${node.systemId} / ${node.itemId}`;
|
return anchorPath ? `${anchorPath} / ${node.itemId}` : `${node.systemId} / ${node.itemId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,16 +140,16 @@ export function describeHoverLabel(world: WorldState | undefined, item: Selectab
|
|||||||
|
|
||||||
if (item.kind === "claim") {
|
if (item.kind === "claim") {
|
||||||
const claim = world.claims.get(item.id);
|
const claim = world.claims.get(item.id);
|
||||||
const anchorPath = claim?.celestialId
|
const anchorPath = claim
|
||||||
? describeCelestialPathWithinSystem(world, claim.systemId, claim.celestialId)
|
? describeAnchorPathWithinSystem(world, claim.systemId, claim.anchorId)
|
||||||
: undefined;
|
: undefined;
|
||||||
return anchorPath ? `${anchorPath} claim` : `Claim ${item.id}`;
|
return anchorPath ? `${anchorPath} claim` : `Claim ${item.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.kind === "construction-site") {
|
if (item.kind === "construction-site") {
|
||||||
const site = world.constructionSites.get(item.id);
|
const site = world.constructionSites.get(item.id);
|
||||||
const anchorPath = site?.celestialId
|
const anchorPath = site
|
||||||
? describeCelestialPathWithinSystem(world, site.systemId, site.celestialId)
|
? describeAnchorPathWithinSystem(world, site.systemId, site.anchorId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const siteLabel = site ? (site.blueprintId ?? site.targetDefinitionId) : item.id;
|
const siteLabel = site ? (site.blueprintId ?? site.targetDefinitionId) : item.id;
|
||||||
return anchorPath ? `${anchorPath} / ${siteLabel}` : `Construction ${siteLabel}`;
|
return anchorPath ? `${anchorPath} / ${siteLabel}` : `Construction ${siteLabel}`;
|
||||||
@@ -210,20 +215,74 @@ export function resolveFocusedCelestialId(world: WorldState | undefined, selecte
|
|||||||
return selected.id;
|
return selected.id;
|
||||||
}
|
}
|
||||||
if (selected.kind === "ship") {
|
if (selected.kind === "ship") {
|
||||||
return world.ships.get(selected.id)?.spatialState.currentCelestialId ?? world.ships.get(selected.id)?.celestialId ?? undefined;
|
const ship = world.ships.get(selected.id);
|
||||||
|
return ship?.spatialState.currentAnchorId && world.celestials.has(ship.spatialState.currentAnchorId)
|
||||||
|
? ship.spatialState.currentAnchorId
|
||||||
|
: (ship?.anchorId && world.celestials.has(ship.anchorId) ? ship.anchorId : undefined);
|
||||||
}
|
}
|
||||||
if (selected.kind === "station") {
|
if (selected.kind === "station") {
|
||||||
return world.stations.get(selected.id)?.celestialId ?? undefined;
|
const station = world.stations.get(selected.id);
|
||||||
|
return station?.anchorId && world.celestials.has(station.anchorId) ? station.anchorId : undefined;
|
||||||
}
|
}
|
||||||
if (selected.kind === "claim") {
|
if (selected.kind === "claim") {
|
||||||
return world.claims.get(selected.id)?.celestialId ?? undefined;
|
const claim = world.claims.get(selected.id);
|
||||||
|
return claim && world.celestials.has(claim.anchorId) ? claim.anchorId : undefined;
|
||||||
}
|
}
|
||||||
if (selected.kind === "construction-site") {
|
if (selected.kind === "construction-site") {
|
||||||
return world.constructionSites.get(selected.id)?.celestialId ?? undefined;
|
const site = world.constructionSites.get(selected.id);
|
||||||
|
return site && world.celestials.has(site.anchorId) ? site.anchorId : undefined;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveFocusedAnchorId(world: WorldState | undefined, selectedItems: Selectable[]): string | undefined {
|
||||||
|
if (!world || selectedItems.length !== 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = selectedItems[0];
|
||||||
|
if (selected.kind === "node") {
|
||||||
|
return world.nodes.get(selected.id)?.anchorId;
|
||||||
|
}
|
||||||
|
if (selected.kind === "ship") {
|
||||||
|
const ship = world.ships.get(selected.id);
|
||||||
|
return ship?.spatialState.currentAnchorId
|
||||||
|
?? ship?.anchorId
|
||||||
|
?? resolveFocusedCelestialId(world, selectedItems);
|
||||||
|
}
|
||||||
|
if (selected.kind === "station") {
|
||||||
|
const station = world.stations.get(selected.id);
|
||||||
|
return station?.anchorId
|
||||||
|
?? resolveFocusedCelestialId(world, selectedItems);
|
||||||
|
}
|
||||||
|
if (selected.kind === "claim") {
|
||||||
|
const claim = world.claims.get(selected.id);
|
||||||
|
return claim?.anchorId
|
||||||
|
?? resolveFocusedCelestialId(world, selectedItems);
|
||||||
|
}
|
||||||
|
if (selected.kind === "construction-site") {
|
||||||
|
const site = world.constructionSites.get(selected.id);
|
||||||
|
return site?.anchorId
|
||||||
|
?? resolveFocusedCelestialId(world, selectedItems);
|
||||||
|
}
|
||||||
|
if (selected.kind === "celestial") {
|
||||||
|
if (world.anchors.has(selected.id)) {
|
||||||
|
return selected.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const orbitBackedAnchor = [...world.anchors.values()].find((anchor) => anchor.orbitReferenceId === selected.id);
|
||||||
|
return orbitBackedAnchor?.id;
|
||||||
|
}
|
||||||
|
if (selected.kind === "planet") {
|
||||||
|
return `${selected.systemId}-planet-${selected.planetIndex + 1}`;
|
||||||
|
}
|
||||||
|
if (selected.kind === "moon") {
|
||||||
|
return `${selected.systemId}-planet-${selected.planetIndex + 1}-moon-${selected.moonIndex + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function describeOrbitalParent(world: WorldState | undefined, systemId?: string, anchor?: OrbitalAnchor): string {
|
export function describeOrbitalParent(world: WorldState | undefined, systemId?: string, anchor?: OrbitalAnchor): string {
|
||||||
if (!world || !systemId) {
|
if (!world || !systemId) {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
@@ -330,23 +389,31 @@ export function describeShipState(world: WorldState | undefined, ship: ShipSnaps
|
|||||||
return baseState;
|
return baseState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const destinationNodeId = ship.spatialState.destinationNodeId ?? ship.spatialState.transit?.destinationNodeId;
|
const destinationAnchorId = ship.spatialState.destinationAnchorId ?? ship.spatialState.transit?.destinationAnchorId;
|
||||||
if (!destinationNodeId) {
|
if (!destinationAnchorId) {
|
||||||
return baseState;
|
return baseState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const destinationCelestial = world.celestials.get(destinationNodeId);
|
const destinationAnchor = destinationAnchorId ? world.anchors.get(destinationAnchorId) : undefined;
|
||||||
if (!destinationCelestial) {
|
|
||||||
return `${baseState} -> ${destinationNodeId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseState === "warping" || baseState === "spooling-warp") {
|
if (baseState === "warping" || baseState === "spooling-warp") {
|
||||||
const destinationPath = describeCelestialPathWithinSystem(world, destinationCelestial.systemId, destinationNodeId);
|
const destinationSystemId = destinationAnchor?.systemId ?? ship.spatialState.currentSystemId ?? ship.systemId;
|
||||||
return `${baseState} -> ${destinationPath ?? destinationNodeId}`;
|
const destinationPath = describeAnchorPathWithinSystem(
|
||||||
|
world,
|
||||||
|
destinationSystemId,
|
||||||
|
destinationAnchorId,
|
||||||
|
);
|
||||||
|
return `${baseState} -> ${destinationPath ?? destinationAnchorId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const destinationSystem = world.systems.get(destinationCelestial.systemId);
|
const destinationSystemId = destinationAnchor?.systemId
|
||||||
return `${baseState} -> ${destinationSystem?.label ?? destinationCelestial.systemId}`;
|
?? ship.spatialState.currentSystemId
|
||||||
|
?? ship.systemId;
|
||||||
|
const destinationSystem = world.systems.get(destinationSystemId);
|
||||||
|
if (!destinationSystem) {
|
||||||
|
return `${baseState} -> ${destinationAnchorId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${baseState} -> ${destinationSystem.label}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeShipObjective(objective: string): string {
|
export function describeShipObjective(objective: string): string {
|
||||||
@@ -406,8 +473,8 @@ export function describeShipLocation(world: WorldState | undefined, ship: ShipSn
|
|||||||
if (ship.dockedStationId) {
|
if (ship.dockedStationId) {
|
||||||
const station = world.stations.get(ship.dockedStationId);
|
const station = world.stations.get(ship.dockedStationId);
|
||||||
if (station) {
|
if (station) {
|
||||||
const anchorPath = station.celestialId
|
const anchorPath = station.anchorId
|
||||||
? describeCelestialPathWithinSystem(world, station.systemId, station.celestialId)
|
? describeAnchorPathWithinSystem(world, station.systemId, station.anchorId)
|
||||||
: undefined;
|
: undefined;
|
||||||
return {
|
return {
|
||||||
system: systemLabel,
|
system: systemLabel,
|
||||||
@@ -416,11 +483,11 @@ export function describeShipLocation(world: WorldState | undefined, ship: ShipSn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentCelestialId = ship.spatialState.currentCelestialId ?? ship.celestialId;
|
const currentAnchorId = ship.spatialState.currentAnchorId ?? ship.anchorId;
|
||||||
if (currentCelestialId) {
|
if (currentAnchorId) {
|
||||||
const celestialPath = describeCelestialPathWithinSystem(world, systemId, currentCelestialId);
|
const anchorPath = describeAnchorPathWithinSystem(world, systemId, currentAnchorId);
|
||||||
if (celestialPath) {
|
if (anchorPath) {
|
||||||
return { system: systemLabel, local: celestialPath };
|
return { system: systemLabel, local: anchorPath };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,9 +513,9 @@ export function describeActiveSpace(
|
|||||||
return activeSystem.label;
|
return activeSystem.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
const celestialId = resolveFocusedCelestialId(world, selectedItems);
|
const anchorId = resolveFocusedAnchorId(world, selectedItems);
|
||||||
if (celestialId) {
|
if (anchorId) {
|
||||||
const localPath = describeCelestialPathWithinSystem(world, activeSystem.id, celestialId);
|
const localPath = describeAnchorPathWithinSystem(world, activeSystem.id, anchorId);
|
||||||
return localPath
|
return localPath
|
||||||
? `${activeSystem.label} / ${localPath}`
|
? `${activeSystem.label} / ${localPath}`
|
||||||
: activeSystem.label;
|
: activeSystem.label;
|
||||||
@@ -472,10 +539,9 @@ export function describeCelestialPathWithinSystem(world: WorldState, systemId: s
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (celestial.parentNodeId) {
|
const anchorId = resolveAnchorIdForCelestial(world, celestialId);
|
||||||
const parentPath = describeCelestialPathWithinSystem(world, systemId, celestial.parentNodeId);
|
if (anchorId) {
|
||||||
const segment = describeCelestialSegment(system, celestial);
|
return describeAnchorPathWithinSystem(world, systemId, anchorId);
|
||||||
return parentPath ? `${parentPath}/${segment}` : segment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (celestial.kind === "star") {
|
if (celestial.kind === "star") {
|
||||||
@@ -485,6 +551,60 @@ export function describeCelestialPathWithinSystem(world: WorldState, systemId: s
|
|||||||
return describeCelestialSegment(system, celestial);
|
return describeCelestialSegment(system, celestial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function describeAnchorPathWithinSystem(world: WorldState, systemId: string, anchorId: string, celestialId?: string | null): string | undefined {
|
||||||
|
const anchor = world.anchors.get(anchorId);
|
||||||
|
if (anchor?.parentAnchorId) {
|
||||||
|
const parentPath = describeAnchorPathWithinSystem(world, systemId, anchor.parentAnchorId);
|
||||||
|
const segment = describeAnchorSegment(anchor);
|
||||||
|
return parentPath ? `${parentPath}/${segment}` : segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (celestialId) {
|
||||||
|
return describeCelestialPathWithinSystem(world, systemId, celestialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anchor) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return describeAnchorSegment(anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeAnchorSegment(anchor: { kind: string; id: string; orbitReferenceId?: string | null }): string {
|
||||||
|
if (anchor.orbitReferenceId) {
|
||||||
|
return describeAnchorOrbitReference(anchor.orbitReferenceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor.kind === "resource-node") {
|
||||||
|
return anchor.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchor.kind.replace(/-/g, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAnchorIdForCelestial(world: WorldState, celestialId: string): string | undefined {
|
||||||
|
return world.anchors.has(celestialId) ? celestialId : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeAnchorOrbitReference(referenceId: string): string {
|
||||||
|
const lagrangeMatch = referenceId.match(/(l[1-5])$/i);
|
||||||
|
if (lagrangeMatch) {
|
||||||
|
return lagrangeMatch[1].toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const moonMatch = referenceId.match(/moon-(\d+)$/i);
|
||||||
|
if (moonMatch) {
|
||||||
|
return `Moon ${moonMatch[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const planetMatch = referenceId.match(/planet-(\d+)$/i);
|
||||||
|
if (planetMatch) {
|
||||||
|
return `Planet ${planetMatch[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return referenceId;
|
||||||
|
}
|
||||||
|
|
||||||
function describeCelestialSegment(system: SystemSnapshot, celestial: CelestialSnapshot): string {
|
function describeCelestialSegment(system: SystemSnapshot, celestial: CelestialSnapshot): string {
|
||||||
const moonMatch = celestial.id.match(/-planet-(\d+)-moon-(\d+)$/);
|
const moonMatch = celestial.id.match(/-planet-(\d+)-moon-(\d+)$/);
|
||||||
if (moonMatch) {
|
if (moonMatch) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export function createWorldState(snapshot: WorldSnapshot): WorldState {
|
|||||||
generatedAtUtc: snapshot.generatedAtUtc,
|
generatedAtUtc: snapshot.generatedAtUtc,
|
||||||
systems: new Map(snapshot.systems.map((system) => [system.id, system])),
|
systems: new Map(snapshot.systems.map((system) => [system.id, system])),
|
||||||
celestials: new Map(snapshot.celestials.map((celestial) => [celestial.id, celestial])),
|
celestials: new Map(snapshot.celestials.map((celestial) => [celestial.id, celestial])),
|
||||||
|
anchors: new Map(snapshot.anchors.map((anchor) => [anchor.id, anchor])),
|
||||||
nodes: new Map(snapshot.nodes.map((node) => [node.id, node])),
|
nodes: new Map(snapshot.nodes.map((node) => [node.id, node])),
|
||||||
stations: new Map(snapshot.stations.map((station) => [station.id, station])),
|
stations: new Map(snapshot.stations.map((station) => [station.id, station])),
|
||||||
claims: new Map(snapshot.claims.map((claim) => [claim.id, claim])),
|
claims: new Map(snapshot.claims.map((claim) => [claim.id, claim])),
|
||||||
@@ -65,6 +66,9 @@ export function applyDeltaToWorld(world: WorldState, delta: WorldDelta): boolean
|
|||||||
for (const celestial of delta.celestials) {
|
for (const celestial of delta.celestials) {
|
||||||
world.celestials.set(celestial.id, celestial);
|
world.celestials.set(celestial.id, celestial);
|
||||||
}
|
}
|
||||||
|
for (const anchor of delta.anchors) {
|
||||||
|
world.anchors.set(anchor.id, anchor);
|
||||||
|
}
|
||||||
for (const node of delta.nodes) {
|
for (const node of delta.nodes) {
|
||||||
world.nodes.set(node.id, node);
|
world.nodes.set(node.id, node);
|
||||||
}
|
}
|
||||||
@@ -101,6 +105,7 @@ export function recordDeltaStats(networkStats: NetworkStats, delta: WorldDelta,
|
|||||||
+ delta.stations.length
|
+ delta.stations.length
|
||||||
+ delta.nodes.length
|
+ delta.nodes.length
|
||||||
+ delta.celestials.length
|
+ delta.celestials.length
|
||||||
|
+ delta.anchors.length
|
||||||
+ delta.claims.length
|
+ delta.claims.length
|
||||||
+ delta.constructionSites.length
|
+ delta.constructionSites.length
|
||||||
+ delta.marketOrders.length
|
+ delta.marketOrders.length
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import type { SceneNode } from "./viewerScenePrimitives";
|
import type { SceneNode } from "./viewerScenePrimitives";
|
||||||
import type {
|
import type {
|
||||||
|
AnchorSnapshot,
|
||||||
CelestialSnapshot,
|
CelestialSnapshot,
|
||||||
ClaimSnapshot,
|
ClaimSnapshot,
|
||||||
ConstructionSiteSnapshot,
|
ConstructionSiteSnapshot,
|
||||||
@@ -35,6 +36,7 @@ export type Selectable =
|
|||||||
|
|
||||||
export interface ShipVisual {
|
export interface ShipVisual {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
|
anchorId?: string;
|
||||||
mesh: SceneNode;
|
mesh: SceneNode;
|
||||||
icon: SceneNode;
|
icon: SceneNode;
|
||||||
iconBaseScale: number;
|
iconBaseScale: number;
|
||||||
@@ -74,6 +76,7 @@ export type OrbitalAnchor =
|
|||||||
|
|
||||||
export interface NodeVisual {
|
export interface NodeVisual {
|
||||||
systemId: string;
|
systemId: string;
|
||||||
|
anchorId: string;
|
||||||
mesh: SceneNode;
|
mesh: SceneNode;
|
||||||
icon: SceneNode;
|
icon: SceneNode;
|
||||||
sourceKind: string;
|
sourceKind: string;
|
||||||
@@ -96,7 +99,8 @@ export interface CelestialVisual {
|
|||||||
|
|
||||||
export interface ClaimVisual {
|
export interface ClaimVisual {
|
||||||
id: string;
|
id: string;
|
||||||
celestialId: string;
|
anchorId: string;
|
||||||
|
celestialId?: string | null;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
mesh: SceneNode;
|
mesh: SceneNode;
|
||||||
icon: SceneNode;
|
icon: SceneNode;
|
||||||
@@ -105,7 +109,8 @@ export interface ClaimVisual {
|
|||||||
|
|
||||||
export interface ConstructionSiteVisual {
|
export interface ConstructionSiteVisual {
|
||||||
id: string;
|
id: string;
|
||||||
celestialId: string;
|
anchorId: string;
|
||||||
|
celestialId?: string | null;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
mesh: SceneNode;
|
mesh: SceneNode;
|
||||||
icon: SceneNode;
|
icon: SceneNode;
|
||||||
@@ -115,6 +120,7 @@ export interface ConstructionSiteVisual {
|
|||||||
export interface StructureVisual {
|
export interface StructureVisual {
|
||||||
id: string;
|
id: string;
|
||||||
systemId: string;
|
systemId: string;
|
||||||
|
anchorId?: string | null;
|
||||||
mesh: SceneNode;
|
mesh: SceneNode;
|
||||||
icon: SceneNode;
|
icon: SceneNode;
|
||||||
anchor: OrbitalAnchor;
|
anchor: OrbitalAnchor;
|
||||||
@@ -145,6 +151,7 @@ export interface WorldState {
|
|||||||
generatedAtUtc: string;
|
generatedAtUtc: string;
|
||||||
systems: Map<string, SystemSnapshot>;
|
systems: Map<string, SystemSnapshot>;
|
||||||
celestials: Map<string, CelestialSnapshot>;
|
celestials: Map<string, CelestialSnapshot>;
|
||||||
|
anchors: Map<string, AnchorSnapshot>;
|
||||||
nodes: Map<string, ResourceNodeSnapshot>;
|
nodes: Map<string, ResourceNodeSnapshot>;
|
||||||
stations: Map<string, StationSnapshot>;
|
stations: Map<string, StationSnapshot>;
|
||||||
claims: Map<string, ClaimSnapshot>;
|
claims: Map<string, ClaimSnapshot>;
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ export interface ViewerWorldLifecycleContext {
|
|||||||
applyClaimDeltas: (claims: ClaimDelta[]) => void;
|
applyClaimDeltas: (claims: ClaimDelta[]) => void;
|
||||||
applyConstructionSiteDeltas: (sites: ConstructionSiteDelta[]) => void;
|
applyConstructionSiteDeltas: (sites: ConstructionSiteDelta[]) => void;
|
||||||
applyShipDeltas: (ships: ShipDelta[], tickIntervalMs: number) => void;
|
applyShipDeltas: (ships: ShipDelta[], tickIntervalMs: number) => void;
|
||||||
|
refreshLocalLayer: () => void;
|
||||||
refreshHistoryWindows: () => void;
|
refreshHistoryWindows: () => void;
|
||||||
resolveFocusedCelestialId: () => string | undefined;
|
resolveFocusedAnchorId: () => string | undefined;
|
||||||
updateSystemSummaries: () => void;
|
updateSystemSummaries: () => void;
|
||||||
applyZoomPresentation: () => void;
|
applyZoomPresentation: () => void;
|
||||||
updateNetworkPanel: () => void;
|
updateNetworkPanel: () => void;
|
||||||
@@ -165,6 +166,7 @@ export class ViewerWorldLifecycle {
|
|||||||
this.context.syncClaims(snapshot.claims);
|
this.context.syncClaims(snapshot.claims);
|
||||||
this.context.syncConstructionSites(snapshot.constructionSites);
|
this.context.syncConstructionSites(snapshot.constructionSites);
|
||||||
this.context.syncShips(snapshot.ships, snapshot.tickIntervalMs);
|
this.context.syncShips(snapshot.ships, snapshot.tickIntervalMs);
|
||||||
|
this.context.refreshLocalLayer();
|
||||||
this.rebuildFactions(snapshot.factions);
|
this.rebuildFactions(snapshot.factions);
|
||||||
this.context.updateSystemSummaries();
|
this.context.updateSystemSummaries();
|
||||||
this.context.applyZoomPresentation();
|
this.context.applyZoomPresentation();
|
||||||
@@ -185,6 +187,7 @@ export class ViewerWorldLifecycle {
|
|||||||
this.context.applyClaimDeltas(delta.claims);
|
this.context.applyClaimDeltas(delta.claims);
|
||||||
this.context.applyConstructionSiteDeltas(delta.constructionSites);
|
this.context.applyConstructionSiteDeltas(delta.constructionSites);
|
||||||
this.context.applyShipDeltas(delta.ships, delta.tickIntervalMs);
|
this.context.applyShipDeltas(delta.ships, delta.tickIntervalMs);
|
||||||
|
this.context.refreshLocalLayer();
|
||||||
this.rebuildFactions(cloneFactions(world));
|
this.rebuildFactions(cloneFactions(world));
|
||||||
this.context.updateSystemSummaries();
|
this.context.updateSystemSummaries();
|
||||||
}
|
}
|
||||||
@@ -219,6 +222,7 @@ export class ViewerWorldLifecycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.context.refreshHistoryWindows();
|
this.context.refreshHistoryWindows();
|
||||||
|
this.context.refreshLocalLayer();
|
||||||
this.context.updateSystemPanel();
|
this.context.updateSystemPanel();
|
||||||
this.refreshStreamScopeIfNeeded();
|
this.refreshStreamScopeIfNeeded();
|
||||||
const detailState = buildDetailPanelState({
|
const detailState = buildDetailPanelState({
|
||||||
@@ -241,12 +245,12 @@ export class ViewerWorldLifecycle {
|
|||||||
return { scopeKind: "universe" as const };
|
return { scopeKind: "universe" as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
const celestialId = this.context.resolveFocusedCelestialId();
|
const anchorId = this.context.resolveFocusedAnchorId();
|
||||||
if (this.context.getPovLevel() === "local" && celestialId) {
|
if (this.context.getPovLevel() === "local" && anchorId) {
|
||||||
return {
|
return {
|
||||||
scopeKind: "local-celestial" as const,
|
scopeKind: "local-anchor" as const,
|
||||||
systemId: activeSystemId,
|
systemId: activeSystemId,
|
||||||
celestialId,
|
anchorId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
toThreeVector,
|
toThreeVector,
|
||||||
} from "./viewerMath";
|
} from "./viewerMath";
|
||||||
import { ACTIVE_SYSTEM_DETAIL_SCALE } from "./viewerConstants";
|
import { ACTIVE_SYSTEM_DETAIL_SCALE } from "./viewerConstants";
|
||||||
import { describeActiveSpace, resolveFocusedCelestialId } from "./viewerSelection";
|
import { describeActiveSpace, resolveFocusedAnchorId } from "./viewerSelection";
|
||||||
import {
|
import {
|
||||||
resolveShipHeading,
|
resolveShipHeading,
|
||||||
updateSystemStarPresentation,
|
updateSystemStarPresentation,
|
||||||
@@ -58,15 +58,21 @@ export interface WorldOrbitalContext {
|
|||||||
|
|
||||||
export interface WorldPresentationContext extends WorldOrbitalContext {
|
export interface WorldPresentationContext extends WorldOrbitalContext {
|
||||||
activeSystemId?: string;
|
activeSystemId?: string;
|
||||||
|
focusedAnchorId?: string;
|
||||||
cameraMode: CameraMode;
|
cameraMode: CameraMode;
|
||||||
povLevel: PovLevel;
|
povLevel: PovLevel;
|
||||||
orbitYaw: number;
|
orbitYaw: number;
|
||||||
camera: THREE.PerspectiveCamera;
|
camera: THREE.PerspectiveCamera;
|
||||||
systemAnchor: THREE.Vector3;
|
systemAnchor: THREE.Vector3;
|
||||||
shipVisuals: Map<string, ShipVisual>;
|
shipVisuals: Map<string, ShipVisual>;
|
||||||
|
localShipVisuals: Map<string, ShipVisual>;
|
||||||
claimVisuals: Map<string, ClaimVisual>;
|
claimVisuals: Map<string, ClaimVisual>;
|
||||||
|
localClaimVisuals: Map<string, ClaimVisual>;
|
||||||
constructionSiteVisuals: Map<string, ConstructionSiteVisual>;
|
constructionSiteVisuals: Map<string, ConstructionSiteVisual>;
|
||||||
|
localConstructionSiteVisuals: Map<string, ConstructionSiteVisual>;
|
||||||
systemVisuals: Map<string, SystemVisual>;
|
systemVisuals: Map<string, SystemVisual>;
|
||||||
|
localNodeVisuals: Map<string, NodeVisual>;
|
||||||
|
localStationVisuals: Map<string, StructureVisual>;
|
||||||
systemSummaryVisuals: Map<string, any>;
|
systemSummaryVisuals: Map<string, any>;
|
||||||
toDisplayLocalPosition: (localPosition: THREE.Vector3) => THREE.Vector3;
|
toDisplayLocalPosition: (localPosition: THREE.Vector3) => THREE.Vector3;
|
||||||
updateSystemDetailVisibility: () => void;
|
updateSystemDetailVisibility: () => void;
|
||||||
@@ -95,7 +101,7 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const worldPosition = getAnimatedShipLocalPosition(visual, now);
|
const worldPosition = resolveShipRenderPosition(context, ship, visual, now, renderMode);
|
||||||
const displayPosition = context.toDisplayLocalPosition(worldPosition);
|
const displayPosition = context.toDisplayLocalPosition(worldPosition);
|
||||||
visual.mesh.setPosition(displayPosition);
|
visual.mesh.setPosition(displayPosition);
|
||||||
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
@@ -124,8 +130,22 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [shipId, visual] of context.localShipVisuals.entries()) {
|
||||||
|
const ship = context.world?.ships.get(shipId);
|
||||||
|
if (!ship) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPosition = getAnimatedShipLocalPosition(visual, now);
|
||||||
|
const displayPosition = context.toDisplayLocalPosition(localPosition);
|
||||||
|
visual.mesh.setPosition(displayPosition);
|
||||||
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
|
visual.mesh.setVisible(renderMode === "local");
|
||||||
|
visual.icon.setVisible(renderMode === "local");
|
||||||
|
}
|
||||||
|
|
||||||
for (const visual of context.nodeVisuals.values()) {
|
for (const visual of context.nodeVisuals.values()) {
|
||||||
const animatedLocalPosition = computeNodeLocalPosition(context, visual, worldTimeSeconds);
|
const animatedLocalPosition = resolveNodeRenderPosition(context, visual, worldTimeSeconds, renderMode);
|
||||||
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
|
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
|
||||||
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
|
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
|
||||||
@@ -148,7 +168,7 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const visual of context.stationVisuals.values()) {
|
for (const visual of context.stationVisuals.values()) {
|
||||||
const animatedLocalPosition = resolveStructureAnimatedLocalPosition(context, visual, worldTimeSeconds);
|
const animatedLocalPosition = resolveStructureRenderPosition(context, visual, worldTimeSeconds, renderMode);
|
||||||
const displayPosition = context.toDisplayLocalPosition(animatedLocalPosition);
|
const displayPosition = context.toDisplayLocalPosition(animatedLocalPosition);
|
||||||
visual.mesh.setPosition(displayPosition);
|
visual.mesh.setPosition(displayPosition);
|
||||||
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
@@ -165,7 +185,7 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const visual of context.claimVisuals.values()) {
|
for (const visual of context.claimVisuals.values()) {
|
||||||
const animatedLocalPosition = computeCelestialLocalPositionById(context, visual.celestialId, worldTimeSeconds) ?? visual.localPosition.clone();
|
const animatedLocalPosition = visual.localPosition.clone();
|
||||||
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
|
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
|
||||||
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
|
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
|
||||||
@@ -173,12 +193,41 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const visual of context.constructionSiteVisuals.values()) {
|
for (const visual of context.constructionSiteVisuals.values()) {
|
||||||
const animatedLocalPosition = computeCelestialLocalPositionById(context, visual.celestialId, worldTimeSeconds) ?? visual.localPosition.clone();
|
const animatedLocalPosition = visual.localPosition.clone();
|
||||||
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
|
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
|
||||||
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
|
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
|
||||||
visual.icon.setVisible(visual.systemId === context.activeSystemId);
|
visual.icon.setVisible(visual.systemId === context.activeSystemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const visual of context.localNodeVisuals.values()) {
|
||||||
|
visual.mesh.setPosition(context.toDisplayLocalPosition(visual.localPosition.clone()));
|
||||||
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
|
visual.mesh.setVisible(renderMode === "local");
|
||||||
|
visual.icon.setVisible(renderMode === "local");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const visual of context.localStationVisuals.values()) {
|
||||||
|
const displayPosition = context.toDisplayLocalPosition(visual.localPosition.clone());
|
||||||
|
visual.mesh.setPosition(displayPosition);
|
||||||
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
|
visual.mesh.setVisible(renderMode === "local");
|
||||||
|
visual.icon.setVisible(renderMode === "local");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const visual of context.localClaimVisuals.values()) {
|
||||||
|
visual.mesh.setPosition(context.toDisplayLocalPosition(visual.localPosition.clone()));
|
||||||
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
|
visual.mesh.setVisible(renderMode === "local");
|
||||||
|
visual.icon.setVisible(renderMode === "local");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const visual of context.localConstructionSiteVisuals.values()) {
|
||||||
|
visual.mesh.setPosition(context.toDisplayLocalPosition(visual.localPosition.clone()));
|
||||||
|
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||||
|
visual.mesh.setVisible(renderMode === "local");
|
||||||
|
visual.icon.setVisible(renderMode === "local");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RenderSpaceMode = "galaxy" | "system" | "local";
|
type RenderSpaceMode = "galaxy" | "system" | "local";
|
||||||
@@ -213,6 +262,60 @@ export function resolveShipWorldPosition(
|
|||||||
return context.toDisplayLocalPosition(animatedLocalPosition);
|
return context.toDisplayLocalPosition(animatedLocalPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveAnchorSystemPosition(context: WorldOrbitalContext, anchorId?: string | null) {
|
||||||
|
if (!anchorId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchor = context.world?.anchors.get(anchorId);
|
||||||
|
return anchor ? toThreeVector(anchor.systemPosition) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveShipRenderPosition(
|
||||||
|
context: WorldPresentationContext,
|
||||||
|
ship: ShipSnapshot,
|
||||||
|
visual: ShipVisual,
|
||||||
|
now: number,
|
||||||
|
renderMode: RenderSpaceMode,
|
||||||
|
) {
|
||||||
|
const animatedLocalPosition = getAnimatedShipLocalPosition(visual, now);
|
||||||
|
const currentAnchorId = ship.spatialState.currentAnchorId ?? ship.anchorId ?? visual.anchorId;
|
||||||
|
const anchoredSystemPosition = resolveAnchorSystemPosition(context, currentAnchorId)
|
||||||
|
?? (ship.spatialState.systemPosition ? toThreeVector(ship.spatialState.systemPosition) : undefined);
|
||||||
|
|
||||||
|
if (renderMode === "local" && currentAnchorId && currentAnchorId === context.focusedAnchorId) {
|
||||||
|
return animatedLocalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchoredSystemPosition ?? animatedLocalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNodeRenderPosition(
|
||||||
|
context: WorldPresentationContext,
|
||||||
|
visual: NodeVisual,
|
||||||
|
timeSeconds: number,
|
||||||
|
renderMode: RenderSpaceMode,
|
||||||
|
) {
|
||||||
|
if (renderMode === "local" && visual.anchorId === context.focusedAnchorId) {
|
||||||
|
return computeNodeLocalPosition(context, visual, timeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveAnchorSystemPosition(context, visual.anchorId) ?? visual.localPosition.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveStructureRenderPosition(
|
||||||
|
context: WorldPresentationContext,
|
||||||
|
visual: StructureVisual,
|
||||||
|
timeSeconds: number,
|
||||||
|
renderMode: RenderSpaceMode,
|
||||||
|
) {
|
||||||
|
if (renderMode === "local" && visual.anchorId && visual.anchorId === context.focusedAnchorId) {
|
||||||
|
return resolveStructureAnimatedLocalPosition(context, visual, timeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveAnchorSystemPosition(context, visual.anchorId) ?? visual.localPosition.clone();
|
||||||
|
}
|
||||||
|
|
||||||
export function updateSystemSummaries(world: WorldState | undefined, systemSummaryVisuals: Map<string, any>) {
|
export function updateSystemSummaries(world: WorldState | undefined, systemSummaryVisuals: Map<string, any>) {
|
||||||
if (!world) {
|
if (!world) {
|
||||||
return;
|
return;
|
||||||
@@ -310,9 +413,9 @@ export function describeGameStatus(params: GameStatusParams) {
|
|||||||
? `sys pos: ${fmtVec(systemAnchor.clone().divideScalar(KILOMETERS_PER_AU), 3)} AU`
|
? `sys pos: ${fmtVec(systemAnchor.clone().divideScalar(KILOMETERS_PER_AU), 3)} AU`
|
||||||
: "";
|
: "";
|
||||||
// Local space: position relative to the focused celestial's orbital anchor in km
|
// Local space: position relative to the focused celestial's orbital anchor in km
|
||||||
const focusedCelestialId = resolveFocusedCelestialId(world, selectedItems);
|
const focusedAnchorId = resolveFocusedAnchorId(world, selectedItems);
|
||||||
const celestialAnchor = focusedCelestialId
|
const celestialAnchor = focusedAnchorId
|
||||||
? world?.celestials.get(focusedCelestialId)?.orbitalAnchor
|
? (world?.anchors.get(focusedAnchorId)?.systemPosition ?? world?.celestials.get(focusedAnchorId)?.orbitalAnchor)
|
||||||
: undefined;
|
: undefined;
|
||||||
const locPos = systemAnchor && celestialAnchor
|
const locPos = systemAnchor && celestialAnchor
|
||||||
? `loc pos: ${fmtVec(systemAnchor.clone().sub(toThreeVector(celestialAnchor)), 0)} km`
|
? `loc pos: ${fmtVec(systemAnchor.clone().sub(toThreeVector(celestialAnchor)), 0)} km`
|
||||||
@@ -415,7 +518,14 @@ export function resolveOrbitalAnchor(context: WorldOrbitalContext, systemId: str
|
|||||||
return bestAnchor;
|
return bestAnchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolvePointPosition(context: WorldOrbitalContext, _systemId: string, celestialId?: string | null) {
|
export function resolvePointPosition(context: WorldOrbitalContext, _systemId: string, celestialId?: string | null, anchorId?: string | null) {
|
||||||
|
if (anchorId) {
|
||||||
|
const anchor = context.world?.anchors.get(anchorId);
|
||||||
|
if (anchor) {
|
||||||
|
return toThreeVector(anchor.systemPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (celestialId) {
|
if (celestialId) {
|
||||||
const celestial = context.world?.celestials.get(celestialId);
|
const celestial = context.world?.celestials.get(celestialId);
|
||||||
if (celestial) {
|
if (celestial) {
|
||||||
@@ -446,17 +556,17 @@ export function computeCelestialLocalPositionById(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const basePosition = toThreeVector(celestial.orbitalAnchor);
|
const basePosition = toThreeVector(celestial.orbitalAnchor);
|
||||||
if (!celestial.parentNodeId) {
|
if (!celestial.parentAnchorId) {
|
||||||
return basePosition;
|
return basePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentCelestial = context.world.celestials.get(celestial.parentNodeId);
|
const parentCelestial = context.world.celestials.get(celestial.parentAnchorId);
|
||||||
if (!parentCelestial) {
|
if (!parentCelestial) {
|
||||||
return basePosition;
|
return basePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
visiting.add(celestialId);
|
visiting.add(celestialId);
|
||||||
const parentCurrentPosition = computeCelestialLocalPositionById(context, celestial.parentNodeId, timeSeconds, visiting);
|
const parentCurrentPosition = computeCelestialLocalPositionById(context, celestial.parentAnchorId, timeSeconds, visiting);
|
||||||
visiting.delete(celestialId);
|
visiting.delete(celestialId);
|
||||||
if (!parentCurrentPosition) {
|
if (!parentCurrentPosition) {
|
||||||
return basePosition;
|
return basePosition;
|
||||||
@@ -548,9 +658,16 @@ function resolveStructureAnimatedLocalPosition(context: WorldOrbitalContext, vis
|
|||||||
}
|
}
|
||||||
|
|
||||||
const station = context.world.stations.get(visual.id);
|
const station = context.world.stations.get(visual.id);
|
||||||
if (!station?.celestialId) {
|
if (!station) {
|
||||||
return computeStructureLocalPosition(context, visual, timeSeconds, 0.14);
|
return computeStructureLocalPosition(context, visual, timeSeconds, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
return computeCelestialLocalPositionById(context, station.celestialId, timeSeconds) ?? visual.localPosition.clone();
|
if (station.anchorId) {
|
||||||
|
const anchor = context.world.anchors.get(station.anchorId);
|
||||||
|
if (anchor) {
|
||||||
|
return toThreeVector(anchor.systemPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computeStructureLocalPosition(context, visual, timeSeconds, 0.14);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default defineConfig({
|
|||||||
port: 5174,
|
port: 5174,
|
||||||
allowedHosts: ["sobina.local"],
|
allowedHosts: ["sobina.local"],
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": "http://127.0.0.1:5080",
|
"/api": "http://127.0.0.1:5079",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ Recommended global ID families:
|
|||||||
- `commanderId`
|
- `commanderId`
|
||||||
- `systemId`
|
- `systemId`
|
||||||
- `anchorId`
|
- `anchorId`
|
||||||
- `localspaceId`
|
|
||||||
- `stationId`
|
- `stationId`
|
||||||
- `shipId`
|
- `shipId`
|
||||||
- `moduleId`
|
- `moduleId`
|
||||||
@@ -59,15 +58,14 @@ The intended core entities are:
|
|||||||
2. `Commander`
|
2. `Commander`
|
||||||
3. `System`
|
3. `System`
|
||||||
4. `Anchor`
|
4. `Anchor`
|
||||||
5. `Localspace`
|
5. `Station`
|
||||||
6. `Station`
|
6. `Ship`
|
||||||
7. `Ship`
|
7. `ModuleInstance`
|
||||||
8. `ModuleInstance`
|
8. `Claim`
|
||||||
9. `Claim`
|
9. `ConstructionSite`
|
||||||
10. `ConstructionSite`
|
10. `MarketOrder`
|
||||||
11. `MarketOrder`
|
11. `Recipe`
|
||||||
12. `Recipe`
|
12. `PolicySet`
|
||||||
13. `PolicySet`
|
|
||||||
|
|
||||||
## Faction
|
## Faction
|
||||||
|
|
||||||
@@ -120,12 +118,12 @@ Suggested fields:
|
|||||||
- `label`
|
- `label`
|
||||||
- `galaxyPosition`
|
- `galaxyPosition`
|
||||||
- `star definition`
|
- `star definition`
|
||||||
- `nodeIds`
|
- `anchorIds`
|
||||||
- `faction influence later`
|
- `faction influence later`
|
||||||
|
|
||||||
## Anchor
|
## Anchor
|
||||||
|
|
||||||
An anchor is a meaningful location in a system that owns a localspace.
|
An anchor is a meaningful location in a system.
|
||||||
|
|
||||||
Suggested fields:
|
Suggested fields:
|
||||||
|
|
||||||
@@ -133,7 +131,7 @@ Suggested fields:
|
|||||||
- `systemId`
|
- `systemId`
|
||||||
- `kind`
|
- `kind`
|
||||||
- `systemPosition`
|
- `systemPosition`
|
||||||
- `localspaceId`
|
- `localspaceRadius`
|
||||||
- `parentAnchorId?`
|
- `parentAnchorId?`
|
||||||
- `orbital metadata?`
|
- `orbital metadata?`
|
||||||
- `constructionIds?`
|
- `constructionIds?`
|
||||||
@@ -146,25 +144,9 @@ Recommended anchor kinds:
|
|||||||
- `lagrange-point`
|
- `lagrange-point`
|
||||||
- `resource-node`
|
- `resource-node`
|
||||||
|
|
||||||
## Localspace
|
|
||||||
|
|
||||||
A localspace is the tactical simulation context attached to one anchor.
|
|
||||||
|
|
||||||
Suggested fields:
|
|
||||||
|
|
||||||
- `localspaceId`
|
|
||||||
- `anchorId`
|
|
||||||
- `systemId`
|
|
||||||
- `radius`
|
|
||||||
- `occupantShipIds`
|
|
||||||
- `occupantStationIds`
|
|
||||||
- `occupantClaimIds`
|
|
||||||
- `occupantConstructionSiteIds`
|
|
||||||
- `serverAuthorityId later`
|
|
||||||
|
|
||||||
## Station
|
## Station
|
||||||
|
|
||||||
A station is a constructed structure that lives inside one localspace.
|
A station is a constructed structure that lives at one anchor.
|
||||||
|
|
||||||
Suggested fields:
|
Suggested fields:
|
||||||
|
|
||||||
@@ -173,7 +155,6 @@ Suggested fields:
|
|||||||
- `commanderId?`
|
- `commanderId?`
|
||||||
- `anchorId`
|
- `anchorId`
|
||||||
- `systemId`
|
- `systemId`
|
||||||
- `localspaceId`
|
|
||||||
- `moduleIds`
|
- `moduleIds`
|
||||||
- `inventory`
|
- `inventory`
|
||||||
- `population`
|
- `population`
|
||||||
@@ -236,7 +217,6 @@ Suggested fields:
|
|||||||
- `commanderId?`
|
- `commanderId?`
|
||||||
- `systemId`
|
- `systemId`
|
||||||
- `anchorId`
|
- `anchorId`
|
||||||
- `localspaceId`
|
|
||||||
- `placedAt`
|
- `placedAt`
|
||||||
- `activatesAt`
|
- `activatesAt`
|
||||||
- `state`
|
- `state`
|
||||||
@@ -258,7 +238,6 @@ Suggested fields:
|
|||||||
- `constructionSiteId`
|
- `constructionSiteId`
|
||||||
- `ownerFactionId`
|
- `ownerFactionId`
|
||||||
- `anchorId`
|
- `anchorId`
|
||||||
- `localspaceId`
|
|
||||||
- `targetKind`
|
- `targetKind`
|
||||||
- `targetDefinitionId`
|
- `targetDefinitionId`
|
||||||
- `requiredItems`
|
- `requiredItems`
|
||||||
@@ -351,7 +330,6 @@ Recommended ship spatial state fields:
|
|||||||
- `spaceLayer`
|
- `spaceLayer`
|
||||||
- `currentSystemId`
|
- `currentSystemId`
|
||||||
- `currentAnchorId?`
|
- `currentAnchorId?`
|
||||||
- `currentLocalspaceId?`
|
|
||||||
- `localPosition?`
|
- `localPosition?`
|
||||||
- `systemPosition?`
|
- `systemPosition?`
|
||||||
- `movementRegime`
|
- `movementRegime`
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ Every event should conceptually have:
|
|||||||
- `kind`
|
- `kind`
|
||||||
- `spaceLayer`
|
- `spaceLayer`
|
||||||
- `systemId?`
|
- `systemId?`
|
||||||
- `localspaceId?`
|
|
||||||
- `anchorId?`
|
- `anchorId?`
|
||||||
- `primaryEntityKind`
|
- `primaryEntityKind`
|
||||||
- `primaryEntityId`
|
- `primaryEntityId`
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Current state:
|
|||||||
|
|
||||||
Primary gaps:
|
Primary gaps:
|
||||||
|
|
||||||
- [`RuntimeModels.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/RuntimeModels.cs) has no `AnchorRuntime`, `LocalspaceRuntime`, `ClaimRuntime`, or `ConstructionSiteRuntime`.
|
- [`RuntimeModels.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/RuntimeModels.cs) has no first-class `AnchorRuntime` aligned with the target design, plus no fully anchor-native `ClaimRuntime` or `ConstructionSiteRuntime`.
|
||||||
- [`ScenarioLoader.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/ScenarioLoader.cs) computes station positions directly instead of creating anchor-backed placement.
|
- [`ScenarioLoader.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/ScenarioLoader.cs) computes station positions directly instead of creating anchor-backed placement.
|
||||||
- [`SimulationEngine.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/SimulationEngine.cs) still treats travel as raw coordinate movement rather than anchor-to-anchor transit between spaces.
|
- [`SimulationEngine.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/SimulationEngine.cs) still treats travel as raw coordinate movement rather than anchor-to-anchor transit between spaces.
|
||||||
|
|
||||||
@@ -247,7 +247,6 @@ Work:
|
|||||||
|
|
||||||
- extend [`RuntimeModels.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/RuntimeModels.cs) with:
|
- extend [`RuntimeModels.cs`](/home/jbourdon/repos/space-game/apps/backend/Simulation/RuntimeModels.cs) with:
|
||||||
- `AnchorRuntime`
|
- `AnchorRuntime`
|
||||||
- `LocalspaceRuntime`
|
|
||||||
- `CommanderRuntime`
|
- `CommanderRuntime`
|
||||||
- `ClaimRuntime`
|
- `ClaimRuntime`
|
||||||
- `ConstructionSiteRuntime`
|
- `ConstructionSiteRuntime`
|
||||||
@@ -262,7 +261,6 @@ Work:
|
|||||||
- add structured ship spatial state:
|
- add structured ship spatial state:
|
||||||
- current space layer
|
- current space layer
|
||||||
- current anchor
|
- current anchor
|
||||||
- current localspace
|
|
||||||
- current transit
|
- current transit
|
||||||
|
|
||||||
Why first:
|
Why first:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ It is the canonical reference for:
|
|||||||
- solar systems
|
- solar systems
|
||||||
- celestials
|
- celestials
|
||||||
- anchors
|
- anchors
|
||||||
- localspaces
|
- localspaces as the tactical space around anchors
|
||||||
- ship and station placement
|
- ship and station placement
|
||||||
- intra-system travel
|
- intra-system travel
|
||||||
- inter-system travel
|
- inter-system travel
|
||||||
@@ -27,14 +27,13 @@ The structure can be understood as a tree:
|
|||||||
- the galaxy contains solar systems
|
- the galaxy contains solar systems
|
||||||
- each solar system contains celestials and other derived locations
|
- each solar system contains celestials and other derived locations
|
||||||
- each meaningful location is an anchor
|
- each meaningful location is an anchor
|
||||||
- each anchor owns one localspace
|
- each anchor has a localspace around it
|
||||||
|
|
||||||
The intended structure is:
|
The intended structure is:
|
||||||
|
|
||||||
1. `galaxy`
|
1. `galaxy`
|
||||||
2. `solar system`
|
2. `solar system`
|
||||||
3. `anchor`
|
3. `anchor`
|
||||||
4. `localspace`
|
|
||||||
|
|
||||||
Ships and stations do not live in arbitrary free-floating "system local space".
|
Ships and stations do not live in arbitrary free-floating "system local space".
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ Systems remain important for:
|
|||||||
|
|
||||||
## Anchors
|
## Anchors
|
||||||
|
|
||||||
An anchor is a meaningful object in a system that owns a localspace.
|
An anchor is a meaningful object in a system.
|
||||||
|
|
||||||
Anchors are first-class world entities.
|
Anchors are first-class world entities.
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@ Each anchor should have:
|
|||||||
- an anchor type
|
- an anchor type
|
||||||
- a position in system space
|
- a position in system space
|
||||||
- optional orbital metadata
|
- optional orbital metadata
|
||||||
- an associated localspace definition
|
- a localspace around it
|
||||||
- optional parent/child relationships
|
- optional parent/child relationships
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -131,7 +130,7 @@ Examples:
|
|||||||
- a resource node is an anchor but not a celestial
|
- a resource node is an anchor but not a celestial
|
||||||
- a Lagrange point can be the child of a moon, which is the child of a planet, which is the child of a star
|
- a Lagrange point can be the child of a moon, which is the child of a planet, which is the child of a star
|
||||||
|
|
||||||
Every anchor has exactly one localspace.
|
Every anchor has exactly one localspace around it.
|
||||||
|
|
||||||
## Celestials
|
## Celestials
|
||||||
|
|
||||||
@@ -147,9 +146,9 @@ Celestials exist for three reasons:
|
|||||||
|
|
||||||
1. they structure the solar system visually and strategically
|
1. they structure the solar system visually and strategically
|
||||||
2. they define orbital relationships
|
2. they define orbital relationships
|
||||||
3. they provide valid anchors for localspaces and derived locations such as Lagrange points
|
3. they provide valid anchors and derived locations such as Lagrange points
|
||||||
|
|
||||||
Every star, planet, and moon gets a localspace.
|
Every star, planet, and moon has a localspace around it.
|
||||||
|
|
||||||
Not all anchors are celestials, but all celestials are anchors.
|
Not all anchors are celestials, but all celestials are anchors.
|
||||||
|
|
||||||
@@ -165,7 +164,7 @@ Initial assumptions:
|
|||||||
|
|
||||||
- major orbitals can expose `L1` through `L5`
|
- major orbitals can expose `L1` through `L5`
|
||||||
- each exposed Lagrange point is its own anchor
|
- each exposed Lagrange point is its own anchor
|
||||||
- each exposed Lagrange point has its own localspace
|
- each exposed Lagrange point has its own localspace around it
|
||||||
- Lagrange points are valid construction sites
|
- Lagrange points are valid construction sites
|
||||||
|
|
||||||
For now, all five may exist for supported orbitals, but the intended direction is that only major planets should necessarily expose all five.
|
For now, all five may exist for supported orbitals, but the intended direction is that only major planets should necessarily expose all five.
|
||||||
@@ -186,7 +185,7 @@ That means a resource node can have:
|
|||||||
|
|
||||||
- a stable identity
|
- a stable identity
|
||||||
- a place in a solar system
|
- a place in a solar system
|
||||||
- its own localspace
|
- its own localspace around it
|
||||||
|
|
||||||
This is desirable because it allows resources to exist anywhere meaningful in a system while still fitting the anchored localspace model.
|
This is desirable because it allows resources to exist anywhere meaningful in a system while still fitting the anchored localspace model.
|
||||||
|
|
||||||
@@ -198,6 +197,22 @@ Resource nodes are not construction sites.
|
|||||||
|
|
||||||
That is intentional so they can be spawned, depleted, despawned, and regenerated more freely than permanent infrastructure anchors.
|
That is intentional so they can be spawned, depleted, despawned, and regenerated more freely than permanent infrastructure anchors.
|
||||||
|
|
||||||
|
Resource nodes are strategic mining destinations.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
- outside localspace, a miner travels to a resource-node anchor
|
||||||
|
- inside that localspace, the miner chooses a concrete extractable target
|
||||||
|
|
||||||
|
The concrete extractable target is a tactical mining concern, not a strategic travel identity.
|
||||||
|
|
||||||
|
So:
|
||||||
|
|
||||||
|
- travel uses `anchorId`
|
||||||
|
- local extraction uses a localspace-level target such as a rock, cluster, or gas pocket
|
||||||
|
|
||||||
|
Do not use a generic `NodeId` to mean both.
|
||||||
|
|
||||||
## Localspace
|
## Localspace
|
||||||
|
|
||||||
`localspace` is the tactical simulation term and should be the only term used for this concept.
|
`localspace` is the tactical simulation term and should be the only term used for this concept.
|
||||||
@@ -212,7 +227,7 @@ Use:
|
|||||||
|
|
||||||
- `localspace`
|
- `localspace`
|
||||||
|
|
||||||
Each localspace belongs to exactly one anchor.
|
Each localspace is the tactical space around exactly one anchor.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@@ -233,16 +248,26 @@ Localspace is where close simulation happens:
|
|||||||
- local logistics
|
- local logistics
|
||||||
- tactical defense
|
- tactical defense
|
||||||
|
|
||||||
|
For mining specifically:
|
||||||
|
|
||||||
|
- the destination localspace is chosen by anchor
|
||||||
|
- the final extractable target is chosen only after the ship is inside that localspace
|
||||||
|
|
||||||
|
This preserves the distinction between:
|
||||||
|
|
||||||
|
- strategic travel to a mining site
|
||||||
|
- tactical mining behavior inside that site
|
||||||
|
|
||||||
Ships and constructions do not exist directly in system space. They exist in one localspace at a time unless they are explicitly traveling between anchors.
|
Ships and constructions do not exist directly in system space. They exist in one localspace at a time unless they are explicitly traveling between anchors.
|
||||||
|
|
||||||
## Ship Placement
|
## Ship Placement
|
||||||
|
|
||||||
A ship always belongs to exactly one localspace unless it is actively transitioning between anchors.
|
A ship always belongs to exactly one anchor unless it is actively transitioning between anchors.
|
||||||
|
|
||||||
Normal ship state should be one of:
|
Normal ship state should be one of:
|
||||||
|
|
||||||
- in a localspace
|
- at an anchor
|
||||||
- traveling between localspaces in the same system
|
- traveling between anchors in the same system
|
||||||
- traveling between systems
|
- traveling between systems
|
||||||
|
|
||||||
Inside a localspace, ships use tactical movement with thrusters.
|
Inside a localspace, ships use tactical movement with thrusters.
|
||||||
@@ -444,6 +469,82 @@ Localspace view should show:
|
|||||||
|
|
||||||
The viewer should not imply that the full solar system is one continuous local battlefield.
|
The viewer should not imply that the full solar system is one continuous local battlefield.
|
||||||
|
|
||||||
|
Viewer zoom transitions are a client-side presentation effect.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
- zooming from galaxy to system does not imply one shared coordinate space
|
||||||
|
- zooming from system to localspace does not imply one shared coordinate space
|
||||||
|
- the client may animate or blend between views for readability
|
||||||
|
- those effects do not define simulation truth
|
||||||
|
|
||||||
|
## Scales And Units
|
||||||
|
|
||||||
|
Each spatial layer owns its own units.
|
||||||
|
|
||||||
|
Those units should be native to that layer, not projections of one universal master coordinate system.
|
||||||
|
|
||||||
|
### Galaxy
|
||||||
|
|
||||||
|
The galaxy layer uses:
|
||||||
|
|
||||||
|
- `light-years`
|
||||||
|
|
||||||
|
This is the scale for:
|
||||||
|
|
||||||
|
- system positions
|
||||||
|
- inter-system distances
|
||||||
|
- strategic map layout
|
||||||
|
|
||||||
|
### System
|
||||||
|
|
||||||
|
The system layer uses:
|
||||||
|
|
||||||
|
- `AU`
|
||||||
|
|
||||||
|
This is the scale for:
|
||||||
|
|
||||||
|
- anchor positions inside a system
|
||||||
|
- orbital relationships
|
||||||
|
- intra-system routing and warp travel
|
||||||
|
|
||||||
|
Variation at localspace scale is intentionally insignificant at this layer.
|
||||||
|
|
||||||
|
### Localspace
|
||||||
|
|
||||||
|
The localspace layer uses:
|
||||||
|
|
||||||
|
- `meters`
|
||||||
|
- `m/s`
|
||||||
|
|
||||||
|
This is the scale for:
|
||||||
|
|
||||||
|
- tactical movement
|
||||||
|
- combat
|
||||||
|
- docking
|
||||||
|
- mining
|
||||||
|
- construction
|
||||||
|
|
||||||
|
Variation at system scale is intentionally insignificant at this layer.
|
||||||
|
|
||||||
|
## Cross-Layer Rule
|
||||||
|
|
||||||
|
The simulation should not try to preserve one continuous coordinate frame across galaxy, system, and localspace.
|
||||||
|
|
||||||
|
Cross-layer relationships should be modeled through:
|
||||||
|
|
||||||
|
- containment
|
||||||
|
- references
|
||||||
|
- travel state
|
||||||
|
|
||||||
|
Not through:
|
||||||
|
|
||||||
|
- universal coordinate conversion
|
||||||
|
- raw projection of localspace offsets into system space
|
||||||
|
- raw projection of system offsets into galaxy space
|
||||||
|
|
||||||
|
The viewer may animate transitions between layers, but that is presentation only. It does not mean the simulation uses one continuous spatial frame.
|
||||||
|
|
||||||
## Ownership And Sovereignty
|
## Ownership And Sovereignty
|
||||||
|
|
||||||
Ownership and sovereignty should primarily be tracked at system level.
|
Ownership and sovereignty should primarily be tracked at system level.
|
||||||
@@ -451,7 +552,7 @@ Ownership and sovereignty should primarily be tracked at system level.
|
|||||||
This is not fully defined yet, but the current design direction is:
|
This is not fully defined yet, but the current design direction is:
|
||||||
|
|
||||||
- systems are the main sovereignty unit
|
- systems are the main sovereignty unit
|
||||||
- localspaces and constructions exist inside systems
|
- anchors and constructions exist inside systems
|
||||||
- local conflicts and control still matter tactically
|
- local conflicts and control still matter tactically
|
||||||
|
|
||||||
## Simulation Implications
|
## Simulation Implications
|
||||||
@@ -466,7 +567,7 @@ This model supports:
|
|||||||
|
|
||||||
It also gives a cleaner authority boundary for later scaling:
|
It also gives a cleaner authority boundary for later scaling:
|
||||||
|
|
||||||
- one localspace can become one simulation partition
|
- one anchor can become one tactical simulation partition
|
||||||
- one system can remain a higher-level strategic container
|
- one system can remain a higher-level strategic container
|
||||||
|
|
||||||
This supports:
|
This supports:
|
||||||
@@ -493,8 +594,8 @@ Until the implementation is updated, the following terms should be used consiste
|
|||||||
- `galaxy`: the top-level strategic star map
|
- `galaxy`: the top-level strategic star map
|
||||||
- `system`: the solar system container
|
- `system`: the solar system container
|
||||||
- `celestial`: star, planet, or moon
|
- `celestial`: star, planet, or moon
|
||||||
- `anchor`: anything that owns a localspace
|
- `anchor`: a meaningful location in a system and the primary tactical simulation container
|
||||||
- `localspace`: the tactical simulation bubble attached to one anchor
|
- `localspace`: the tactical simulation space around an anchor, not a separately identified entity
|
||||||
- `intra-system warp`: movement between anchors in the same system
|
- `intra-system warp`: movement between anchors in the same system
|
||||||
- `inter-system FTL`: movement between systems
|
- `inter-system FTL`: movement between systems
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user