mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-25 08:22:07 +00:00
* Improve GWCA bindings generator based on headers * Move all structs to gwca * Progress * Complete parser work * More generator adjustments * GWCA fixes * GWCA improvements * GWCA fixes * GWCA fixes * GWCA fixes * GWCA fixes * Fix API usage of GWCA * Update GWCA --------- Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
660 lines
25 KiB
C#
660 lines
25 KiB
C#
using Daybreak.Configuration.Options;
|
|
using Daybreak.Shared.Models.Api;
|
|
using Daybreak.Shared.Models.Builds;
|
|
using Daybreak.Shared.Models.FocusView;
|
|
using Daybreak.Shared.Models.Guildwars;
|
|
using Daybreak.Shared.Services.Api;
|
|
using Daybreak.Shared.Services.BuildTemplates;
|
|
using Daybreak.Shared.Services.Experience;
|
|
using Daybreak.Shared.Services.LaunchConfigurations;
|
|
using Daybreak.Shared.Services.Notifications;
|
|
using Daybreak.Shared.Services.Options;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Core.Extensions;
|
|
using System.Diagnostics;
|
|
using System.Extensions;
|
|
using System.Extensions.Core;
|
|
using TrailBlazr.Services;
|
|
using TrailBlazr.ViewModels;
|
|
|
|
namespace Daybreak.Views;
|
|
|
|
|
|
public sealed class FocusViewModel(
|
|
IOptionsProvider optionsProvider,
|
|
IBuildTemplateManager buildTemplateManager,
|
|
IViewManager viewManager,
|
|
INotificationService notificationService,
|
|
ILaunchConfigurationService launchConfigurationService,
|
|
IDaybreakApiService daybreakApiService,
|
|
IExperienceCalculator experienceCalculator,
|
|
IOptionsMonitor<FocusViewOptions> options,
|
|
ILogger<FocusView> logger)
|
|
: ViewModelBase<FocusViewModel, FocusView>
|
|
{
|
|
private const int MaxRetryAttempts = 5;
|
|
|
|
private static readonly TimeSpan GameInfoFrequency = TimeSpan.FromSeconds(1);
|
|
private static readonly TimeSpan GameInfoTimeout = TimeSpan.FromSeconds(5);
|
|
private static readonly TimeSpan BuildsCheckFrequency = TimeSpan.FromSeconds(30);
|
|
|
|
private readonly IOptionsProvider optionsProvider = optionsProvider.ThrowIfNull();
|
|
private readonly IBuildTemplateManager buildTemplateManager = buildTemplateManager.ThrowIfNull();
|
|
private readonly IViewManager viewManager = viewManager.ThrowIfNull();
|
|
private readonly INotificationService notificationService = notificationService.ThrowIfNull();
|
|
private readonly ILaunchConfigurationService launchConfigurationService = launchConfigurationService.ThrowIfNull();
|
|
private readonly IDaybreakApiService daybreakApiService = daybreakApiService.ThrowIfNull();
|
|
private readonly IExperienceCalculator experienceCalculator = experienceCalculator.ThrowIfNull();
|
|
private readonly IOptionsMonitor<FocusViewOptions> options = options.ThrowIfNull();
|
|
private readonly ILogger<FocusView> logger = logger.ThrowIfNull();
|
|
|
|
private ScopedApiContext? apiContext;
|
|
private Process? process;
|
|
private CancellationTokenSource? cancellationSource = default;
|
|
private DateTime lastBuildCheck = DateTime.MinValue;
|
|
|
|
public CharacterComponentContext? CharacterComponentContext { get; private set; }
|
|
public CurrentMapComponentContext? CurrentMapComponentContext { get; private set; }
|
|
public PlayerResourcesComponentContext? PlayerResourcesComponentContext { get; private set; }
|
|
public QuestLogComponentContext? QuestLogComponentContext { get; private set; }
|
|
public TitleInformationComponentContext? TitleInformationComponentContext { get; private set; }
|
|
public VanquishComponentContext? VanquishComponentContext { get; private set; }
|
|
public BuildComponentContext? BuildComponentContext { get; private set; }
|
|
public string? BrowserSource { get; private set; } = "https://wiki.guildwars.com/wiki/Main_Page";
|
|
|
|
public override async ValueTask ParametersSet(FocusView view, CancellationToken cancellationToken)
|
|
{
|
|
var scopedLogger = this.logger.CreateScopedLogger();
|
|
var launchConfigId = view.ConfigurationId;
|
|
var daybreakLaunchConfig = this.launchConfigurationService.GetLaunchConfigurations()
|
|
.FirstOrDefault(x => x.Identifier == launchConfigId);
|
|
if (daybreakLaunchConfig is null)
|
|
{
|
|
scopedLogger.LogError("Launch configuration with ID {configId} not found", launchConfigId);
|
|
this.notificationService.NotifyError(
|
|
title: "Focus View Error",
|
|
description: $"Could not find launch configuration by id {launchConfigId}");
|
|
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
if (!int.TryParse(view.ProcessId, out var processId))
|
|
{
|
|
scopedLogger.LogError("Process id is invalid {processId}", view.ProcessId);
|
|
this.notificationService.NotifyError(
|
|
title: "Focus View Error",
|
|
description: $"Could not find GuildWars process by id {processId}");
|
|
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
this.process = Process.GetProcessById(processId);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
scopedLogger.LogError(ex, "Encountered exception when fetching process with id {processId}", processId);
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
var apiContext = await this.daybreakApiService.GetDaybreakApiContext(this.process, cancellationToken);
|
|
if (apiContext is null)
|
|
{
|
|
scopedLogger.LogError("Could not attach to GuildWars process");
|
|
this.notificationService.NotifyError(
|
|
title: "Focus View Error",
|
|
description: $"Could not attach to GuildWars process");
|
|
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
this.apiContext = apiContext;
|
|
this.Initialize();
|
|
}
|
|
|
|
public async void OnCharacterChanged(CharacterSelectComponentEntry selectedCharacter)
|
|
{
|
|
if (this.apiContext is null ||
|
|
this.cancellationSource is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.SetupDefaultValues();
|
|
await this.apiContext.SwitchCharacter(selectedCharacter.CharacterName, this.cancellationSource.Token);
|
|
}
|
|
|
|
public void OnExperienceDisplayClicked()
|
|
{
|
|
var options = this.options.CurrentValue;
|
|
options.ExperienceDisplay = CycleValue(options.ExperienceDisplay);
|
|
this.optionsProvider.SaveOption(options);
|
|
if (this.CharacterComponentContext is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.CharacterComponentContext = new CharacterComponentContext
|
|
{
|
|
Characters = this.CharacterComponentContext.Characters,
|
|
CurrentCharacter = this.CharacterComponentContext.CurrentCharacter,
|
|
CurrentTotalExperience = this.CharacterComponentContext.CurrentTotalExperience,
|
|
ExperienceForCurrentLevel = this.CharacterComponentContext.ExperienceForCurrentLevel,
|
|
ExperienceForNextLevel = this.CharacterComponentContext.ExperienceForNextLevel,
|
|
NextExperienceThreshold = this.CharacterComponentContext.NextExperienceThreshold,
|
|
TotalExperienceForNextLevel = this.CharacterComponentContext.TotalExperienceForNextLevel,
|
|
ExperienceDisplay = options.ExperienceDisplay
|
|
};
|
|
this.RefreshView();
|
|
}
|
|
|
|
public void OnVanquishingDisplayClicked()
|
|
{
|
|
var options = this.options.CurrentValue;
|
|
options.VanquishingDisplay = CycleValue(options.VanquishingDisplay);
|
|
this.optionsProvider.SaveOption(options);
|
|
if (this.VanquishComponentContext is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.VanquishComponentContext = new VanquishComponentContext
|
|
{
|
|
Display = options.VanquishingDisplay,
|
|
FoesKilled = this.VanquishComponentContext.FoesKilled,
|
|
FoesToKill = this.VanquishComponentContext.FoesToKill,
|
|
HardMode = this.VanquishComponentContext.HardMode,
|
|
Vanquishing = this.VanquishComponentContext.Vanquishing
|
|
};
|
|
this.RefreshView();
|
|
}
|
|
|
|
public void OnCurrentMapClicked()
|
|
{
|
|
if (this.CurrentMapComponentContext?.CurrentMap is not Map map)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.BrowserSource = map.WikiUrl;
|
|
}
|
|
|
|
public void OnTitleClicked()
|
|
{
|
|
if (this.TitleInformationComponentContext?.Title is not Title title)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.BrowserSource = title.WikiUrl;
|
|
}
|
|
|
|
public void OnKurzickBarClicked()
|
|
{
|
|
}
|
|
|
|
public void OnLuxonBarClicked()
|
|
{
|
|
}
|
|
|
|
public void OnBalthazarBarClicked()
|
|
{
|
|
}
|
|
|
|
public void OnImperialBarClicked()
|
|
{
|
|
}
|
|
|
|
public void OnQuestClicked(QuestMetadata questMetadata)
|
|
{
|
|
if (questMetadata.Quest is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.BrowserSource = questMetadata.Quest.WikiUrl;
|
|
}
|
|
|
|
public async void OnBuildClicked(IBuildEntry buildEntry)
|
|
{
|
|
if (this.apiContext is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (buildEntry is SingleBuildEntry singleBuild)
|
|
{
|
|
var code = this.buildTemplateManager.EncodeTemplate(singleBuild);
|
|
await this.apiContext.PostMainPlayerBuild(code, this.cancellationSource?.Token ?? CancellationToken.None);
|
|
}
|
|
else if (buildEntry is TeamBuildEntry teamBuild)
|
|
{
|
|
var partyLoadout = this.buildTemplateManager.ConvertToPartyLoadout(teamBuild);
|
|
await this.apiContext.PostPartyLoadout(partyLoadout, this.cancellationSource?.Token ?? CancellationToken.None);
|
|
}
|
|
}
|
|
|
|
public async void OnPlayerSingleBuildClicked()
|
|
{
|
|
if (this.apiContext is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var entry = await this.apiContext.GetMainPlayerBuild(this.cancellationSource?.Token ?? CancellationToken.None);
|
|
if (entry is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var singleBuildEntry = this.buildTemplateManager.CreateSingleBuild(entry);
|
|
this.viewManager.ShowView<BuildRoutingView>((nameof(BuildRoutingView.BuildName), Uri.EscapeDataString(singleBuildEntry.Name ?? throw new InvalidOperationException())));
|
|
}
|
|
|
|
public async void OnPlayerTeamBuildClicked()
|
|
{
|
|
if (this.apiContext is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var loadout = await this.apiContext.GetPartyLoadout(this.cancellationSource?.Token ?? CancellationToken.None);
|
|
if (loadout is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var teamBuildEntry = this.buildTemplateManager.CreateTeamBuild(loadout);
|
|
this.viewManager.ShowView<BuildRoutingView>((nameof(BuildRoutingView.BuildName), Uri.EscapeDataString(teamBuildEntry.Name ?? throw new InvalidOperationException())));
|
|
}
|
|
|
|
private void ViewManager_ShowViewRequested(object? _, TrailBlazr.Models.ViewRequest e)
|
|
{
|
|
if (e.ViewModelType == typeof(FocusViewModel))
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.Cleanup();
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
this.viewManager.ShowViewRequested += this.ViewManager_ShowViewRequested;
|
|
this.cancellationSource?.Cancel();
|
|
this.cancellationSource?.Dispose();
|
|
this.cancellationSource = new CancellationTokenSource();
|
|
var ct = this.cancellationSource.Token;
|
|
this.SetupDefaultValues();
|
|
Task.Factory.StartNew(() => this.PeriodicallyFetchGameInformation(ct), ct, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
|
}
|
|
|
|
private void Cleanup()
|
|
{
|
|
this.viewManager.ShowViewRequested -= this.ViewManager_ShowViewRequested;
|
|
this.cancellationSource?.Cancel();
|
|
this.cancellationSource?.Dispose();
|
|
this.cancellationSource = default;
|
|
}
|
|
|
|
private void SetupDefaultValues()
|
|
{
|
|
this.CharacterComponentContext = default;
|
|
this.CurrentMapComponentContext = default;
|
|
this.PlayerResourcesComponentContext = default;
|
|
this.QuestLogComponentContext = default;
|
|
this.TitleInformationComponentContext = default;
|
|
this.VanquishComponentContext = default;
|
|
this.BuildComponentContext = default;
|
|
this.RefreshView();
|
|
}
|
|
|
|
private async Task PeriodicallyFetchGameInformation(CancellationToken cancellationToken)
|
|
{
|
|
var scopedLogger = this.logger.CreateScopedLogger();
|
|
var retryCount = 0;
|
|
while (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
scopedLogger.LogInformation("Cancellation requested, stopping game information fetch loop");
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
if (this.apiContext is null || this.process is null || this.process.HasExited)
|
|
{
|
|
scopedLogger.LogInformation("Process has exited or API context is null, stopping game information fetch loop");
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!await this.FetchGameInformation(cancellationToken))
|
|
{
|
|
retryCount++;
|
|
}
|
|
else
|
|
{
|
|
retryCount = 0;
|
|
await this.RefreshViewAsync();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
scopedLogger.LogError(ex, "Error occurred while fetching game information");
|
|
retryCount++;
|
|
}
|
|
|
|
|
|
if (retryCount >= MaxRetryAttempts)
|
|
{
|
|
this.viewManager.ShowView<LaunchView>();
|
|
return;
|
|
}
|
|
|
|
await Task.Delay(GameInfoFrequency, cancellationToken);
|
|
}
|
|
}
|
|
|
|
private async ValueTask<bool> FetchGameInformation(CancellationToken cancellationToken)
|
|
{
|
|
var scopedLogger = this.logger.CreateScopedLogger();
|
|
if (this.apiContext is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var timeoutCts = new CancellationTokenSource(GameInfoTimeout);
|
|
var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
|
var timeoutToken = timeoutTokenSource.Token;
|
|
|
|
var characterListTask = this.apiContext.GetCharacters(timeoutToken);
|
|
var mainPlayerInstanceTask = this.apiContext.GetMainPlayerInstanceInfo(timeoutToken);
|
|
var mainPlayerStateTask = this.apiContext.GetMainPlayerState(timeoutToken);
|
|
var titleInfoTask = this.apiContext.GetTitleInfo(timeoutToken);
|
|
var questLogTask = this.apiContext.GetMainPlayerQuestLog(timeoutToken);
|
|
var mainPlayerBuildContextTask = this.apiContext.GetMainPlayerBuildContext(timeoutToken);
|
|
try
|
|
{
|
|
await Task.WhenAny([
|
|
characterListTask,
|
|
mainPlayerInstanceTask,
|
|
mainPlayerStateTask,
|
|
titleInfoTask,
|
|
questLogTask,
|
|
mainPlayerBuildContextTask
|
|
]);
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
scopedLogger.LogWarning("Timeout occurred while fetching game information");
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
scopedLogger.LogError(ex, "Error fetching game information");
|
|
return false;
|
|
}
|
|
|
|
var characterList = await characterListTask;
|
|
var mainPlayerInstance = await mainPlayerInstanceTask;
|
|
var mainPlayerState = await mainPlayerStateTask;
|
|
var titleInformation = await titleInfoTask;
|
|
var questLog = await questLogTask;
|
|
var mainPlayerBuildContext = await mainPlayerBuildContextTask;
|
|
|
|
if (mainPlayerInstance is null ||
|
|
mainPlayerInstance.Type is Shared.Models.Api.InstanceType.Loading)
|
|
{
|
|
this.SetupDefaultValues();
|
|
return true;
|
|
}
|
|
|
|
this.CharacterComponentContext = this.ParseCharacterComponentContext(characterList, mainPlayerState);
|
|
this.CurrentMapComponentContext = ParseCurrentMapComponentContext(mainPlayerInstance);
|
|
this.PlayerResourcesComponentContext = ParsePlayerResourcesContext(mainPlayerState);
|
|
this.QuestLogComponentContext = ParseQuestLogComponentContext(questLog);
|
|
this.TitleInformationComponentContext = ParseTitleInformationComponentContext(titleInformation);
|
|
this.VanquishComponentContext = this.ParseVanquishComponentContext(mainPlayerInstance);
|
|
this.BuildComponentContext = await this.ParseBuildComponentContext(mainPlayerInstance, mainPlayerBuildContext, cancellationToken);
|
|
return true;
|
|
}
|
|
|
|
private CharacterComponentContext? ParseCharacterComponentContext(CharacterSelectInformation? characterSelectInformation, MainPlayerState? mainPlayerState)
|
|
{
|
|
if (characterSelectInformation is null ||
|
|
characterSelectInformation.CharacterNames is null ||
|
|
characterSelectInformation.CurrentCharacter is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var parsedCharacters = characterSelectInformation.CharacterNames.Select(entry =>
|
|
{
|
|
if (!Profession.TryParse((int)entry.Primary, out var primaryProfession))
|
|
{
|
|
primaryProfession = Profession.None;
|
|
}
|
|
|
|
if (!Profession.TryParse((int)entry.Secondary, out var secondaryProfession))
|
|
{
|
|
secondaryProfession = Profession.None;
|
|
}
|
|
|
|
return new CharacterSelectComponentEntry
|
|
{
|
|
CharacterName = entry.Name,
|
|
DisplayName = $"{primaryProfession.Alias}{(secondaryProfession != Profession.None && secondaryProfession is not null ? $"/{secondaryProfession.Alias}" : "")} {entry.Name}"
|
|
};
|
|
}).ToList();
|
|
|
|
var selectedCharacter = parsedCharacters.FirstOrDefault(x => x.CharacterName == characterSelectInformation.CurrentCharacter.Name);
|
|
if (selectedCharacter is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var currentExperience = mainPlayerState?.CurrentExperience ?? 0U;
|
|
var experienceForCurrentLevel = this.experienceCalculator.GetExperienceForCurrentLevel(currentExperience);
|
|
var experienceForNextLevel = this.experienceCalculator.GetRemainingExperienceForNextLevel(currentExperience);
|
|
var nextExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(currentExperience);
|
|
var totalExperienceForNextLevel = this.experienceCalculator.GetTotalExperienceForNextLevel(currentExperience);
|
|
return new CharacterComponentContext
|
|
{
|
|
Characters = parsedCharacters,
|
|
CurrentCharacter = selectedCharacter,
|
|
CurrentTotalExperience = currentExperience,
|
|
ExperienceForCurrentLevel = experienceForCurrentLevel,
|
|
ExperienceForNextLevel = experienceForNextLevel,
|
|
NextExperienceThreshold = nextExperienceThreshold,
|
|
TotalExperienceForNextLevel = totalExperienceForNextLevel,
|
|
ExperienceDisplay = this.options.CurrentValue.ExperienceDisplay
|
|
};
|
|
}
|
|
|
|
private async Task<BuildComponentContext?> ParseBuildComponentContext(InstanceInfo? instanceInfo, MainPlayerBuildContext? mainPlayerBuildContext, CancellationToken cancellationToken)
|
|
{
|
|
if (instanceInfo is null || mainPlayerBuildContext is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if (this.BuildComponentContext?.CharacterUnlockedSkills.SequenceEqual(mainPlayerBuildContext.UnlockedCharacterSkills) is true &&
|
|
this.BuildComponentContext?.UnlockedProfessions == mainPlayerBuildContext.UnlockedProfessions &&
|
|
this.BuildComponentContext?.PrimaryProfessionId == mainPlayerBuildContext.PrimaryProfessionId &&
|
|
DateTime.Now < this.lastBuildCheck + BuildsCheckFrequency)
|
|
{
|
|
return this.BuildComponentContext;
|
|
}
|
|
|
|
var matchingBuilds = await this.buildTemplateManager.GetBuilds()
|
|
.Where(b =>
|
|
{
|
|
if (b is SingleBuildEntry singleBuild &&
|
|
this.buildTemplateManager.CanApply(mainPlayerBuildContext, singleBuild))
|
|
{
|
|
return true;
|
|
}
|
|
else if (b is TeamBuildEntry teamBuild &&
|
|
this.buildTemplateManager.CanApply(mainPlayerBuildContext, teamBuild))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}).ToListAsync(cancellationToken);
|
|
|
|
this.lastBuildCheck = DateTime.Now;
|
|
return new BuildComponentContext
|
|
{
|
|
IsInOutpost = instanceInfo.Type is Shared.Models.Api.InstanceType.Outpost,
|
|
PrimaryProfessionId = mainPlayerBuildContext.PrimaryProfessionId,
|
|
CharacterUnlockedSkills = mainPlayerBuildContext.UnlockedCharacterSkills,
|
|
UnlockedProfessions = mainPlayerBuildContext.UnlockedProfessions,
|
|
AvailableBuilds = matchingBuilds
|
|
};
|
|
}
|
|
|
|
private static CurrentMapComponentContext? ParseCurrentMapComponentContext(InstanceInfo? mainPlayerInstance)
|
|
{
|
|
_ = Map.TryParse(((int?)mainPlayerInstance?.MapId) ?? -1, out var currentMap);
|
|
if (currentMap is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return new CurrentMapComponentContext { CurrentMap = currentMap };
|
|
}
|
|
|
|
private static PlayerResourcesComponentContext? ParsePlayerResourcesContext(MainPlayerState? mainPlayerState)
|
|
{
|
|
if (mainPlayerState is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return new PlayerResourcesComponentContext
|
|
{
|
|
CurrentBalthazar = mainPlayerState.CurrentBalthazar,
|
|
MaxBalthazar = mainPlayerState.MaxBalthazar,
|
|
TotalBalthazar = mainPlayerState.TotalBalthazar,
|
|
CurrentImperial = mainPlayerState.CurrentImperial,
|
|
MaxImperial = mainPlayerState.MaxImperial,
|
|
TotalImperial = mainPlayerState.TotalImperial,
|
|
CurrentKurzick = mainPlayerState.CurrentKurzick,
|
|
MaxKurzick = mainPlayerState.MaxKurzick,
|
|
TotalKurzick = mainPlayerState.TotalKurzick,
|
|
CurrentLuxon = mainPlayerState.CurrentLuxon,
|
|
MaxLuxon = mainPlayerState.MaxLuxon,
|
|
TotalLuxon = mainPlayerState.TotalLuxon
|
|
};
|
|
}
|
|
|
|
private static QuestLogComponentContext? ParseQuestLogComponentContext(QuestLogInformation? questLog)
|
|
{
|
|
if (questLog is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var currentQuestEntry = questLog.Quests.FirstOrDefault(q => q.QuestId == questLog.CurrentQuestId);
|
|
QuestMetadata? currentQuestMeta = default;
|
|
if (currentQuestEntry is not null)
|
|
{
|
|
_ = Quest.TryParse((int)questLog.CurrentQuestId, out var currentQuest);
|
|
_ = Map.TryParse((int)currentQuestEntry.MapFrom, out var mapFrom);
|
|
currentQuestMeta = new QuestMetadata
|
|
{
|
|
From = mapFrom,
|
|
Quest = currentQuest
|
|
};
|
|
}
|
|
|
|
return new QuestLogComponentContext
|
|
{
|
|
CurrentQuest = currentQuestMeta,
|
|
Quests = [.. questLog.Quests
|
|
.Where(q => q.QuestId != questLog.CurrentQuestId)
|
|
.Select(quest =>
|
|
{
|
|
if (!Quest.TryParse((int)quest.QuestId, out var parsedQuest) ||
|
|
!Map.TryParse((int)quest.MapFrom, out var mapFrom))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return new QuestMetadata { From = mapFrom, Quest = parsedQuest };
|
|
})
|
|
.OfType<QuestMetadata>()]
|
|
};
|
|
}
|
|
|
|
private static TitleInformationComponentContext? ParseTitleInformationComponentContext(TitleInfo? titleInfo)
|
|
{
|
|
if (titleInfo is null ||
|
|
!Title.TryParse((int)titleInfo.Id, out var title))
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return new TitleInformationComponentContext
|
|
{
|
|
Title = title,
|
|
CurrentPoints = titleInfo.CurrentPoints,
|
|
IsPercentage = titleInfo.IsPercentage,
|
|
MaxTierNumber = titleInfo.MaxTierNumber,
|
|
TierNumber = titleInfo.TierNumber,
|
|
PointsForCurrentRank = titleInfo.PointsForCurrentRank,
|
|
PointsForNextRank = titleInfo.PointsForNextRank
|
|
};
|
|
}
|
|
|
|
private VanquishComponentContext? ParseVanquishComponentContext(InstanceInfo? instanceInfo)
|
|
{
|
|
if (instanceInfo is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return new VanquishComponentContext
|
|
{
|
|
FoesKilled = instanceInfo.FoesKilled,
|
|
FoesToKill = instanceInfo.FoesToKill,
|
|
HardMode = instanceInfo.Difficulty is DifficultyInfo.Hard,
|
|
Vanquishing = (instanceInfo.FoesToKill + instanceInfo.FoesKilled > 0U) && instanceInfo.Difficulty is DifficultyInfo.Hard,
|
|
Display = this.options.CurrentValue.VanquishingDisplay
|
|
};
|
|
}
|
|
|
|
private static PointsDisplay CycleValue(PointsDisplay points)
|
|
{
|
|
return points switch
|
|
{
|
|
PointsDisplay.CurrentAndMax => PointsDisplay.Remaining,
|
|
PointsDisplay.Remaining => PointsDisplay.Percentage,
|
|
PointsDisplay.Percentage => PointsDisplay.CurrentAndMax,
|
|
_ => throw new InvalidOperationException()
|
|
};
|
|
}
|
|
|
|
private static ExperienceDisplay CycleValue(ExperienceDisplay display)
|
|
{
|
|
return display switch
|
|
{
|
|
ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax => ExperienceDisplay.Percentage,
|
|
ExperienceDisplay.Percentage => ExperienceDisplay.RemainingUntilNextLevel,
|
|
ExperienceDisplay.RemainingUntilNextLevel => ExperienceDisplay.TotalCurretAndTotalMax,
|
|
ExperienceDisplay.TotalCurretAndTotalMax => ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax,
|
|
_ => throw new InvalidOperationException()
|
|
};
|
|
}
|
|
}
|