Add player onboarding and tactical viewer updates
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user