From bdf5332328b28c1b46d327525ebd6b5a6f4941d2 Mon Sep 17 00:00:00 2001 From: danio0106 <88092336+danio0106@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:28:13 +0100 Subject: [PATCH 1/2] Fix Mirage league pricing and ExileCore API drift --- Ninja Price/Main/CustomItem.cs | 72 +++++++++++++++++++++++++++++++++- Ninja Price/Main/Main.cs | 8 +++- Ninja Price/Main/Render.cs | 38 ++++++++++++++++-- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/Ninja Price/Main/CustomItem.cs b/Ninja Price/Main/CustomItem.cs index 71b0d48..88b1173 100644 --- a/Ninja Price/Main/CustomItem.cs +++ b/Ninja Price/Main/CustomItem.cs @@ -73,6 +73,70 @@ public class CurrencyData public int MaxStackSize = 0; } + private static bool IsLikelyMapItem(string path, string className, string baseName) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + if (path.Contains("MapKey", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("Metadata/Items/Maps/", StringComparison.Ordinal)) + { + return true; + } + + if (!string.IsNullOrEmpty(className) && + className.Contains("Map", StringComparison.OrdinalIgnoreCase) && + !className.Contains("Fragment", StringComparison.OrdinalIgnoreCase) && + !className.Contains("MiscMapItem", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return !string.IsNullOrEmpty(baseName) && baseName.Contains("Map", StringComparison.OrdinalIgnoreCase); + } + + private static int GetFallbackMapTier(string path, string baseName) + { + var tierFromPath = ExtractTrailingNumber(path, "MapKeyTier"); + if (tierFromPath > 0) + { + return tierFromPath; + } + + var tierFromName = ExtractTrailingNumber(baseName, "(Tier "); + if (tierFromName > 0) + { + return tierFromName; + } + + return 0; + } + + private static int ExtractTrailingNumber(string value, string token) + { + if (string.IsNullOrEmpty(value)) + { + return 0; + } + + var tokenIndex = value.IndexOf(token, StringComparison.OrdinalIgnoreCase); + if (tokenIndex < 0) + { + return 0; + } + + var numberStart = tokenIndex + token.Length; + var numberEnd = numberStart; + while (numberEnd < value.Length && char.IsDigit(value[numberEnd])) + { + numberEnd++; + } + + return numberEnd > numberStart && int.TryParse(value[numberStart..numberEnd], out var number) ? number : 0; + } + public CustomItem() { } @@ -220,8 +284,12 @@ public CustomItem(Entity itemEntity, Element element) if (weaponClass.Any(ClassName.Equals)) IsWeapon = true; - MapInfo.MapTier = itemEntity.TryGetComponent(out var map) ? map.Tier : 0; - MapInfo.IsMap = MapInfo.MapTier > 0; + MapInfo.MapTier = GetFallbackMapTier(Path, BaseName); + MapInfo.IsMap = MapInfo.MapTier > 0 || IsLikelyMapItem(Path, ClassName, BaseName); + if (MapInfo.IsMap && MapInfo.MapTier == 0) + { + MapInfo.MapTier = 1; + } if (Rarity != ItemRarity.Unique && MapInfo.IsMap) { diff --git a/Ninja Price/Main/Main.cs b/Ninja Price/Main/Main.cs index d2e1721..b82113f 100644 --- a/Ninja Price/Main/Main.cs +++ b/Ninja Price/Main/Main.cs @@ -34,6 +34,7 @@ public override bool Initialise() Directory.CreateDirectory(NinjaDirectory); UpdateLeagueList(); + SyncCurrentLeague(); StartDataReload(Settings.DataSourceSettings.League.Value, false); Settings.DataSourceSettings.ReloadPrices.OnPressed += () => StartDataReload(Settings.DataSourceSettings.League.Value, true); @@ -215,7 +216,12 @@ private void UpdateLeagueList() { var leagueListFromUrl = Utils.DownloadFromUrl("https://poe.ninja/poe1/api/data/index-state").Result; var leagueData = JsonConvert.DeserializeObject(leagueListFromUrl); - leagueList.UnionWith(leagueData.economyLeagues.Where(league => league.indexed).Select(league => league.name)); + if (leagueData?.economyLeagues != null) + { + leagueList.UnionWith(leagueData.economyLeagues + .Select(league => league.name) + .Where(name => !string.IsNullOrWhiteSpace(name))); + } } catch (Exception ex) { diff --git a/Ninja Price/Main/Render.cs b/Ninja Price/Main/Render.cs index 5f9432f..9034442 100644 --- a/Ninja Price/Main/Render.cs +++ b/Ninja Price/Main/Render.cs @@ -8,6 +8,7 @@ using ImGuiNET; using Ninja_Price.Enums; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -55,6 +56,35 @@ public Main() _disenchantCache = new TimeCache>(() => GameController.Files.VillageUniqueDisenchantValues.EntriesList, 1000); } + private object GetOptionalIngameUiWindow(string propertyName) + { + var ingameUi = GameController?.Game?.IngameState?.IngameUi; + return ingameUi?.GetType().GetProperty(propertyName)?.GetValue(ingameUi); + } + + private static bool IsWindowVisible(object window) + { + return window?.GetType().GetProperty("IsVisible")?.GetValue(window) is bool isVisible && isVisible; + } + + private List GetTrappedStashItems() + { + var trappedStashWindow = GetOptionalIngameUiWindow("TrappedStashWindow"); + if (!IsWindowVisible(trappedStashWindow)) + { + return []; + } + + return trappedStashWindow?.GetType().GetProperty("Items")?.GetValue(trappedStashWindow) is IEnumerable items + ? items.OfType().ToList() + : []; + } + + private bool IsTrappedStashVisible() + { + return IsWindowVisible(GetOptionalIngameUiWindow("TrappedStashWindow")); + } + private List GetItemsOnGround(List previousValue) { var prevDict = previousValue @@ -110,6 +140,8 @@ private List GetItemsOnGroundSlow() public override void Render() { + SyncCurrentLeague(); + #region Reset All Data StashTabValue = 0; @@ -167,8 +199,8 @@ public override void Render() { ItemList = ritualItems; } - if (Settings.LeagueSpecificSettings.ShowTrappedStashPrices && - GameController.Game.IngameState.IngameUi.TrappedStashWindow is { IsVisible: true, Items: { Count: > 0 } trappedStashItems }) + var trappedStashItems = Settings.LeagueSpecificSettings.ShowTrappedStashPrices ? GetTrappedStashItems() : []; + if (trappedStashItems.Count > 0) { ItemList = trappedStashItems; } @@ -281,7 +313,7 @@ public void DrawGraphics() } } else if ( - Settings.LeagueSpecificSettings.ShowTrappedStashPrices && GameController.IngameState.IngameUi.TrappedStashWindow.IsVisible || + Settings.LeagueSpecificSettings.ShowTrappedStashPrices && IsTrappedStashVisible() || Settings.LeagueSpecificSettings.ShowRitualWindowPrices && GameController.IngameState.IngameUi.RitualWindow.IsVisible || Settings.LeagueSpecificSettings.ShowVillageRewardWindowPrices && GameController.IngameState.IngameUi.VillageRewardWindow.IsVisible || Settings.LeagueSpecificSettings.ShowMercenaryInventoryPrices && GameController.IngameState.IngameUi.MercenaryEncounterWindow.IsVisible || From 119ad587c710843f3186dfe93b0f3ed8244c5e88 Mon Sep 17 00:00:00 2001 From: Anthony JP E Mok Date: Sat, 28 Mar 2026 20:12:25 +1100 Subject: [PATCH 2/2] Add sound notification filters by item type - add sound alert toggles for uniques, currency, fragments, maps, cards, essences, gems and jewels, beasts, and other types - route ground drop sound alerts through shared category and playback helpers - keep existing value threshold and custom unique sound behaviour while allowing item-type filtering --- Ninja Price/Main/Render.cs | 115 +++++++++++++++++++++++-------- Ninja Price/Settings/Settings.cs | 9 +++ 2 files changed, 96 insertions(+), 28 deletions(-) diff --git a/Ninja Price/Main/Render.cs b/Ninja Price/Main/Render.cs index 9034442..10a95f3 100644 --- a/Ninja Price/Main/Render.cs +++ b/Ninja Price/Main/Render.cs @@ -412,6 +412,90 @@ private void DrawItemPriceInline(CustomItem customItem) textColor, FontAlign.Center, backgroundColor); } + private string GetMatchingCustomSoundFile(CustomItem item) + { + return item.UniqueNameCandidates.Any() || !string.IsNullOrEmpty(item.UniqueName) + ? item.UniqueNameCandidates + .DefaultIfEmpty(item.UniqueName) + .Select(x => _soundFiles.GetValueOrDefault(x)) + .FirstOrDefault(x => x != null) + : null; + } + + private bool ShouldPlaySoundForItem(CustomItem item, string matchingCustomFile) + { + if (!Settings.SoundNotificationSettings.Enabled || !IsSoundCategoryEnabled(item)) + { + return false; + } + + return item.PriceData.MaxChaosValue >= Settings.SoundNotificationSettings.ValueThreshold.Value || + Settings.SoundNotificationSettings.PlayCustomSoundsIfBelowThreshold && matchingCustomFile != null; + } + + private bool IsSoundCategoryEnabled(CustomItem item) + { + return item.ItemType switch + { + ItemTypes.UniqueAccessory or + ItemTypes.UniqueArmour or + ItemTypes.UniqueFlask or + ItemTypes.UniqueJewel or + ItemTypes.UniqueMap or + ItemTypes.UniqueWeapon => Settings.SoundNotificationSettings.AlertForUniques, + + ItemTypes.Currency or + ItemTypes.DjinnCoin or + ItemTypes.Wombgift or + ItemTypes.Catalyst or + ItemTypes.Artifact or + ItemTypes.Oil or + ItemTypes.Tattoo or + ItemTypes.Omen or + ItemTypes.Resonator or + ItemTypes.Fossil or + ItemTypes.Incubator or + ItemTypes.DeliriumOrbs or + ItemTypes.Vials or + ItemTypes.KalguuranRune or + ItemTypes.AllflameEmber => Settings.SoundNotificationSettings.AlertForCurrency, + + ItemTypes.Fragment or + ItemTypes.Scarab or + ItemTypes.Invitation or + ItemTypes.InscribedUltimatum => Settings.SoundNotificationSettings.AlertForFragments, + + ItemTypes.Map => Settings.SoundNotificationSettings.AlertForMaps, + ItemTypes.DivinationCard => Settings.SoundNotificationSettings.AlertForDivinationCards, + ItemTypes.Essence => Settings.SoundNotificationSettings.AlertForEssences, + ItemTypes.SkillGem or + ItemTypes.ClusterJewel => Settings.SoundNotificationSettings.AlertForGemsAndJewels, + ItemTypes.Beast => Settings.SoundNotificationSettings.AlertForBeasts, + _ => Settings.SoundNotificationSettings.AlertForOtherTypes + }; + } + + private void PlaySoundAlert(string matchingCustomFile) + { + var defaultFile = Path.Join(ConfigDirectory, Main.DefaultWav); + if (matchingCustomFile != null && !File.Exists(matchingCustomFile)) + { + LogError($"Unable to find {matchingCustomFile}. It was probably deleted. Reload the sound list to update your preferences"); + matchingCustomFile = null; + } + + var fileToPlay = matchingCustomFile ?? defaultFile; + if (File.Exists(fileToPlay)) + { + GameController.SoundController.PlaySound(fileToPlay, Settings.SoundNotificationSettings.Volume); + } + else if (fileToPlay == defaultFile) + { + LogError( + $"Unable to find the default sound file ({defaultFile}) to play. Disable the sound notification feature, reload the sound list to let the plugin create it, or create it yourself"); + } + } + private void ProcessHoveredItem() { if (!Settings.HoveredItemSettings.Show) return; @@ -1019,37 +1103,12 @@ private void ProcessItemsOnGround() if (Settings.SoundNotificationSettings.Enabled && !_soundPlayedTracker.ContainsKey(item.EntityId)) { - var matchingCustomFile = - item.UniqueNameCandidates.Any() || - !string.IsNullOrEmpty(item.UniqueName) - ? item.UniqueNameCandidates - .DefaultIfEmpty(item.UniqueName) - .Select(x => _soundFiles.GetValueOrDefault(x)) - .FirstOrDefault(x => x != null) - : null; - if (item.PriceData.MaxChaosValue >= Settings.SoundNotificationSettings.ValueThreshold || - Settings.SoundNotificationSettings.PlayCustomSoundsIfBelowThreshold && matchingCustomFile != null) + var matchingCustomFile = GetMatchingCustomSoundFile(item); + if (ShouldPlaySoundForItem(item, matchingCustomFile)) { if (_soundPlayedTracker.TryAdd(item.EntityId, true)) { - var defaultFile = Path.Join(ConfigDirectory, "default.wav"); - if (matchingCustomFile != null && !File.Exists(matchingCustomFile)) - { - LogError($"Unable to find {matchingCustomFile}. It was probably deleted. Reload the sound list to update your preferences"); - matchingCustomFile = null; - } - - var fileToPlay = matchingCustomFile ?? defaultFile; - - if (File.Exists(fileToPlay)) - { - GameController.SoundController.PlaySound(fileToPlay, Settings.SoundNotificationSettings.Volume); - } - else if (fileToPlay == defaultFile) - { - LogError( - $"Unable to find the default sound file ({defaultFile}) to play. Disable the sound notification feature, reload the sound list to let the plugin create it, or create it yourself"); - } + PlaySoundAlert(matchingCustomFile); } } } diff --git a/Ninja Price/Settings/Settings.cs b/Ninja Price/Settings/Settings.cs index 310f4a3..4087b95 100644 --- a/Ninja Price/Settings/Settings.cs +++ b/Ninja Price/Settings/Settings.cs @@ -209,4 +209,13 @@ public class SoundNotificationSettings public RangeNode Volume { get; set; } = new(1, 0, 2); public RangeNode ValueThreshold { get; set; } = new(50, 0, 100000); public ToggleNode PlayCustomSoundsIfBelowThreshold { get; set; } = new ToggleNode(true); + public ToggleNode AlertForUniques { get; set; } = new ToggleNode(true); + public ToggleNode AlertForCurrency { get; set; } = new ToggleNode(true); + public ToggleNode AlertForFragments { get; set; } = new ToggleNode(true); + public ToggleNode AlertForMaps { get; set; } = new ToggleNode(true); + public ToggleNode AlertForDivinationCards { get; set; } = new ToggleNode(true); + public ToggleNode AlertForEssences { get; set; } = new ToggleNode(true); + public ToggleNode AlertForGemsAndJewels { get; set; } = new ToggleNode(true); + public ToggleNode AlertForBeasts { get; set; } = new ToggleNode(true); + public ToggleNode AlertForOtherTypes { get; set; } = new ToggleNode(true); }