|
|
|
|
@@ -20,14 +20,12 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
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.");
|
|
|
|
|
|
|
|
|
|
var player = playerStateStore.GetOrAddPlayerFaction(playerId, () => new PlayerFactionRuntime
|
|
|
|
|
{
|
|
|
|
|
Id = PlayerFactionDomainId,
|
|
|
|
|
Label = $"{sovereignFaction.Label} Command",
|
|
|
|
|
SovereignFactionId = sovereignFaction.Id,
|
|
|
|
|
Label = "Pending Pilot",
|
|
|
|
|
SovereignFactionId = string.Empty,
|
|
|
|
|
RequiresOnboarding = true,
|
|
|
|
|
CreatedAtUtc = world.GeneratedAtUtc,
|
|
|
|
|
UpdatedAtUtc = world.GeneratedAtUtc,
|
|
|
|
|
});
|
|
|
|
|
@@ -37,6 +35,58 @@ internal sealed class PlayerFactionService
|
|
|
|
|
return player;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime CompleteOnboarding(
|
|
|
|
|
SimulationWorld world,
|
|
|
|
|
IPlayerStateStore playerStateStore,
|
|
|
|
|
string playerId,
|
|
|
|
|
CompletePlayerOnboardingRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
if (!player.RequiresOnboarding)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Player onboarding has already been completed.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var personaName = request.Name.Trim();
|
|
|
|
|
if (personaName.Length < 2)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Player name must contain at least 2 characters.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (personaName.Length > 48)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Player name must contain at most 48 characters.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ownedFactionId = BuildOwnedFactionId(playerId);
|
|
|
|
|
if (world.Factions.Any(faction => string.Equals(faction.Id, ownedFactionId, StringComparison.Ordinal)))
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException($"Player faction '{ownedFactionId}' already exists in the current world.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
player.Label = personaName;
|
|
|
|
|
player.PersonaName = personaName;
|
|
|
|
|
player.RaceId = request.RaceId.Trim();
|
|
|
|
|
player.SovereignFactionId = ownedFactionId;
|
|
|
|
|
player.RequiresOnboarding = false;
|
|
|
|
|
player.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
|
|
|
|
return player;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime EnsureInitializedDomain(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
if (player.RequiresOnboarding || string.IsNullOrWhiteSpace(player.SovereignFactionId))
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Player onboarding must be completed before issuing gameplay commands.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return player;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static string BuildOwnedFactionId(string playerId) =>
|
|
|
|
|
$"player-{playerId.Replace("-", string.Empty, StringComparison.Ordinal).ToLowerInvariant()}";
|
|
|
|
|
|
|
|
|
|
internal void Update(SimulationWorld world, IPlayerStateStore playerStateStore, float _deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
|
|
|
{
|
|
|
|
|
if (playerStateStore.GetPlayerFactions().Count == 0)
|
|
|
|
|
@@ -63,7 +113,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime CreateOrganization(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, PlayerOrganizationCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var id = CreateDomainId(request.Kind, request.Label, ExistingOrganizationIds(player));
|
|
|
|
|
var nowUtc = DateTimeOffset.UtcNow;
|
|
|
|
|
|
|
|
|
|
@@ -180,7 +230,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime DeleteOrganization(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string organizationId)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
RemoveOrganization(player, organizationId);
|
|
|
|
|
player.Assignments.RemoveAll(assignment =>
|
|
|
|
|
assignment.FleetId == organizationId ||
|
|
|
|
|
@@ -198,7 +248,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpdateOrganizationMembership(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string organizationId, PlayerOrganizationMembershipCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var kind = ResolveOrganizationKind(player, organizationId);
|
|
|
|
|
switch (kind)
|
|
|
|
|
{
|
|
|
|
|
@@ -249,7 +299,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpsertDirective(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? directiveId, PlayerDirectiveCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var directive = directiveId is null
|
|
|
|
|
? null
|
|
|
|
|
: player.Directives.FirstOrDefault(candidate => string.Equals(candidate.Id, directiveId, StringComparison.Ordinal));
|
|
|
|
|
@@ -326,7 +376,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime DeleteDirective(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string directiveId)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
player.Directives.RemoveAll(directive => directive.Id == directiveId);
|
|
|
|
|
foreach (var assignment in player.Assignments.Where(assignment => assignment.DirectiveId == directiveId))
|
|
|
|
|
{
|
|
|
|
|
@@ -340,7 +390,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpsertPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? policyId, PlayerPolicyCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var policy = policyId is null
|
|
|
|
|
? null
|
|
|
|
|
: player.Policies.FirstOrDefault(candidate => string.Equals(candidate.Id, policyId, StringComparison.Ordinal));
|
|
|
|
|
@@ -411,7 +461,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpsertAutomationPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? automationPolicyId, PlayerAutomationPolicyCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var policy = automationPolicyId is null
|
|
|
|
|
? null
|
|
|
|
|
: player.AutomationPolicies.FirstOrDefault(candidate => string.Equals(candidate.Id, automationPolicyId, StringComparison.Ordinal));
|
|
|
|
|
@@ -469,7 +519,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpsertReinforcementPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? reinforcementPolicyId, PlayerReinforcementPolicyCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var policy = reinforcementPolicyId is null
|
|
|
|
|
? null
|
|
|
|
|
: player.ReinforcementPolicies.FirstOrDefault(candidate => string.Equals(candidate.Id, reinforcementPolicyId, StringComparison.Ordinal));
|
|
|
|
|
@@ -503,7 +553,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpsertProductionProgram(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? productionProgramId, PlayerProductionProgramCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var program = productionProgramId is null
|
|
|
|
|
? null
|
|
|
|
|
: player.ProductionPrograms.FirstOrDefault(candidate => string.Equals(candidate.Id, productionProgramId, StringComparison.Ordinal));
|
|
|
|
|
@@ -535,7 +585,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpsertAssignment(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string assetId, PlayerAssetAssignmentCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
var assignment = player.Assignments.FirstOrDefault(candidate =>
|
|
|
|
|
string.Equals(candidate.AssetId, assetId, StringComparison.Ordinal) &&
|
|
|
|
|
string.Equals(candidate.AssetKind, request.AssetKind, StringComparison.Ordinal));
|
|
|
|
|
@@ -594,7 +644,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal PlayerFactionRuntime UpdateStrategicIntent(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, PlayerStrategicIntentCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
player.StrategicIntent.StrategicPosture = request.StrategicPosture;
|
|
|
|
|
player.StrategicIntent.EconomicPosture = request.EconomicPosture;
|
|
|
|
|
player.StrategicIntent.MilitaryPosture = request.MilitaryPosture;
|
|
|
|
|
@@ -610,7 +660,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal ShipRuntime? EnqueueDirectShipOrder(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, ShipOrderCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
if (!player.AssetRegistry.ShipIds.Contains(shipId))
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
@@ -669,7 +719,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal ShipRuntime? RemoveDirectShipOrder(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, string orderId)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
if (!player.AssetRegistry.ShipIds.Contains(shipId))
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
@@ -712,7 +762,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
internal ShipRuntime? ConfigureDirectShipBehavior(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, ShipDefaultBehaviorCommandRequest request)
|
|
|
|
|
{
|
|
|
|
|
var player = EnsureDomain(world, playerStateStore, playerId);
|
|
|
|
|
var player = EnsureInitializedDomain(world, playerStateStore, playerId);
|
|
|
|
|
if (!player.AssetRegistry.ShipIds.Contains(shipId))
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
@@ -852,6 +902,24 @@ internal sealed class PlayerFactionService
|
|
|
|
|
|
|
|
|
|
private static void SyncRegistry(SimulationWorld world, PlayerFactionRuntime player)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(player.SovereignFactionId))
|
|
|
|
|
{
|
|
|
|
|
SyncSet(player.AssetRegistry.ShipIds, []);
|
|
|
|
|
SyncSet(player.AssetRegistry.StationIds, []);
|
|
|
|
|
SyncSet(player.AssetRegistry.CommanderIds, []);
|
|
|
|
|
SyncSet(player.AssetRegistry.ClaimIds, []);
|
|
|
|
|
SyncSet(player.AssetRegistry.ConstructionSiteIds, []);
|
|
|
|
|
SyncSet(player.AssetRegistry.PolicySetIds, player.Policies.Where(entry => entry.PolicySetId is not null).Select(entry => entry.PolicySetId!));
|
|
|
|
|
SyncSet(player.AssetRegistry.MarketOrderIds, []);
|
|
|
|
|
SyncSet(player.AssetRegistry.FleetIds, player.Fleets.Select(fleet => fleet.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.TaskForceIds, player.TaskForces.Select(taskForce => taskForce.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.StationGroupIds, player.StationGroups.Select(group => group.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.EconomicRegionIds, player.EconomicRegions.Select(region => region.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.FrontIds, player.Fronts.Select(front => front.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.ReserveIds, player.Reserves.Select(reserve => reserve.Id));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SyncSet(player.AssetRegistry.ShipIds, world.Ships.Where(ship => ship.FactionId == player.SovereignFactionId).Select(ship => ship.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.StationIds, world.Stations.Where(station => station.FactionId == player.SovereignFactionId).Select(station => station.Id));
|
|
|
|
|
SyncSet(player.AssetRegistry.CommanderIds, world.Commanders.Where(commander => commander.FactionId == player.SovereignFactionId).Select(commander => commander.Id));
|
|
|
|
|
@@ -1224,8 +1292,7 @@ internal sealed class PlayerFactionService
|
|
|
|
|
return player.AutomationPolicies.FirstOrDefault(policy => policy.Id == automationId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SelectScopedAutomationPolicy(player, assignment, assetKind, assetId)
|
|
|
|
|
?? player.AutomationPolicies.FirstOrDefault(policy => policy.Id == "player-core-automation");
|
|
|
|
|
return SelectScopedAutomationPolicy(player, assignment, assetKind, assetId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static PlayerFactionPolicyRuntime? ResolvePolicy(PlayerFactionRuntime player, PlayerAssignmentRuntime? assignment, PlayerDirectiveRuntime? directive, string assetKind, string assetId)
|
|
|
|
|
|