feat: improved ops-strip with faction and stations
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
namespace SpaceGame.Simulation.Api.Contracts;
|
||||
|
||||
public sealed record FactionGoapStateSnapshot(
|
||||
int MilitaryShipCount,
|
||||
int MinerShipCount,
|
||||
int TransportShipCount,
|
||||
int ConstructorShipCount,
|
||||
int ControlledSystemCount,
|
||||
int TargetSystemCount,
|
||||
bool HasShipFactory,
|
||||
float OreStockpile,
|
||||
float RefinedMetalsStockpile);
|
||||
|
||||
public sealed record FactionGoapPrioritySnapshot(string GoalName, float Priority);
|
||||
|
||||
public sealed record FactionSnapshot(
|
||||
string Id,
|
||||
string Label,
|
||||
@@ -10,7 +23,9 @@ public sealed record FactionSnapshot(
|
||||
float GoodsProduced,
|
||||
int ShipsBuilt,
|
||||
int ShipsLost,
|
||||
string? DefaultPolicySetId);
|
||||
string? DefaultPolicySetId,
|
||||
FactionGoapStateSnapshot? GoapState,
|
||||
IReadOnlyList<FactionGoapPrioritySnapshot>? GoapPriorities);
|
||||
|
||||
public sealed record FactionDelta(
|
||||
string Id,
|
||||
@@ -22,4 +37,6 @@ public sealed record FactionDelta(
|
||||
float GoodsProduced,
|
||||
int ShipsBuilt,
|
||||
int ShipsLost,
|
||||
string? DefaultPolicySetId);
|
||||
string? DefaultPolicySetId,
|
||||
FactionGoapStateSnapshot? GoapState,
|
||||
IReadOnlyList<FactionGoapPrioritySnapshot>? GoapPriorities);
|
||||
|
||||
@@ -36,6 +36,8 @@ public sealed class CommanderRuntime
|
||||
public CommanderTaskRuntime? ActiveTask { get; set; }
|
||||
public HashSet<string> SubordinateCommanderIds { get; } = new(StringComparer.Ordinal);
|
||||
public bool IsAlive { get; set; } = true;
|
||||
public FactionPlanningState? LastPlanningState { get; set; }
|
||||
public IReadOnlyList<(string Name, float Priority)>? LastGoalPriorities { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CommanderBehaviorRuntime
|
||||
|
||||
@@ -21,10 +21,7 @@ public sealed partial class ScenarioLoader
|
||||
factionIds.Add(DefaultFactionId);
|
||||
}
|
||||
|
||||
factionIds.Add(UnclaimedFactionId);
|
||||
|
||||
return factionIds
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.Select(CreateFaction)
|
||||
.ToList();
|
||||
}
|
||||
@@ -40,13 +37,6 @@ public sealed partial class ScenarioLoader
|
||||
Color = "#7ed4ff",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
UnclaimedFactionId => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
Label = "Unclaimed",
|
||||
Color = "#7f8794",
|
||||
Credits = 0f,
|
||||
},
|
||||
_ => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
@@ -107,26 +97,21 @@ public sealed partial class ScenarioLoader
|
||||
var claims = new List<ClaimRuntime>();
|
||||
foreach (var node in nodes.Where((candidate) => candidate.Kind == SpatialNodeKind.LagrangePoint))
|
||||
{
|
||||
var owningFactionId = stationsByAnchorNodeId.TryGetValue(node.Id, out var station)
|
||||
? station.FactionId
|
||||
: UnclaimedFactionId;
|
||||
var activatesAtUtc = owningFactionId == UnclaimedFactionId
|
||||
? nowUtc
|
||||
: nowUtc.AddSeconds(8);
|
||||
var state = owningFactionId == UnclaimedFactionId
|
||||
? ClaimStateKinds.Active
|
||||
: ClaimStateKinds.Activating;
|
||||
if (!stationsByAnchorNodeId.TryGetValue(node.Id, out var station))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
claims.Add(new ClaimRuntime
|
||||
{
|
||||
Id = $"claim-{node.Id}",
|
||||
FactionId = owningFactionId,
|
||||
FactionId = station.FactionId,
|
||||
SystemId = node.SystemId,
|
||||
NodeId = node.Id,
|
||||
BubbleId = node.BubbleId,
|
||||
PlacedAtUtc = nowUtc,
|
||||
ActivatesAtUtc = activatesAtUtc,
|
||||
State = state,
|
||||
ActivatesAtUtc = nowUtc.AddSeconds(8),
|
||||
State = ClaimStateKinds.Activating,
|
||||
Health = 100f,
|
||||
});
|
||||
}
|
||||
@@ -248,11 +233,6 @@ public sealed partial class ScenarioLoader
|
||||
var policies = new List<PolicySetRuntime>(factions.Count);
|
||||
foreach (var faction in factions)
|
||||
{
|
||||
if (string.Equals(faction.Id, UnclaimedFactionId, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var policyId = $"policy-{faction.Id}";
|
||||
faction.DefaultPolicySetId = policyId;
|
||||
policies.Add(new PolicySetRuntime
|
||||
@@ -277,11 +257,6 @@ public sealed partial class ScenarioLoader
|
||||
|
||||
foreach (var faction in factions)
|
||||
{
|
||||
if (string.Equals(faction.Id, UnclaimedFactionId, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var commander = new CommanderRuntime
|
||||
{
|
||||
Id = $"commander-faction-{faction.Id}",
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace SpaceGame.Simulation.Api.Simulation;
|
||||
public sealed partial class ScenarioLoader
|
||||
{
|
||||
private const string DefaultFactionId = "sol-dominion";
|
||||
private const string UnclaimedFactionId = "unclaimed";
|
||||
private const int WorldSeed = 1;
|
||||
private const float MinimumFactionCredits = 0f;
|
||||
private const float MinimumRefineryOre = 0f;
|
||||
|
||||
@@ -81,7 +81,11 @@ public sealed partial class SimulationEngine
|
||||
var rankedGoals = _factionGoals
|
||||
.Select(g => (goal: g, priority: g.ComputePriority(state, world, commander)))
|
||||
.Where(x => x.priority > 0f)
|
||||
.OrderByDescending(x => x.priority);
|
||||
.OrderByDescending(x => x.priority)
|
||||
.ToList();
|
||||
|
||||
commander.LastPlanningState = state;
|
||||
commander.LastGoalPriorities = rankedGoals.Select(x => (x.goal.Name, x.priority)).ToList();
|
||||
|
||||
// Execute the first action of each active goal's plan (top 3 to avoid conflicts).
|
||||
foreach (var (goal, _) in rankedGoals.Take(3))
|
||||
|
||||
@@ -169,7 +169,7 @@ public sealed partial class SimulationEngine
|
||||
ship.History,
|
||||
ship.CurrentAction,
|
||||
ship.SpatialState)).ToList(),
|
||||
world.Factions.Select(ToFactionDelta).Select(faction => new FactionSnapshot(
|
||||
world.Factions.Select(faction => ToFactionDelta(faction, FindFactionCommander(world, faction.Id))).Select(faction => new FactionSnapshot(
|
||||
faction.Id,
|
||||
faction.Label,
|
||||
faction.Color,
|
||||
@@ -179,7 +179,9 @@ public sealed partial class SimulationEngine
|
||||
faction.GoodsProduced,
|
||||
faction.ShipsBuilt,
|
||||
faction.ShipsLost,
|
||||
faction.DefaultPolicySetId)).ToList());
|
||||
faction.DefaultPolicySetId,
|
||||
faction.GoapState,
|
||||
faction.GoapPriorities)).ToList());
|
||||
}
|
||||
|
||||
public void PrimeDeltaBaseline(SimulationWorld world)
|
||||
@@ -231,7 +233,7 @@ public sealed partial class SimulationEngine
|
||||
|
||||
foreach (var faction in world.Factions)
|
||||
{
|
||||
faction.LastDeltaSignature = BuildFactionSignature(faction);
|
||||
faction.LastDeltaSignature = BuildFactionSignature(faction, FindFactionCommander(world, faction.Id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,19 +404,25 @@ public sealed partial class SimulationEngine
|
||||
var deltas = new List<FactionDelta>();
|
||||
foreach (var faction in world.Factions)
|
||||
{
|
||||
var signature = BuildFactionSignature(faction);
|
||||
var commander = FindFactionCommander(world, faction.Id);
|
||||
var signature = BuildFactionSignature(faction, commander);
|
||||
if (signature == faction.LastDeltaSignature)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
faction.LastDeltaSignature = signature;
|
||||
deltas.Add(ToFactionDelta(faction));
|
||||
deltas.Add(ToFactionDelta(faction, commander));
|
||||
}
|
||||
|
||||
return deltas;
|
||||
}
|
||||
|
||||
private static CommanderRuntime? FindFactionCommander(SimulationWorld world, string factionId) =>
|
||||
world.Commanders.FirstOrDefault(c =>
|
||||
c.FactionId == factionId &&
|
||||
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
|
||||
|
||||
private static string BuildNodeSignature(ResourceNodeRuntime node) =>
|
||||
$"{node.SystemId}|{node.Position.X:0.###}|{node.Position.Y:0.###}|{node.Position.Z:0.###}|{node.AnchorNodeId}|{node.OreRemaining:0.###}";
|
||||
|
||||
@@ -508,8 +516,13 @@ public sealed partial class SimulationEngine
|
||||
.OrderBy(entry => entry.Key, StringComparer.Ordinal)
|
||||
.Select(entry => $"{entry.Key}:{entry.Value:0.###}"));
|
||||
|
||||
private static string BuildFactionSignature(FactionRuntime faction) =>
|
||||
$"{faction.Credits:0.###}|{faction.PopulationTotal:0.###}|{faction.OreMined:0.###}|{faction.GoodsProduced:0.###}|{faction.ShipsBuilt}|{faction.ShipsLost}|{faction.DefaultPolicySetId}";
|
||||
private static string BuildFactionSignature(FactionRuntime faction, CommanderRuntime? commander)
|
||||
{
|
||||
var goapSig = commander?.LastGoalPriorities is { } prios
|
||||
? string.Join(",", prios.Select(p => $"{p.Name}:{p.Priority:0.##}"))
|
||||
: string.Empty;
|
||||
return $"{faction.Credits:0.###}|{faction.PopulationTotal:0.###}|{faction.OreMined:0.###}|{faction.GoodsProduced:0.###}|{faction.ShipsBuilt}|{faction.ShipsLost}|{faction.DefaultPolicySetId}|{goapSig}";
|
||||
}
|
||||
|
||||
private static ResourceNodeDelta ToNodeDelta(ResourceNodeRuntime node) => new(
|
||||
node.Id,
|
||||
@@ -755,17 +768,44 @@ public sealed partial class SimulationEngine
|
||||
.Select(entry => new InventoryEntry(entry.Key, entry.Value))
|
||||
.ToList();
|
||||
|
||||
private static FactionDelta ToFactionDelta(FactionRuntime faction) => new(
|
||||
faction.Id,
|
||||
faction.Label,
|
||||
faction.Color,
|
||||
faction.Credits,
|
||||
faction.PopulationTotal,
|
||||
faction.OreMined,
|
||||
faction.GoodsProduced,
|
||||
faction.ShipsBuilt,
|
||||
faction.ShipsLost,
|
||||
faction.DefaultPolicySetId);
|
||||
private static FactionDelta ToFactionDelta(FactionRuntime faction, CommanderRuntime? commander)
|
||||
{
|
||||
FactionGoapStateSnapshot? goapState = null;
|
||||
IReadOnlyList<FactionGoapPrioritySnapshot>? goapPriorities = null;
|
||||
|
||||
if (commander?.LastPlanningState is { } ps)
|
||||
{
|
||||
goapState = new FactionGoapStateSnapshot(
|
||||
ps.MilitaryShipCount,
|
||||
ps.MinerShipCount,
|
||||
ps.TransportShipCount,
|
||||
ps.ConstructorShipCount,
|
||||
ps.ControlledSystemCount,
|
||||
ps.TargetSystemCount,
|
||||
ps.HasShipFactory,
|
||||
ps.OreStockpile,
|
||||
ps.RefinedMetalsStockpile);
|
||||
}
|
||||
|
||||
if (commander?.LastGoalPriorities is { } prios)
|
||||
{
|
||||
goapPriorities = prios.Select(p => new FactionGoapPrioritySnapshot(p.Name, p.Priority)).ToList();
|
||||
}
|
||||
|
||||
return new FactionDelta(
|
||||
faction.Id,
|
||||
faction.Label,
|
||||
faction.Color,
|
||||
faction.Credits,
|
||||
faction.PopulationTotal,
|
||||
faction.OreMined,
|
||||
faction.GoodsProduced,
|
||||
faction.ShipsBuilt,
|
||||
faction.ShipsLost,
|
||||
faction.DefaultPolicySetId,
|
||||
goapState,
|
||||
goapPriorities);
|
||||
}
|
||||
|
||||
private static ShipSpatialStateSnapshot ToShipSpatialStateSnapshot(ShipSpatialStateRuntime state) => new(
|
||||
state.SpaceLayer,
|
||||
|
||||
@@ -300,27 +300,24 @@ public sealed partial class SimulationEngine
|
||||
|
||||
private static int GetFactionControlledSystemsCount(SimulationWorld world, string factionId)
|
||||
{
|
||||
return world.Claims
|
||||
.Where(claim => claim.State != ClaimStateKinds.Destroyed)
|
||||
.Select(claim => claim.SystemId)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.Count(systemId => FactionControlsSystem(world, factionId, systemId));
|
||||
return world.Systems.Count(system => FactionControlsSystem(world, factionId, system.Definition.Id));
|
||||
}
|
||||
|
||||
private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId)
|
||||
{
|
||||
var buildableLocations = world.Claims
|
||||
.Where(claim =>
|
||||
claim.SystemId == systemId &&
|
||||
claim.State is ClaimStateKinds.Activating or ClaimStateKinds.Active)
|
||||
.ToList();
|
||||
if (buildableLocations.Count == 0)
|
||||
var totalLagrangePoints = world.SpatialNodes.Count(node =>
|
||||
node.SystemId == systemId &&
|
||||
node.Kind == SpatialNodeKind.LagrangePoint);
|
||||
if (totalLagrangePoints == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ownedLocations = buildableLocations.Count(claim => claim.FactionId == factionId);
|
||||
return ownedLocations > (buildableLocations.Count / 2f);
|
||||
var ownedLocations = world.Claims.Count(claim =>
|
||||
claim.SystemId == systemId &&
|
||||
claim.FactionId == factionId &&
|
||||
claim.State is ClaimStateKinds.Activating or ClaimStateKinds.Active);
|
||||
return ownedLocations > (totalLagrangePoints / 2f);
|
||||
}
|
||||
|
||||
private sealed record DesiredMarketOrder(string Kind, string ItemId, float Amount, float Valuation, float? ReserveThreshold);
|
||||
|
||||
Reference in New Issue
Block a user