Refactor runtime bootstrap and ship control flows
This commit is contained in:
@@ -7,7 +7,6 @@ public sealed class CreatePlayerOrganizationHandler(WorldService worldService) :
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/player-faction/organizations");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerOrganizationCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -12,7 +12,6 @@ public sealed class DeletePlayerDirectiveHandler(WorldService worldService) : En
|
||||
public override void Configure()
|
||||
{
|
||||
Delete("/api/player-faction/directives/{directiveId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(DeletePlayerDirectiveRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -12,7 +12,6 @@ public sealed class DeletePlayerOrganizationHandler(WorldService worldService) :
|
||||
public override void Configure()
|
||||
{
|
||||
Delete("/api/player-faction/organizations/{organizationId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(DeletePlayerOrganizationRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -7,7 +7,6 @@ public sealed class GetPlayerFactionHandler(WorldService worldService) : Endpoin
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/player-faction");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -7,7 +7,6 @@ public sealed class UpdatePlayerOrganizationMembershipHandler(WorldService world
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/api/player-faction/organizations/{organizationId}/membership");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerOrganizationMembershipCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -7,7 +7,6 @@ public sealed class UpdatePlayerStrategicIntentHandler(WorldService worldService
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/api/player-faction/strategic-intent");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerStrategicIntentCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -7,7 +7,6 @@ public sealed class UpsertPlayerAssignmentHandler(WorldService worldService) : E
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/api/player-faction/assets/{assetId}/assignment");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerAssetAssignmentCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -8,7 +8,6 @@ public sealed class UpsertPlayerAutomationPolicyHandler(WorldService worldServic
|
||||
{
|
||||
Post("/api/player-faction/automation-policies");
|
||||
Put("/api/player-faction/automation-policies/{automationPolicyId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerAutomationPolicyCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -8,7 +8,6 @@ public sealed class UpsertPlayerDirectiveHandler(WorldService worldService) : En
|
||||
{
|
||||
Post("/api/player-faction/directives");
|
||||
Put("/api/player-faction/directives/{directiveId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerDirectiveCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -8,7 +8,6 @@ public sealed class UpsertPlayerPolicyHandler(WorldService worldService) : Endpo
|
||||
{
|
||||
Post("/api/player-faction/policies");
|
||||
Put("/api/player-faction/policies/{policyId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerPolicyCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -8,7 +8,6 @@ public sealed class UpsertPlayerProductionProgramHandler(WorldService worldServi
|
||||
{
|
||||
Post("/api/player-faction/production-programs");
|
||||
Put("/api/player-faction/production-programs/{productionProgramId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerProductionProgramCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -8,7 +8,6 @@ public sealed class UpsertPlayerReinforcementPolicyHandler(WorldService worldSer
|
||||
{
|
||||
Post("/api/player-faction/reinforcement-policies");
|
||||
Put("/api/player-faction/reinforcement-policies/{reinforcementPolicyId}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PlayerReinforcementPolicyCommandRequest request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
|
||||
|
||||
namespace SpaceGame.Api.PlayerFaction.Runtime;
|
||||
|
||||
public sealed class PlayerFactionRuntime
|
||||
@@ -180,7 +182,7 @@ public sealed class PlayerAutomationPolicyRuntime
|
||||
public string ScopeKind { get; set; } = "player-faction";
|
||||
public string? ScopeId { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string BehaviorKind { get; set; } = "idle";
|
||||
public string BehaviorKind { get; set; } = Idle;
|
||||
public bool UseOrders { get; set; }
|
||||
public string? StagingOrderKind { get; set; }
|
||||
public int MaxSystemRange { get; set; }
|
||||
@@ -242,7 +244,7 @@ public sealed class PlayerDirectiveRuntime
|
||||
public string? HomeStationId { get; set; }
|
||||
public string? SourceStationId { get; set; }
|
||||
public string? DestinationStationId { get; set; }
|
||||
public string BehaviorKind { get; set; } = "idle";
|
||||
public string BehaviorKind { get; set; } = Idle;
|
||||
public bool UseOrders { get; set; }
|
||||
public string? StagingOrderKind { get; set; }
|
||||
public string? ItemId { get; set; }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace SpaceGame.Api.PlayerFaction.Simulation;
|
||||
|
||||
public interface IPlayerStateStore
|
||||
{
|
||||
bool TryGetPlayerFaction(string playerId, out PlayerFactionRuntime playerFaction);
|
||||
PlayerFactionRuntime GetOrAddPlayerFaction(string playerId, Func<PlayerFactionRuntime> factory);
|
||||
IReadOnlyCollection<PlayerFactionRuntime> GetPlayerFactions();
|
||||
void Clear();
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
namespace SpaceGame.Api.PlayerFaction.Simulation;
|
||||
|
||||
public sealed class PlayerFactionProjectionService
|
||||
{
|
||||
public PlayerFactionSnapshot? ToSnapshot(PlayerFactionRuntime? player)
|
||||
{
|
||||
if (player is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PlayerFactionSnapshot(
|
||||
player.Id,
|
||||
player.Label,
|
||||
player.SovereignFactionId,
|
||||
player.Status,
|
||||
player.CreatedAtUtc,
|
||||
player.UpdatedAtUtc,
|
||||
new PlayerAssetRegistrySnapshot(
|
||||
player.AssetRegistry.ShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.StationIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.CommanderIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.ClaimIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.ConstructionSiteIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.PolicySetIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.FleetIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.TaskForceIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.StationGroupIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.EconomicRegionIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.FrontIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
player.AssetRegistry.ReserveIds.OrderBy(id => id, StringComparer.Ordinal).ToList()),
|
||||
new PlayerStrategicIntentSnapshot(
|
||||
player.StrategicIntent.StrategicPosture,
|
||||
player.StrategicIntent.EconomicPosture,
|
||||
player.StrategicIntent.MilitaryPosture,
|
||||
player.StrategicIntent.LogisticsPosture,
|
||||
player.StrategicIntent.DesiredReserveRatio,
|
||||
player.StrategicIntent.AllowDelegatedCombatAutomation,
|
||||
player.StrategicIntent.AllowDelegatedEconomicAutomation,
|
||||
player.StrategicIntent.Notes),
|
||||
player.Fleets.Select(fleet => new PlayerFleetSnapshot(
|
||||
fleet.Id,
|
||||
fleet.Label,
|
||||
fleet.Status,
|
||||
fleet.Role,
|
||||
fleet.CommanderId,
|
||||
fleet.FrontId,
|
||||
fleet.HomeSystemId,
|
||||
fleet.HomeStationId,
|
||||
fleet.PolicyId,
|
||||
fleet.AutomationPolicyId,
|
||||
fleet.ReinforcementPolicyId,
|
||||
fleet.AssetIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
fleet.TaskForceIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
fleet.DirectiveIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
fleet.UpdatedAtUtc)).ToList(),
|
||||
player.TaskForces.Select(taskForce => new PlayerTaskForceSnapshot(
|
||||
taskForce.Id,
|
||||
taskForce.Label,
|
||||
taskForce.Status,
|
||||
taskForce.Role,
|
||||
taskForce.FleetId,
|
||||
taskForce.CommanderId,
|
||||
taskForce.FrontId,
|
||||
taskForce.PolicyId,
|
||||
taskForce.AutomationPolicyId,
|
||||
taskForce.AssetIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
taskForce.DirectiveIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
taskForce.UpdatedAtUtc)).ToList(),
|
||||
player.StationGroups.Select(group => new PlayerStationGroupSnapshot(
|
||||
group.Id,
|
||||
group.Label,
|
||||
group.Status,
|
||||
group.Role,
|
||||
group.EconomicRegionId,
|
||||
group.PolicyId,
|
||||
group.AutomationPolicyId,
|
||||
group.StationIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
group.DirectiveIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
group.FocusItemIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
group.UpdatedAtUtc)).ToList(),
|
||||
player.EconomicRegions.Select(region => new PlayerEconomicRegionSnapshot(
|
||||
region.Id,
|
||||
region.Label,
|
||||
region.Status,
|
||||
region.Role,
|
||||
region.SharedEconomicRegionId,
|
||||
region.PolicyId,
|
||||
region.AutomationPolicyId,
|
||||
region.SystemIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
region.StationGroupIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
region.DirectiveIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
region.UpdatedAtUtc)).ToList(),
|
||||
player.Fronts.Select(front => new PlayerFrontSnapshot(
|
||||
front.Id,
|
||||
front.Label,
|
||||
front.Status,
|
||||
front.Priority,
|
||||
front.Posture,
|
||||
front.SharedFrontLineId,
|
||||
front.TargetFactionId,
|
||||
front.SystemIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
front.FleetIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
front.ReserveIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
front.DirectiveIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
front.UpdatedAtUtc)).ToList(),
|
||||
player.Reserves.Select(reserve => new PlayerReserveGroupSnapshot(
|
||||
reserve.Id,
|
||||
reserve.Label,
|
||||
reserve.Status,
|
||||
reserve.ReserveKind,
|
||||
reserve.HomeSystemId,
|
||||
reserve.PolicyId,
|
||||
reserve.AssetIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
reserve.FrontIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
reserve.UpdatedAtUtc)).ToList(),
|
||||
player.Policies.Select(policy => new PlayerFactionPolicySnapshot(
|
||||
policy.Id,
|
||||
policy.Label,
|
||||
policy.ScopeKind,
|
||||
policy.ScopeId,
|
||||
policy.PolicySetId,
|
||||
policy.AllowDelegatedCombat,
|
||||
policy.AllowDelegatedTrade,
|
||||
policy.ReserveCreditsRatio,
|
||||
policy.ReserveMilitaryRatio,
|
||||
policy.TradeAccessPolicy,
|
||||
policy.DockingAccessPolicy,
|
||||
policy.ConstructionAccessPolicy,
|
||||
policy.OperationalRangePolicy,
|
||||
policy.CombatEngagementPolicy,
|
||||
policy.AvoidHostileSystems,
|
||||
policy.FleeHullRatio,
|
||||
policy.BlacklistedSystemIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
policy.Notes,
|
||||
policy.UpdatedAtUtc)).ToList(),
|
||||
player.AutomationPolicies.Select(policy => new PlayerAutomationPolicySnapshot(
|
||||
policy.Id,
|
||||
policy.Label,
|
||||
policy.ScopeKind,
|
||||
policy.ScopeId,
|
||||
policy.Enabled,
|
||||
policy.BehaviorKind,
|
||||
policy.UseOrders,
|
||||
policy.StagingOrderKind,
|
||||
policy.MaxSystemRange,
|
||||
policy.KnownStationsOnly,
|
||||
policy.Radius,
|
||||
policy.WaitSeconds,
|
||||
policy.PreferredItemId,
|
||||
policy.Notes,
|
||||
policy.RepeatOrders.Select(ToShipOrderTemplateSnapshot).ToList(),
|
||||
policy.UpdatedAtUtc)).ToList(),
|
||||
player.ReinforcementPolicies.Select(policy => new PlayerReinforcementPolicySnapshot(
|
||||
policy.Id,
|
||||
policy.Label,
|
||||
policy.ScopeKind,
|
||||
policy.ScopeId,
|
||||
policy.ShipKind,
|
||||
policy.DesiredAssetCount,
|
||||
policy.MinimumReserveCount,
|
||||
policy.AutoTransferReserves,
|
||||
policy.AutoQueueProduction,
|
||||
policy.SourceReserveId,
|
||||
policy.TargetFrontId,
|
||||
policy.Notes,
|
||||
policy.UpdatedAtUtc)).ToList(),
|
||||
player.ProductionPrograms.Select(program => new PlayerProductionProgramSnapshot(
|
||||
program.Id,
|
||||
program.Label,
|
||||
program.Status,
|
||||
program.Kind,
|
||||
program.TargetShipKind,
|
||||
program.TargetModuleId,
|
||||
program.TargetItemId,
|
||||
program.TargetCount,
|
||||
program.CurrentCount,
|
||||
program.StationGroupId,
|
||||
program.ReinforcementPolicyId,
|
||||
program.Notes,
|
||||
program.UpdatedAtUtc)).ToList(),
|
||||
player.Directives.Select(directive => new PlayerDirectiveSnapshot(
|
||||
directive.Id,
|
||||
directive.Label,
|
||||
directive.Status,
|
||||
directive.Kind,
|
||||
directive.ScopeKind,
|
||||
directive.ScopeId,
|
||||
directive.TargetEntityId,
|
||||
directive.TargetSystemId,
|
||||
directive.TargetPosition is null ? null : ToDto(directive.TargetPosition.Value),
|
||||
directive.HomeSystemId,
|
||||
directive.HomeStationId,
|
||||
directive.SourceStationId,
|
||||
directive.DestinationStationId,
|
||||
directive.BehaviorKind,
|
||||
directive.UseOrders,
|
||||
directive.StagingOrderKind,
|
||||
directive.ItemId,
|
||||
directive.PreferredNodeId,
|
||||
directive.PreferredConstructionSiteId,
|
||||
directive.PreferredModuleId,
|
||||
directive.Priority,
|
||||
directive.Radius,
|
||||
directive.WaitSeconds,
|
||||
directive.MaxSystemRange,
|
||||
directive.KnownStationsOnly,
|
||||
directive.PatrolPoints.Select(ToDto).ToList(),
|
||||
directive.RepeatOrders.Select(ToShipOrderTemplateSnapshot).ToList(),
|
||||
directive.PolicyId,
|
||||
directive.AutomationPolicyId,
|
||||
directive.Notes,
|
||||
directive.CreatedAtUtc,
|
||||
directive.UpdatedAtUtc)).ToList(),
|
||||
player.Assignments.Select(assignment => new PlayerAssignmentSnapshot(
|
||||
assignment.Id,
|
||||
assignment.AssetKind,
|
||||
assignment.AssetId,
|
||||
assignment.FleetId,
|
||||
assignment.TaskForceId,
|
||||
assignment.StationGroupId,
|
||||
assignment.EconomicRegionId,
|
||||
assignment.FrontId,
|
||||
assignment.ReserveId,
|
||||
assignment.DirectiveId,
|
||||
assignment.PolicyId,
|
||||
assignment.AutomationPolicyId,
|
||||
assignment.Role,
|
||||
assignment.Status,
|
||||
assignment.UpdatedAtUtc)).ToList(),
|
||||
player.DecisionLog.Select(entry => new PlayerDecisionLogEntrySnapshot(
|
||||
entry.Id,
|
||||
entry.Kind,
|
||||
entry.Summary,
|
||||
entry.RelatedEntityKind,
|
||||
entry.RelatedEntityId,
|
||||
entry.OccurredAtUtc)).ToList(),
|
||||
player.Alerts.Select(alert => new PlayerAlertSnapshot(
|
||||
alert.Id,
|
||||
alert.Kind,
|
||||
alert.Severity,
|
||||
alert.Summary,
|
||||
alert.AssetKind,
|
||||
alert.AssetId,
|
||||
alert.RelatedDirectiveId,
|
||||
alert.Status,
|
||||
alert.CreatedAtUtc)).ToList());
|
||||
}
|
||||
|
||||
private static ShipOrderTemplateSnapshot ToShipOrderTemplateSnapshot(ShipOrderTemplateRuntime template) =>
|
||||
new(
|
||||
template.Kind,
|
||||
template.Label,
|
||||
template.TargetEntityId,
|
||||
template.TargetSystemId,
|
||||
template.TargetPosition is null ? null : ToDto(template.TargetPosition.Value),
|
||||
template.SourceStationId,
|
||||
template.DestinationStationId,
|
||||
template.ItemId,
|
||||
template.NodeId,
|
||||
template.ConstructionSiteId,
|
||||
template.ModuleId,
|
||||
template.WaitSeconds,
|
||||
template.Radius,
|
||||
template.MaxSystemRange,
|
||||
template.KnownStationsOnly);
|
||||
|
||||
private static Vector3Dto ToDto(Vector3 vector) => new(vector.X, vector.Y, vector.Z);
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
||||
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
|
||||
|
||||
namespace SpaceGame.Api.PlayerFaction.Simulation;
|
||||
|
||||
internal sealed class PlayerFactionService
|
||||
@@ -6,58 +9,61 @@ internal sealed class PlayerFactionService
|
||||
private const int MaxAlerts = 32;
|
||||
private const string PlayerFactionDomainId = "player-faction";
|
||||
|
||||
internal static bool IsPlayerFaction(SimulationWorld world, string factionId) =>
|
||||
world.PlayerFaction is not null && string.Equals(world.PlayerFaction.SovereignFactionId, factionId, StringComparison.Ordinal);
|
||||
internal static bool IsPlayerFaction(IPlayerStateStore playerStateStore, string factionId) =>
|
||||
playerStateStore.GetPlayerFactions().Any(player =>
|
||||
string.Equals(player.SovereignFactionId, factionId, StringComparison.Ordinal));
|
||||
|
||||
internal PlayerFactionRuntime EnsureDomain(SimulationWorld world)
|
||||
internal PlayerFactionRuntime? TryGetDomain(IPlayerStateStore playerStateStore, string playerId)
|
||||
{
|
||||
if (world.PlayerFaction is not null)
|
||||
{
|
||||
return world.PlayerFaction;
|
||||
}
|
||||
return playerStateStore.TryGetPlayerFaction(playerId, out var player) ? player : null;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime EnsureDomain(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId)
|
||||
{
|
||||
var sovereignFaction = world.Factions.OrderBy(faction => faction.Id, StringComparer.Ordinal).FirstOrDefault()
|
||||
?? throw new InvalidOperationException("Cannot create a player faction domain without any factions in the world.");
|
||||
|
||||
world.PlayerFaction = new PlayerFactionRuntime
|
||||
var player = playerStateStore.GetOrAddPlayerFaction(playerId, () => new PlayerFactionRuntime
|
||||
{
|
||||
Id = PlayerFactionDomainId,
|
||||
Label = $"{sovereignFaction.Label} Command",
|
||||
SovereignFactionId = sovereignFaction.Id,
|
||||
CreatedAtUtc = world.GeneratedAtUtc,
|
||||
UpdatedAtUtc = world.GeneratedAtUtc,
|
||||
};
|
||||
});
|
||||
|
||||
EnsureBaseStructures(world, world.PlayerFaction);
|
||||
SyncRegistry(world, world.PlayerFaction);
|
||||
return world.PlayerFaction;
|
||||
EnsureBaseStructures(world, player);
|
||||
SyncRegistry(world, player);
|
||||
return player;
|
||||
}
|
||||
|
||||
internal void Update(SimulationWorld world, float _deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
internal void Update(SimulationWorld world, IPlayerStateStore playerStateStore, float _deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
if (world.PlayerFaction is null && world.Factions.Count == 0)
|
||||
if (playerStateStore.GetPlayerFactions().Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var player = EnsureDomain(world);
|
||||
EnsureBaseStructures(world, player);
|
||||
SyncRegistry(world, player);
|
||||
PrunePlayerState(world, player);
|
||||
RefreshGeopoliticalOrganizationContext(world, player);
|
||||
ReconcileOrganizationAssignments(world, player);
|
||||
ReconcileDirectiveScopes(player);
|
||||
RefreshProductionPrograms(world, player);
|
||||
ApplyStrategicIntegration(world, player);
|
||||
ApplyPolicies(world, player);
|
||||
ApplyAssignmentsAndDirectives(world, player, events);
|
||||
RefreshAlerts(world, player);
|
||||
player.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
||||
foreach (var player in playerStateStore.GetPlayerFactions())
|
||||
{
|
||||
EnsureBaseStructures(world, player);
|
||||
SyncRegistry(world, player);
|
||||
PrunePlayerState(world, player);
|
||||
RefreshGeopoliticalOrganizationContext(world, player);
|
||||
ReconcileOrganizationAssignments(world, player);
|
||||
ReconcileDirectiveScopes(player);
|
||||
RefreshProductionPrograms(world, player);
|
||||
ApplyStrategicIntegration(world, player);
|
||||
ApplyPolicies(world, player);
|
||||
ApplyAssignmentsAndDirectives(world, player, events);
|
||||
RefreshAlerts(world, player);
|
||||
player.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime CreateOrganization(SimulationWorld world, PlayerOrganizationCommandRequest request)
|
||||
internal PlayerFactionRuntime CreateOrganization(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, PlayerOrganizationCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var id = CreateDomainId(request.Kind, request.Label, ExistingOrganizationIds(player));
|
||||
var nowUtc = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -172,9 +178,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime DeleteOrganization(SimulationWorld world, string organizationId)
|
||||
internal PlayerFactionRuntime DeleteOrganization(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string organizationId)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
RemoveOrganization(player, organizationId);
|
||||
player.Assignments.RemoveAll(assignment =>
|
||||
assignment.FleetId == organizationId ||
|
||||
@@ -190,9 +196,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpdateOrganizationMembership(SimulationWorld world, string organizationId, PlayerOrganizationMembershipCommandRequest request)
|
||||
internal PlayerFactionRuntime UpdateOrganizationMembership(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string organizationId, PlayerOrganizationMembershipCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var kind = ResolveOrganizationKind(player, organizationId);
|
||||
switch (kind)
|
||||
{
|
||||
@@ -241,9 +247,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpsertDirective(SimulationWorld world, string? directiveId, PlayerDirectiveCommandRequest request)
|
||||
internal PlayerFactionRuntime UpsertDirective(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? directiveId, PlayerDirectiveCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var directive = directiveId is null
|
||||
? null
|
||||
: player.Directives.FirstOrDefault(candidate => string.Equals(candidate.Id, directiveId, StringComparison.Ordinal));
|
||||
@@ -318,9 +324,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime DeleteDirective(SimulationWorld world, string directiveId)
|
||||
internal PlayerFactionRuntime DeleteDirective(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string directiveId)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
player.Directives.RemoveAll(directive => directive.Id == directiveId);
|
||||
foreach (var assignment in player.Assignments.Where(assignment => assignment.DirectiveId == directiveId))
|
||||
{
|
||||
@@ -332,9 +338,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpsertPolicy(SimulationWorld world, string? policyId, PlayerPolicyCommandRequest request)
|
||||
internal PlayerFactionRuntime UpsertPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? policyId, PlayerPolicyCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var policy = policyId is null
|
||||
? null
|
||||
: player.Policies.FirstOrDefault(candidate => string.Equals(candidate.Id, policyId, StringComparison.Ordinal));
|
||||
@@ -403,9 +409,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpsertAutomationPolicy(SimulationWorld world, string? automationPolicyId, PlayerAutomationPolicyCommandRequest request)
|
||||
internal PlayerFactionRuntime UpsertAutomationPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? automationPolicyId, PlayerAutomationPolicyCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var policy = automationPolicyId is null
|
||||
? null
|
||||
: player.AutomationPolicies.FirstOrDefault(candidate => string.Equals(candidate.Id, automationPolicyId, StringComparison.Ordinal));
|
||||
@@ -461,9 +467,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpsertReinforcementPolicy(SimulationWorld world, string? reinforcementPolicyId, PlayerReinforcementPolicyCommandRequest request)
|
||||
internal PlayerFactionRuntime UpsertReinforcementPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? reinforcementPolicyId, PlayerReinforcementPolicyCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var policy = reinforcementPolicyId is null
|
||||
? null
|
||||
: player.ReinforcementPolicies.FirstOrDefault(candidate => string.Equals(candidate.Id, reinforcementPolicyId, StringComparison.Ordinal));
|
||||
@@ -495,9 +501,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpsertProductionProgram(SimulationWorld world, string? productionProgramId, PlayerProductionProgramCommandRequest request)
|
||||
internal PlayerFactionRuntime UpsertProductionProgram(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? productionProgramId, PlayerProductionProgramCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var program = productionProgramId is null
|
||||
? null
|
||||
: player.ProductionPrograms.FirstOrDefault(candidate => string.Equals(candidate.Id, productionProgramId, StringComparison.Ordinal));
|
||||
@@ -527,9 +533,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpsertAssignment(SimulationWorld world, string assetId, PlayerAssetAssignmentCommandRequest request)
|
||||
internal PlayerFactionRuntime UpsertAssignment(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string assetId, PlayerAssetAssignmentCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
var assignment = player.Assignments.FirstOrDefault(candidate =>
|
||||
string.Equals(candidate.AssetId, assetId, StringComparison.Ordinal) &&
|
||||
string.Equals(candidate.AssetKind, request.AssetKind, StringComparison.Ordinal));
|
||||
@@ -586,9 +592,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal PlayerFactionRuntime UpdateStrategicIntent(SimulationWorld world, PlayerStrategicIntentCommandRequest request)
|
||||
internal PlayerFactionRuntime UpdateStrategicIntent(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, PlayerStrategicIntentCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
player.StrategicIntent.StrategicPosture = request.StrategicPosture;
|
||||
player.StrategicIntent.EconomicPosture = request.EconomicPosture;
|
||||
player.StrategicIntent.MilitaryPosture = request.MilitaryPosture;
|
||||
@@ -602,9 +608,9 @@ internal sealed class PlayerFactionService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal ShipRuntime? EnqueueDirectShipOrder(SimulationWorld world, string shipId, ShipOrderCommandRequest request)
|
||||
internal ShipRuntime? EnqueueDirectShipOrder(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, ShipOrderCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
if (!player.AssetRegistry.ShipIds.Contains(shipId))
|
||||
{
|
||||
return null;
|
||||
@@ -625,6 +631,8 @@ internal sealed class PlayerFactionService
|
||||
{
|
||||
Id = $"order-{ship.Id}-{Guid.NewGuid():N}",
|
||||
Kind = request.Kind,
|
||||
SourceKind = ShipOrderSourceKind.Player,
|
||||
SourceId = playerId,
|
||||
Priority = request.Priority,
|
||||
InterruptCurrentPlan = request.InterruptCurrentPlan,
|
||||
Label = request.Label,
|
||||
@@ -643,11 +651,11 @@ internal sealed class PlayerFactionService
|
||||
KnownStationsOnly = request.KnownStationsOnly ?? false,
|
||||
});
|
||||
|
||||
AddDecision(player, "ship-order-enqueued", $"Queued {request.Kind} for {ship.Definition.Label}.", "ship", shipId);
|
||||
AddDecision(player, "ship-order-enqueued", $"Queued {request.Kind} for {ship.Definition.Name}.", "ship", shipId);
|
||||
player.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
||||
ship.ControlSourceKind = "player-order";
|
||||
ship.ControlSourceId = ship.OrderQueue
|
||||
.Where(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
.Where(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
.OrderByDescending(order => order.Priority)
|
||||
.ThenBy(order => order.CreatedAtUtc)
|
||||
.Select(order => order.Id)
|
||||
@@ -659,9 +667,9 @@ internal sealed class PlayerFactionService
|
||||
return ship;
|
||||
}
|
||||
|
||||
internal ShipRuntime? RemoveDirectShipOrder(SimulationWorld world, string shipId, string orderId)
|
||||
internal ShipRuntime? RemoveDirectShipOrder(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, string orderId)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
if (!player.AssetRegistry.ShipIds.Contains(shipId))
|
||||
{
|
||||
return null;
|
||||
@@ -676,21 +684,21 @@ internal sealed class PlayerFactionService
|
||||
var removed = ship.OrderQueue.RemoveAll(order => order.Id == orderId);
|
||||
if (removed > 0)
|
||||
{
|
||||
AddDecision(player, "ship-order-removed", $"Removed order {orderId} from {ship.Definition.Label}.", "ship", shipId);
|
||||
AddDecision(player, "ship-order-removed", $"Removed order {orderId} from {ship.Definition.Name}.", "ship", shipId);
|
||||
player.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
ship.ControlSourceKind = ship.OrderQueue.Any(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
ship.ControlSourceKind = ship.OrderQueue.Any(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
? "player-order"
|
||||
: "player-manual";
|
||||
ship.ControlSourceId = ship.OrderQueue
|
||||
.Where(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
.Where(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
.OrderByDescending(order => order.Priority)
|
||||
.ThenBy(order => order.CreatedAtUtc)
|
||||
.Select(order => order.Id)
|
||||
.FirstOrDefault();
|
||||
ship.ControlReason = ship.OrderQueue
|
||||
.Where(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
.Where(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
.OrderByDescending(order => order.Priority)
|
||||
.ThenBy(order => order.CreatedAtUtc)
|
||||
.Select(order => order.Label ?? order.Kind)
|
||||
@@ -702,9 +710,9 @@ internal sealed class PlayerFactionService
|
||||
return ship;
|
||||
}
|
||||
|
||||
internal ShipRuntime? ConfigureDirectShipBehavior(SimulationWorld world, string shipId, ShipDefaultBehaviorCommandRequest request)
|
||||
internal ShipRuntime? ConfigureDirectShipBehavior(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, ShipDefaultBehaviorCommandRequest request)
|
||||
{
|
||||
var player = EnsureDomain(world);
|
||||
var player = EnsureDomain(world, playerStateStore, playerId);
|
||||
if (!player.AssetRegistry.ShipIds.Contains(shipId))
|
||||
{
|
||||
return null;
|
||||
@@ -723,7 +731,7 @@ internal sealed class PlayerFactionService
|
||||
directive = new PlayerDirectiveRuntime
|
||||
{
|
||||
Id = directiveId,
|
||||
Label = $"Direct control {ship.Definition.Label}",
|
||||
Label = $"Direct control {ship.Definition.Name}",
|
||||
ScopeKind = "ship",
|
||||
ScopeId = shipId,
|
||||
Kind = "direct-control",
|
||||
@@ -732,7 +740,7 @@ internal sealed class PlayerFactionService
|
||||
player.Directives.Add(directive);
|
||||
}
|
||||
|
||||
directive.Label = $"Direct control {ship.Definition.Label}";
|
||||
directive.Label = $"Direct control {ship.Definition.Name}";
|
||||
directive.Kind = "direct-control";
|
||||
directive.ScopeKind = "ship";
|
||||
directive.ScopeId = shipId;
|
||||
@@ -746,7 +754,7 @@ internal sealed class PlayerFactionService
|
||||
directive.HomeStationId = request.HomeStationId;
|
||||
directive.SourceStationId = request.HomeStationId;
|
||||
directive.DestinationStationId = null;
|
||||
directive.ItemId = request.PreferredItemId;
|
||||
directive.ItemId = request.ItemId;
|
||||
directive.PreferredNodeId = request.PreferredNodeId;
|
||||
directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
|
||||
directive.PreferredModuleId = request.PreferredModuleId;
|
||||
@@ -793,7 +801,7 @@ internal sealed class PlayerFactionService
|
||||
ship.ControlSourceKind = "player-directive";
|
||||
ship.ControlSourceId = directive.Id;
|
||||
ship.ControlReason = directive.Label;
|
||||
AddDecision(player, "ship-behavior-configured", $"Configured {request.Kind} for {ship.Definition.Label}.", "ship", shipId);
|
||||
AddDecision(player, "ship-behavior-configured", $"Configured {request.Kind} for {ship.Definition.Name}.", "ship", shipId);
|
||||
player.UpdatedAtUtc = directive.UpdatedAtUtc;
|
||||
ship.NeedsReplan = true;
|
||||
ship.LastReplanReason = "player-behavior-configured";
|
||||
@@ -826,7 +834,7 @@ internal sealed class PlayerFactionService
|
||||
{
|
||||
Id = "player-core-automation",
|
||||
Label = "Core Automation",
|
||||
BehaviorKind = "idle",
|
||||
BehaviorKind = Idle,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1035,7 +1043,7 @@ internal sealed class PlayerFactionService
|
||||
var changed = ApplyDirectiveToShip(commander, ship, directive, automation, assignment);
|
||||
if (changed && directive is not null)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "player-directive", $"{ship.Definition.Label} aligned to player directive {directive.Label}.", DateTimeOffset.UtcNow, "player", "universe", ship.Id));
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "player-directive", $"{ship.Definition.Name} aligned to player directive {directive.Label}.", DateTimeOffset.UtcNow, "player", "universe", ship.Id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1246,13 +1254,13 @@ internal sealed class PlayerFactionService
|
||||
? "player-directive"
|
||||
: automation is not null
|
||||
? "player-automation"
|
||||
: ship.OrderQueue.Any(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
: ship.OrderQueue.Any(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
? "player-order"
|
||||
: "player-manual";
|
||||
var desiredControlSourceId = directive?.Id
|
||||
?? automation?.Id
|
||||
?? ship.OrderQueue
|
||||
.Where(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
.Where(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
.OrderByDescending(order => order.Priority)
|
||||
.ThenBy(order => order.CreatedAtUtc)
|
||||
.Select(order => order.Id)
|
||||
@@ -1260,7 +1268,7 @@ internal sealed class PlayerFactionService
|
||||
var desiredControlReason = directive?.Label
|
||||
?? automation?.Label
|
||||
?? ship.OrderQueue
|
||||
.Where(order => !order.Id.StartsWith("ai-order-", StringComparison.Ordinal))
|
||||
.Where(order => order.SourceKind == ShipOrderSourceKind.Player)
|
||||
.OrderByDescending(order => order.Priority)
|
||||
.ThenBy(order => order.CreatedAtUtc)
|
||||
.Select(order => order.Label ?? order.Kind)
|
||||
@@ -1342,7 +1350,7 @@ internal sealed class PlayerFactionService
|
||||
HomeStationId = directive?.HomeStationId ?? ship.DefaultBehavior.HomeStationId,
|
||||
AreaSystemId = directive?.TargetSystemId ?? directive?.HomeSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId,
|
||||
TargetEntityId = directive?.TargetEntityId,
|
||||
PreferredItemId = directive?.ItemId ?? automation?.PreferredItemId ?? ship.DefaultBehavior.PreferredItemId,
|
||||
ItemId = directive?.ItemId ?? automation?.PreferredItemId ?? ship.DefaultBehavior.ItemId,
|
||||
PreferredNodeId = directive?.PreferredNodeId ?? ship.DefaultBehavior.PreferredNodeId,
|
||||
PreferredConstructionSiteId = directive?.PreferredConstructionSiteId ?? ship.DefaultBehavior.PreferredConstructionSiteId,
|
||||
PreferredModuleId = directive?.PreferredModuleId ?? ship.DefaultBehavior.PreferredModuleId,
|
||||
@@ -1375,6 +1383,8 @@ internal sealed class PlayerFactionService
|
||||
{
|
||||
Id = aiOrderId!,
|
||||
Kind = directive.StagingOrderKind!,
|
||||
SourceKind = ShipOrderSourceKind.Player,
|
||||
SourceId = directive.Id,
|
||||
Priority = Math.Max(0, directive.Priority),
|
||||
InterruptCurrentPlan = true,
|
||||
Label = directive.Label,
|
||||
@@ -1447,7 +1457,7 @@ internal sealed class PlayerFactionService
|
||||
target.HomeStationId = source.HomeStationId;
|
||||
target.AreaSystemId = source.AreaSystemId;
|
||||
target.TargetEntityId = source.TargetEntityId;
|
||||
target.PreferredItemId = source.PreferredItemId;
|
||||
target.ItemId = source.ItemId;
|
||||
target.PreferredNodeId = source.PreferredNodeId;
|
||||
target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
|
||||
target.PreferredModuleId = source.PreferredModuleId;
|
||||
@@ -1468,7 +1478,7 @@ internal sealed class PlayerFactionService
|
||||
&& string.Equals(left.HomeStationId, right.HomeStationId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.PreferredItemId, right.PreferredItemId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.PreferredNodeId, right.PreferredNodeId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
|
||||
&& string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
|
||||
@@ -1501,6 +1511,8 @@ internal sealed class PlayerFactionService
|
||||
private static bool ShipOrdersEqual(ShipOrderRuntime left, ShipOrderRuntime right) =>
|
||||
string.Equals(left.Id, right.Id, StringComparison.Ordinal)
|
||||
&& string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
|
||||
&& left.SourceKind == right.SourceKind
|
||||
&& string.Equals(left.SourceId, right.SourceId, StringComparison.Ordinal)
|
||||
&& left.Priority == right.Priority
|
||||
&& left.InterruptCurrentPlan == right.InterruptCurrentPlan
|
||||
&& string.Equals(left.Label, right.Label, StringComparison.Ordinal)
|
||||
@@ -1716,7 +1728,7 @@ internal sealed class PlayerFactionService
|
||||
{
|
||||
program.CurrentCount = world.Ships.Count(ship =>
|
||||
ship.FactionId == player.SovereignFactionId &&
|
||||
string.Equals(ship.Definition.Kind, program.TargetShipKind, StringComparison.Ordinal));
|
||||
string.Equals(GetShipCategory(ship.Definition), program.TargetShipKind, StringComparison.Ordinal));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2113,7 +2125,7 @@ internal sealed class PlayerFactionService
|
||||
{
|
||||
var available = world.Ships.Count(ship =>
|
||||
ship.FactionId == player.SovereignFactionId &&
|
||||
string.Equals(ship.Definition.Kind, policy.ShipKind, StringComparison.Ordinal));
|
||||
string.Equals(GetShipCategory(ship.Definition), policy.ShipKind, StringComparison.Ordinal));
|
||||
if (available < policy.DesiredAssetCount)
|
||||
{
|
||||
player.Alerts.Add(new PlayerAlertRuntime
|
||||
|
||||
26
apps/backend/PlayerFaction/Simulation/PlayerStateStore.cs
Normal file
26
apps/backend/PlayerFaction/Simulation/PlayerStateStore.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace SpaceGame.Api.PlayerFaction.Simulation;
|
||||
|
||||
public sealed class PlayerStateStore : IPlayerStateStore
|
||||
{
|
||||
private readonly Dictionary<string, PlayerFactionRuntime> _playerFactions = new(StringComparer.Ordinal);
|
||||
|
||||
public bool TryGetPlayerFaction(string playerId, out PlayerFactionRuntime playerFaction) =>
|
||||
_playerFactions.TryGetValue(playerId, out playerFaction!);
|
||||
|
||||
public PlayerFactionRuntime GetOrAddPlayerFaction(string playerId, Func<PlayerFactionRuntime> factory)
|
||||
{
|
||||
if (_playerFactions.TryGetValue(playerId, out var existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
var created = factory();
|
||||
_playerFactions[playerId] = created;
|
||||
return created;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<PlayerFactionRuntime> GetPlayerFactions() =>
|
||||
_playerFactions.Values.ToList();
|
||||
|
||||
public void Clear() => _playerFactions.Clear();
|
||||
}
|
||||
Reference in New Issue
Block a user