From 673ed45bfb23ed03f94def871dfd443493ed6867 Mon Sep 17 00:00:00 2001 From: Alexandru Macocian Date: Sat, 28 Oct 2023 11:36:32 +0200 Subject: [PATCH] Name module improvements Add waiting logic for names Remove debouncing logic Add ability to search wiki by npc name Update GWCA --- Daybreak.GWCA/source/NameModule.cpp | 46 ++++-- .../Configuration/ProjectConfiguration.cs | 1 - .../Controls/LivingEntityContextMenu.xaml.cs | 4 +- .../Controls/Minimap/GuildwarsMinimap.xaml.cs | 50 ++++--- Daybreak/Controls/PlayerContextMenu.xaml.cs | 4 +- Daybreak/Daybreak.csproj | 2 +- Daybreak/Models/Guildwars/DebounceResponse.cs | 11 -- Daybreak/Services/Drawing/DrawingService.cs | 40 +++--- Daybreak/Services/Drawing/IDrawingService.cs | 4 +- .../Scanner/GuildwarsEntityDebouncer.cs | 134 ------------------ .../Scanner/IGuildwarsEntityDebouncer.cs | 11 -- .../Models/DebouncePositionalEntityCache.cs | 14 -- Daybreak/Views/FocusView.xaml | 1 + Daybreak/Views/FocusView.xaml.cs | 20 ++- GWCA | 2 +- 15 files changed, 108 insertions(+), 236 deletions(-) delete mode 100644 Daybreak/Models/Guildwars/DebounceResponse.cs delete mode 100644 Daybreak/Services/Scanner/GuildwarsEntityDebouncer.cs delete mode 100644 Daybreak/Services/Scanner/IGuildwarsEntityDebouncer.cs delete mode 100644 Daybreak/Services/Scanner/Models/DebouncePositionalEntityCache.cs diff --git a/Daybreak.GWCA/source/NameModule.cpp b/Daybreak.GWCA/source/NameModule.cpp index c92bfd71..60867426 100644 --- a/Daybreak.GWCA/source/NameModule.cpp +++ b/Daybreak.GWCA/source/NameModule.cpp @@ -16,6 +16,7 @@ #include namespace Daybreak::Modules::NameModule { + std::vector*, std::wstring*>> WaitingList; std::queue>*> PromiseQueue; std::mutex GameThreadMutex; GW::HookEntry GameThreadHook; @@ -32,28 +33,25 @@ namespace Daybreak::Modules::NameModule { return strTo; } - NamePayload GetAsyncName(uint32_t id) { + std::wstring* GetAsyncName(uint32_t id) { NamePayload namePayload; - auto name = new std::wstring(); auto agent = GW::Agents::GetAgentByID(id); if (!agent) { - return namePayload; + return nullptr; } auto agentLiving = agent->GetAsAgentLiving(); if (!agentLiving) { - return namePayload; + return nullptr; } + auto name = new std::wstring(); if (!GW::Agents::AsyncGetAgentName(agentLiving, *name)) { - return namePayload; + delete(name); + return nullptr; } - const auto namestr = WStringToString(*name); - namePayload.Id = id; - namePayload.Name = namestr; - delete(name); - return namePayload; + return name; } void EnsureInitialized() { @@ -62,18 +60,40 @@ namespace Daybreak::Modules::NameModule { GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) { while (!PromiseQueue.empty()) { auto promiseRequest = PromiseQueue.front(); - std::promise& promise = std::get<1>(*promiseRequest); + std::promise &promise = std::get<1>(*promiseRequest); uint32_t id = std::get<0>(*promiseRequest); PromiseQueue.pop(); try { - auto payload = GetAsyncName(id); - promise.set_value(payload); + auto name = GetAsyncName(id); + if (!name) { + continue; + } + + WaitingList.emplace_back(id, &promise, name); } catch (...) { NamePayload payload; promise.set_value(payload); } } + + for (auto i = 0; i < WaitingList.size(); ) { + auto item = &WaitingList[i]; + auto name = std::get<2>(*item); + if (name->empty()) { + i++; + continue; + } + + auto promise = std::get<1>(*item); + auto id = std::get<0>(*item); + WaitingList.erase(WaitingList.begin() + i); + NamePayload payload; + payload.Id = id; + payload.Name = WStringToString(*name); + delete(name); + promise->set_value(payload); + } }); initialized = true; diff --git a/Daybreak/Configuration/ProjectConfiguration.cs b/Daybreak/Configuration/ProjectConfiguration.cs index 99139a4b..eb6fea29 100644 --- a/Daybreak/Configuration/ProjectConfiguration.cs +++ b/Daybreak/Configuration/ProjectConfiguration.cs @@ -224,7 +224,6 @@ public override void RegisterServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Daybreak/Controls/LivingEntityContextMenu.xaml.cs b/Daybreak/Controls/LivingEntityContextMenu.xaml.cs index 03fc4cca..8e85be7e 100644 --- a/Daybreak/Controls/LivingEntityContextMenu.xaml.cs +++ b/Daybreak/Controls/LivingEntityContextMenu.xaml.cs @@ -19,7 +19,7 @@ public partial class LivingEntityContextMenu : UserControl { private readonly IGuildwarsMemoryReader guildwarsMemoryReader; - public event EventHandler? LivingEntityContextMenuClicked; + public event EventHandler<(LivingEntity? Entity, string? Name)>? LivingEntityContextMenuClicked; public event EventHandler? LivingEntityProfessionContextMenuClicked; [GenerateDependencyProperty] @@ -64,7 +64,7 @@ private async void LivingEntityContextMenu_DataContextChanged(object sender, Sys private void NpcDefinitionTextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e) { - this.LivingEntityContextMenuClicked?.Invoke(this, this.DataContext.As()?.LivingEntity ?? default); + this.LivingEntityContextMenuClicked?.Invoke(this, (this.DataContext.As()?.LivingEntity ?? default, this.EntityName)); } private void PrimaryProfessionTextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e) diff --git a/Daybreak/Controls/Minimap/GuildwarsMinimap.xaml.cs b/Daybreak/Controls/Minimap/GuildwarsMinimap.xaml.cs index 1a04fe43..62eb8914 100644 --- a/Daybreak/Controls/Minimap/GuildwarsMinimap.xaml.cs +++ b/Daybreak/Controls/Minimap/GuildwarsMinimap.xaml.cs @@ -7,7 +7,6 @@ using System.Windows.Media; using System; using System.Extensions; -using Daybreak.Services.Scanner; using Microsoft.Extensions.DependencyInjection; using System.Core.Extensions; using System.Windows.Input; @@ -46,7 +45,6 @@ public partial class GuildwarsMinimap : UserControl private readonly List mainPlayerPositionHistory = new(); private readonly IPathfinder pathfinder; private readonly IDrawingService drawingService; - private readonly IGuildwarsEntityDebouncer guildwarsEntityDebouncer; private readonly IThemeManager themeManager; private readonly Color outlineColor = Colors.Chocolate; private readonly TimeSpan offsetRevertDelay = TimeSpan.FromSeconds(3); @@ -65,7 +63,6 @@ public partial class GuildwarsMinimap : UserControl private Point initialClickPoint = new(0, 0); private Point lastPathfindingPoint = new(0, 0); private int pathfindingObjectiveCount = 0; - private DebounceResponse? cachedDebounceResponse; private PathfindingCache? pathfindingCache; [GenerateDependencyProperty] @@ -91,12 +88,12 @@ public partial class GuildwarsMinimap : UserControl public event EventHandler? PlayerInformationClicked; public event EventHandler? MapIconClicked; public event EventHandler? ProfessionClicked; + public event EventHandler NpcNameClicked; public GuildwarsMinimap() :this( Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService(), Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService(), - Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService(), Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService(), Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService()) { @@ -105,13 +102,11 @@ public GuildwarsMinimap() public GuildwarsMinimap( IPathfinder pathfinder, IDrawingService drawingService, - IGuildwarsEntityDebouncer guildwarsEntityDebouncer, IThemeManager themeManager, IMetricsService metricsService) { this.pathfinder = pathfinder.ThrowIfNull(); this.drawingService = drawingService.ThrowIfNull(); - this.guildwarsEntityDebouncer = guildwarsEntityDebouncer.ThrowIfNull(); this.themeManager = themeManager.ThrowIfNull(); this.drawingLatency = metricsService.CreateHistogram(DrawingLatencyName, DrawingLatencyUnitName, DrawingLatencyDescription, Models.Metrics.AggregationTypes.P95); @@ -135,7 +130,6 @@ protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) } else if (e.Property == PathingDataProperty) { - this.guildwarsEntityDebouncer.ClearCaches(); this.mainPlayerPositionHistory.Clear(); this.lastPathfindingPoint = new Point(0, 0); this.pathfindingObjectiveCount = 0; @@ -161,12 +155,11 @@ private void UpdateGameData() return; } - var debounceResponse = this.guildwarsEntityDebouncer.DebounceEntities(this.GameData); if (!double.IsFinite(this.mapWidth) || !double.IsFinite(this.mapHeight) || !double.IsFinite(this.mapVirtualMinWidth) || !double.IsFinite(this.mapVirtualMinHeight) || - debounceResponse.MainPlayer.Position is null) + this.GameData.MainPlayer?.Position is null) { return; } @@ -175,7 +168,7 @@ private void UpdateGameData() this.TargetEntityModelId = (int?)this.GameData.LivingEntities?.FirstOrDefault(e => e.Id == this.TargetEntityId)?.ModelType ?? 0; var screenVirtualWidth = this.ActualWidth / this.Zoom; var screenVirtualHeight = this.ActualHeight / this.Zoom; - var position = debounceResponse.MainPlayer.Position!.Value; + var position = this.GameData.MainPlayer.Position!.Value; this.originPoint = new Point( position.X - (screenVirtualWidth / 2) - (this.originOffset.X / this.Zoom), position.Y + (screenVirtualHeight / 2) + (this.originOffset.Y / this.Zoom)); @@ -189,19 +182,18 @@ private void UpdateGameData() 0); this.MapDrawingHost.Height = this.mapHeight * this.Zoom; this.MapDrawingHost.Width = this.mapWidth * this.Zoom; - this.cachedDebounceResponse = debounceResponse; this.ManageMainPlayerPositionHistory(); this.DrawEntities(); } private void ManageMainPlayerPositionHistory() { - if (this.cachedDebounceResponse is null) + if (this.GameData is null) { return; } - var currentPosition = this.cachedDebounceResponse.MainPlayer.Position ?? throw new InvalidOperationException("Unexpected main player null position"); + var currentPosition = this.GameData.MainPlayer?.Position ?? throw new InvalidOperationException("Unexpected main player null position"); if (this.mainPlayerPositionHistory.Any(oldPosition => PositionsCollide(oldPosition, currentPosition))) { return; @@ -320,12 +312,12 @@ private void DrawEntities() PositionRadius, EntitySize, foregroundColor); - this.drawingService.DrawEngagementArea(bitmap, this.cachedDebounceResponse ?? new DebounceResponse()); + this.drawingService.DrawEngagementArea(bitmap, this.GameData); this.drawingService.DrawMainPlayerPositionHistory(bitmap, this.mainPlayerPositionHistory); this.drawingService.DrawPaths(bitmap, this.pathfindingCache); this.drawingService.DrawQuestObjectives(bitmap, this.GameData.MainPlayer?.QuestLog ?? new List()); this.drawingService.DrawMapIcons(bitmap, this.GameData.MapIcons ?? new List()); - this.drawingService.DrawEntities(bitmap, this.cachedDebounceResponse ?? new DebounceResponse(), this.TargetEntityId); + this.drawingService.DrawEntities(bitmap, this.GameData, this.TargetEntityId); bitmap.Unlock(); this.drawingLatency.Record(sw.ElapsedMilliseconds); } @@ -387,8 +379,8 @@ private async void CalculatePathsToObjectives() var playerPosition = new Point { - X = this.cachedDebounceResponse?.MainPlayer.Position?.X ?? 0, - Y = this.cachedDebounceResponse?.MainPlayer.Position?.Y ?? 0, + X = this.GameData?.MainPlayer?.Position?.X ?? 0, + Y = this.GameData?.MainPlayer?.Position?.Y ?? 0, }; /* @@ -689,24 +681,36 @@ private void QuestContextMenu_QuestContextMenuClicked(object _, QuestMetadata? q this.QuestMetadataClicked?.Invoke(this, quest); } - private void PlayerContextMenu_PlayerContextMenuClicked(object _, PlayerInformation? playerInformation) + private void PlayerContextMenu_PlayerContextMenuClicked(object _, (PlayerInformation? Player, string? Name) tuple) { - if (playerInformation is null) + if (tuple.Name?.IsNullOrWhiteSpace() is false) { + this.NpcNameClicked?.Invoke(this, tuple.Name); return; } - this.PlayerInformationClicked?.Invoke(this, playerInformation); + if (tuple.Player is null) + { + return; + } + + this.PlayerInformationClicked?.Invoke(this, tuple.Player); } - private void LivingEntityContextMenu_LivingEntityContextMenuClicked(object _, LivingEntity? livingEntity) + private void LivingEntityContextMenu_LivingEntityContextMenuClicked(object _, (LivingEntity? LivingEntity, string? Name) tuple) { - if (livingEntity is null) + if (tuple.Name?.IsNullOrWhiteSpace() is false) + { + this.NpcNameClicked?.Invoke(this, tuple.Name); + return; + } + + if (tuple.LivingEntity is null) { return; } - this.LivingEntityClicked?.Invoke(this, livingEntity); + this.LivingEntityClicked?.Invoke(this, tuple.LivingEntity); } private void LivingEntityContextMenu_LivingEntityProfessionContextMenuClicked(object _, Profession? e) diff --git a/Daybreak/Controls/PlayerContextMenu.xaml.cs b/Daybreak/Controls/PlayerContextMenu.xaml.cs index b3db6a45..cc64e175 100644 --- a/Daybreak/Controls/PlayerContextMenu.xaml.cs +++ b/Daybreak/Controls/PlayerContextMenu.xaml.cs @@ -18,7 +18,7 @@ public partial class PlayerContextMenu : UserControl { private readonly IGuildwarsMemoryReader guildwarsMemoryReader; - public event EventHandler? PlayerContextMenuClicked; + public event EventHandler<(PlayerInformation? Player, string? Name)>? PlayerContextMenuClicked; [GenerateDependencyProperty] private string playerName = string.Empty; @@ -49,6 +49,6 @@ private async void PlayerContextMenu_DataContextChanged(object sender, System.Wi private void TextBlock_MouseLeftButtonDown(object _, MouseButtonEventArgs e) { - this.PlayerContextMenuClicked?.Invoke(this, this.DataContext.As()?.Player ?? default); + this.PlayerContextMenuClicked?.Invoke(this, (this.DataContext.As()?.Player ?? default, this.PlayerName)); } } diff --git a/Daybreak/Daybreak.csproj b/Daybreak/Daybreak.csproj index c74068ed..81c1522d 100644 --- a/Daybreak/Daybreak.csproj +++ b/Daybreak/Daybreak.csproj @@ -13,7 +13,7 @@ preview Daybreak.ico true - 0.9.8.128 + 0.9.8.129 true cfb2a489-db80-448d-a969-80270f314c46 True diff --git a/Daybreak/Models/Guildwars/DebounceResponse.cs b/Daybreak/Models/Guildwars/DebounceResponse.cs deleted file mode 100644 index 4ea17d56..00000000 --- a/Daybreak/Models/Guildwars/DebounceResponse.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace Daybreak.Models.Guildwars; - -public sealed class DebounceResponse -{ - public MainPlayerInformation MainPlayer { get; init; } = default!; - public IEnumerable WorldPlayers { get; init; } = default!; - public IEnumerable Party { get; init; } = default!; - public IEnumerable LivingEntities { get; init; } = default!; -} diff --git a/Daybreak/Services/Drawing/DrawingService.cs b/Daybreak/Services/Drawing/DrawingService.cs index c6dd8c00..4b98f1fa 100644 --- a/Daybreak/Services/Drawing/DrawingService.cs +++ b/Daybreak/Services/Drawing/DrawingService.cs @@ -65,26 +65,26 @@ public bool IsEntityOnScreen(Position? position, out int x, out int y) return true; } - public void DrawEntities(WriteableBitmap bitmap, DebounceResponse debounceResponse, int targetEntityId) + public void DrawEntities(WriteableBitmap bitmap, GameData gameData, int targetEntityId) { if (bitmap is null) { return; } - if (debounceResponse is null || - debounceResponse.MainPlayer.Position is null || - debounceResponse.WorldPlayers is null | - debounceResponse.Party is null || - debounceResponse.LivingEntities is null) + if (gameData is null || + gameData.MainPlayer?.Position is null || + gameData.WorldPlayers is null | + gameData.Party is null || + gameData.LivingEntities is null) { return; } - var entities = debounceResponse.LivingEntities.OfType() - .Concat(debounceResponse.Party!.OfType()) - .Concat(debounceResponse.WorldPlayers!.OfType()) - .Append(debounceResponse.MainPlayer) + var entities = gameData.LivingEntities.OfType() + .Concat(gameData.Party!.OfType()) + .Concat(gameData.WorldPlayers!.OfType()) + .Append(gameData.MainPlayer) .Where(IsValidPositionalEntity); var nonTargetedEntities = entities.Where(e => e.Id != targetEntityId); @@ -266,26 +266,26 @@ public void DrawQuestObjectives(WriteableBitmap bitmap, IEnumerable() - .Concat(debounceResponse.Party!.OfType()) - .Concat(debounceResponse.WorldPlayers!.OfType()) - .Append(debounceResponse.MainPlayer) + var entities = gameData.LivingEntities.OfType() + .Concat(gameData.Party!.OfType()) + .Concat(gameData.WorldPlayers!.OfType()) + .Append(gameData.MainPlayer) .Where(IsValidPositionalEntity); foreach (var entity in entities) diff --git a/Daybreak/Services/Drawing/IDrawingService.cs b/Daybreak/Services/Drawing/IDrawingService.cs index 498f0e51..e086e659 100644 --- a/Daybreak/Services/Drawing/IDrawingService.cs +++ b/Daybreak/Services/Drawing/IDrawingService.cs @@ -12,7 +12,7 @@ public interface IDrawingService void DrawPaths(WriteableBitmap writeableBitmap, PathfindingCache? pathfindingCache); - void DrawEntities(WriteableBitmap bitmap, DebounceResponse debounceResponse, int targetEntityId); + void DrawEntities(WriteableBitmap bitmap, GameData gameData, int targetEntityId); void DrawQuestObjectives(WriteableBitmap bitmap, IEnumerable quests); @@ -20,7 +20,7 @@ public interface IDrawingService void DrawMapIcons(WriteableBitmap bitmap, List mapIcons); - void DrawEngagementArea(WriteableBitmap bitmap, DebounceResponse debounceResponse); + void DrawEngagementArea(WriteableBitmap bitmap, GameData gameData); bool IsEntityOnScreen(Position? position, out int x, out int y); } diff --git a/Daybreak/Services/Scanner/GuildwarsEntityDebouncer.cs b/Daybreak/Services/Scanner/GuildwarsEntityDebouncer.cs deleted file mode 100644 index da6423ca..00000000 --- a/Daybreak/Services/Scanner/GuildwarsEntityDebouncer.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Daybreak.Models; -using Daybreak.Models.Guildwars; -using Daybreak.Services.Scanner.Models; -using System.Collections.Generic; -using System.Linq; - -namespace Daybreak.Services.Scanner; - -public sealed class GuildwarsEntityDebouncer : IGuildwarsEntityDebouncer -{ - private const uint MaxMismatchTimer = 100; - - private DebouncePositionalEntityCache mainPlayer = new(); - private List> worldPlayers = new(); - private List> party = new(); - private List> livingEntities = new(); - - public DebounceResponse DebounceEntities(GameData gameData) - { - this.DebounceEntitiesInternal(gameData); - return new DebounceResponse - { - MainPlayer = this.mainPlayer.Entity!, - WorldPlayers = this.worldPlayers.Select(c => c.Entity)!, - Party = this.party.Select(c => c.Entity)!, - LivingEntities = this.livingEntities.Select(c => c.Entity)! - }; - } - - public void ClearCaches() - { - this.ClearInternal(); - } - - private void ClearInternal() - { - this.mainPlayer = new(); - this.worldPlayers.Clear(); - this.party.Clear(); - this.livingEntities.Clear(); - } - - private void DebounceEntitiesInternal(GameData gameData) - { - if (gameData.MainPlayer is MainPlayerInformation newMainPlayer) - { - this.DebounceEntityInternal(newMainPlayer, this.mainPlayer); - } - - if (gameData.WorldPlayers is List newWorldPlayers) - { - this.worldPlayers = this.DebounceEntityListInternal(newWorldPlayers, this.worldPlayers).ToList(); - } - - if (gameData.Party is List newPartyPlayers) - { - this.party = this.DebounceEntityListInternal(newPartyPlayers, this.party).ToList(); - } - - if (gameData.LivingEntities is List newLivingEntities) - { - this.livingEntities = this.DebounceEntityListInternal(newLivingEntities, this.livingEntities).ToList(); - } - } - - private IEnumerable> DebounceEntityListInternal(List newEntityList, List> cacheList) - where T : IEntity - { - var debouncingList = new List<(T, DebouncePositionalEntityCache)>(newEntityList.Count); - foreach(var entity in newEntityList) - { - if (cacheList.FirstOrDefault(e => e.Entity!.Id == entity.Id) is not DebouncePositionalEntityCache cache) - { - cache = new DebouncePositionalEntityCache { Entity = entity }; - cacheList.Add(cache); - } - - cache.Entity = entity; - debouncingList.Add((entity, cache)); - } - - foreach(var debouncingTuple in debouncingList) - { - this.DebounceEntityInternal(debouncingTuple.Item1, debouncingTuple.Item2); - } - - return debouncingList.Select(e => e.Item2); - } - - private void DebounceEntityInternal(T newEntityData, DebouncePositionalEntityCache cachedEntityData) - where T : IEntity - { - // If the new entity has no position, ignore. - if (newEntityData.Position is not Position newPosition) - { - return; - } - - // If the cache is empty, initialize the cache. - if (cachedEntityData.Entity is null) - { - cachedEntityData.Entity = newEntityData; - cachedEntityData.PositionCache.Add(newPosition); - return; - } - - // If the new data is older than the old data, ignore the new data. - // The second if clause is supposed to catch uint overflows. - if (cachedEntityData.Entity.Timer - newEntityData.Timer > 0 && - cachedEntityData.Entity.Timer - newEntityData.Timer < MaxMismatchTimer) - { - return; - } - - // If the position is already in the cache, ignore the new data. - if (cachedEntityData.PositionCache.TryGetValue(newPosition, out _)) - { - return; - } - - // Add the position to the cache. If the cache is full, clear a portion of the cache. - cachedEntityData.PositionCache.Add(newPosition); - cachedEntityData.PositionList.AddLast(newPosition); - cachedEntityData.Entity = newEntityData; - if (cachedEntityData.PositionCache.Count > DebouncePositionalEntityCache.CacheCapacity) - { - for (var i = 0; i < DebouncePositionalEntityCache.CacheStep; i++) - { - cachedEntityData.PositionCache.Remove(cachedEntityData.PositionList.First()); - cachedEntityData.PositionList.RemoveFirst(); - } - } - } -} diff --git a/Daybreak/Services/Scanner/IGuildwarsEntityDebouncer.cs b/Daybreak/Services/Scanner/IGuildwarsEntityDebouncer.cs deleted file mode 100644 index f12ee685..00000000 --- a/Daybreak/Services/Scanner/IGuildwarsEntityDebouncer.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Daybreak.Models; -using Daybreak.Models.Guildwars; - -namespace Daybreak.Services.Scanner; - -public interface IGuildwarsEntityDebouncer -{ - DebounceResponse DebounceEntities(GameData gameData); - - void ClearCaches(); -} diff --git a/Daybreak/Services/Scanner/Models/DebouncePositionalEntityCache.cs b/Daybreak/Services/Scanner/Models/DebouncePositionalEntityCache.cs deleted file mode 100644 index cc0fbcea..00000000 --- a/Daybreak/Services/Scanner/Models/DebouncePositionalEntityCache.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Daybreak.Models.Guildwars; -using System.Collections.Generic; - -namespace Daybreak.Services.Scanner.Models; - -public sealed class DebouncePositionalEntityCache where T : IEntity -{ - public const int CacheCapacity = 1000; - public const int CacheStep = 100; - - public T? Entity { get; set; } = default!; - public HashSet PositionCache { get; } = new HashSet(CacheCapacity); - public LinkedList PositionList { get; } = new LinkedList(); -} diff --git a/Daybreak/Views/FocusView.xaml b/Daybreak/Views/FocusView.xaml index a5b4777c..b7691855 100644 --- a/Daybreak/Views/FocusView.xaml +++ b/Daybreak/Views/FocusView.xaml @@ -111,6 +111,7 @@ PlayerInformationClicked="GuildwarsMinimap_PlayerInformationClicked" MapIconClicked="GuildwarsMinimap_MapIconClicked" ProfessionClicked="GuildwarsMinimap_ProfessionClicked" + NpcNameClicked="GuildwarsMinimap_NpcNameClicked" ClipToBounds="True" Background="{DynamicResource Daybreak.Brushes.Background}" BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}" diff --git a/Daybreak/Views/FocusView.xaml.cs b/Daybreak/Views/FocusView.xaml.cs index acdfa0e6..d549ad74 100644 --- a/Daybreak/Views/FocusView.xaml.cs +++ b/Daybreak/Views/FocusView.xaml.cs @@ -27,6 +27,9 @@ namespace Daybreak.Views; /// public partial class FocusView : UserControl { + private const string NamePlaceholder = "[NamePlaceholder]"; + private const string WikiUrl = "https://wiki.guildwars.com/wiki/[NamePlaceholder]"; + private static readonly TimeSpan UninitializedBackoff = TimeSpan.FromSeconds(15); private readonly IBuildTemplateManager buildTemplateManager; @@ -356,7 +359,8 @@ await Task.WhenAll( if (userData?.User is null || sessionData?.Session is null || mainPlayerData?.PlayerInformation is null || - sessionData.Session.InstanceType is InstanceType.Loading or InstanceType.Undefined) + sessionData.Session.InstanceType is InstanceType.Loading or InstanceType.Undefined || + sessionData.Session.InstanceTimer == 0) { this.MainPlayerDataValid = false; this.Browser.Visibility = Visibility.Collapsed; @@ -564,6 +568,20 @@ private void InventoryComponent_ItemWikiClicked(object _, ItemBase e) this.BrowserAddress = entity.WikiUrl; } + private void GuildwarsMinimap_NpcNameClicked(object _, string e) + { + if (e.IsNullOrEmpty() is not false) + { + return; + } + + var indexOfSeparator = e.IndexOf("["); + indexOfSeparator = indexOfSeparator >= 0 ? indexOfSeparator : e.Length; + var curedNpcName = e[..indexOfSeparator]; + var npcUrl = WikiUrl.Replace(NamePlaceholder, curedNpcName); + this.BrowserAddress = npcUrl; + } + private void InventoryComponent_PriceHistoryClicked(object _, ItemBase e) { this.viewManager.ShowView(e); diff --git a/GWCA b/GWCA index 6542afd8..d85f7c90 160000 --- a/GWCA +++ b/GWCA @@ -1 +1 @@ -Subproject commit 6542afd8baea8643ceb4cb53e47abf5d7e0311b9 +Subproject commit d85f7c90c99273182e8f690e74f0f689007bff0c