Allow users to load and save builds from Focus View (Closes #1018) (#1021)

This commit is contained in:
2025-06-27 13:36:28 +02:00
parent 06288ec2a2
commit 1b7654f70f
22 changed files with 998 additions and 222 deletions
@@ -99,4 +99,16 @@ public sealed class MainPlayerController(
var titleInfo = await this.mainPlayerService.GetTitleInfo(cancellationToken);
return titleInfo is not null ? Results.Ok(titleInfo) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("build-context")]
[EndpointName("GetMainPlayerBuildContext")]
[EndpointSummary("Get the current build context")]
[EndpointDescription("Get the current build context. Returns a json serialized MainPlayerBuildContext object")]
[ProducesResponseType<BuildEntry>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerBuildContext(CancellationToken cancellationToken)
{
var context = await this.mainPlayerService.GetMainPlayerBuildContext(cancellationToken);
return context is not null ? Results.Ok(context) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
<InteropExports>true</InteropExports>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>
<Version>0.9.9.73</Version>
<Version>0.9.9.75</Version>
<PublishAot>true</PublishAot>
<SelfContained>true</SelfContained>
@@ -35,6 +35,7 @@ namespace Daybreak.API.Serialization;
[JsonSerializable(typeof(InstanceInfo))]
[JsonSerializable(typeof(TitleInfo))]
[JsonSerializable(typeof(LoginInfo))]
[JsonSerializable(typeof(MainPlayerBuildContext))]
public partial class ApiJsonSerializerContext : JsonSerializerContext
{
}
@@ -12,6 +12,7 @@ using System.Collections.Concurrent;
using System.Core.Extensions;
using System.Extensions;
using System.Extensions.Core;
using System.Logging;
using System.Runtime.CompilerServices;
using ZLinq;
using InstanceType = Daybreak.API.Interop.GuildWars.InstanceType;
@@ -428,6 +429,52 @@ public sealed class MainPlayerService : IDisposable
}, cancellationToken);
}
public Task<MainPlayerBuildContext?> GetMainPlayerBuildContext(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull ||
gameContext.Pointer->AccountContext is null ||
gameContext.Pointer->WorldContext is null)
{
this.logger.LogError("Game context is not initialized");
return default;
}
var playerAgentId = this.agentContextService.GetPlayerAgentId();
if (playerAgentId is 0x0)
{
scopedLogger.LogError("Failed to get player agent id");
return default;
}
var agentProfession = gameContext.Pointer->WorldContext->Professions.AsValueEnumerable().FirstOrDefault(p => p.AgentId == playerAgentId);
if (agentProfession.AgentId != playerAgentId)
{
scopedLogger.LogError("Failed to find agent profession for player agent id {agentId}", playerAgentId);
return default;
}
return new MainPlayerBuildContext(
PrimaryProfessionId: (uint)agentProfession.CurrentPrimary,
UnlockedProfessions: agentProfession.UnlockedProfessionsFlags,
UnlockedAccountSkills: gameContext.Pointer->AccountContext->UnlockedAccountSkills.AsValueEnumerable().ToArray(),
UnlockedCharacterSkills: gameContext.Pointer->WorldContext->UnlockedCharacterSkills.AsValueEnumerable().ToArray());
}
}, cancellationToken);
}
public CallbackRegistration RegisterMainStateConsumer(TimeSpan frequency, Action<ReadOnlySpan<byte>> onUpdate)
{
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(frequency, TimeSpan.Zero);