diff --git a/Main/EvaluateItem.cs b/Main/EvaluateItem.cs index dac0a38..cb871d7 100644 --- a/Main/EvaluateItem.cs +++ b/Main/EvaluateItem.cs @@ -160,11 +160,10 @@ public void Update() var packSize = 0; var quantity = Entity.GetComponent()?.ItemQuality ?? 0; - var settings = new MapNotifySettings(); // get and evaluate mods - var mapComponent = Entity.GetComponent() ?? null; - Tier = mapComponent?.Tier ?? -1; - NeedsPadding = Tier == -1 ? false : true; + // Map component no longer exists in ExileCore + Tier = -1; + NeedsPadding = false; ZanaMissionType = ObjectiveType.None; Bricked = false; Corrupted = Entity.GetComponent()?.isCorrupted ?? false; @@ -218,7 +217,8 @@ public void Update() if (WarningDictionary.Where(x => mod.RawName.Contains(x.Key)).Any()) { var warning = WarningDictionary.Where(x => mod.RawName.Contains(x.Key)).FirstOrDefault().Value; - if (warning.Bricking || settings.ShowQuantityPercent && quantity <= settings.MapQuantSetting || settings.ShowPackSizePercent && packSize <= settings.MapPackSetting) + // only mods flagged as bricking (";true" in the warnings config) brick a map + if (warning.Bricking) { Bricked = true; } @@ -302,26 +302,15 @@ void UpdateValueIfStatExists(string key, Action updateAction) !ClassID.Contains("MiscMapItem") && !ClassID.Contains("MapFragment")) { - var area = mapComponent.Area; + // Map component no longer exists - using basic name extraction var mapTrim = Entity.GetComponent().Name.Replace(" Map", ""); - if (modsComponent.ItemRarity == ItemRarity.Unique) - { - // normal map at inner, 0x18, 0x18 - var mapUnique = gameController.IngameState.M.Read(mapComponent.Address + 0x10, 0x10, 0x20); - area = gameController.Files.WorldAreas.GetByAddress(mapUnique) ?? area; - mapTrim = area.Name; - } - MapName = $"[T{mapComponent.Tier}] {mapTrim}"; - - - Bonus = ingameState.ServerData.BonusCompletedAreas.Contains(area) ? true : false; - Completed = ingameState.ServerData.CompletedAreas.Contains(area) ? true : false; - mavenDetails.MavenCompletion = ingameState.ServerData.MavenWitnessedAreas.Contains(area) ? true : false; - - if (AreaRegion.TryGetValue(mapTrim, out var region)) - MapRegion = region; - else - MapRegion = "Unknown Region"; + MapName = mapTrim; + MapRegion = "Unknown Region"; + + // ServerData properties for completion tracking no longer exist + Bonus = false; + Completed = false; + mavenDetails.MavenCompletion = false; } if (Entity.Path.Contains("MavenMap")) diff --git a/Main/MapNotify.cs b/Main/MapNotify.cs index f691e76..b752916 100644 --- a/Main/MapNotify.cs +++ b/Main/MapNotify.cs @@ -8,9 +8,9 @@ using ExileCore.Shared.Helpers; using ImGuiNET; using SharpDX; +using System; using System.Collections.Generic; using System.Linq; -using Map = ExileCore.PoEMemory.Components.Map; using nuVector2 = System.Numerics.Vector2; using nuVector4 = System.Numerics.Vector4; @@ -23,6 +23,9 @@ public partial class MapNotify : BaseSettingsPlugin private static IngameState ingameState; public static Dictionary WarningDictionary; public static Dictionary BadModsDictionary; + public static MapNotifySettings PluginSettings; + private static string searchFilter = string.Empty; + private static int lastSearchMatchCount; private CachedValue> _inventoryItems; private CachedValue<(int stashIndex, List)> _stashItems; @@ -41,8 +44,13 @@ private List GetInventoryItems() { foreach (var it in visible) { - if (it?.Item != null && it.Item.HasComponent()) - result.Add(it); + if (it?.Item != null) + { + var baseType = gameController.Files.BaseItemTypes.Translate(it.Item.Path); + var classID = baseType?.ClassName ?? string.Empty; + if (classID.Contains("Map") || it.Item.Path.Contains("Map")) + result.Add(it); + } } } } @@ -63,8 +71,13 @@ private List GetInventoryItems() { foreach (var it in visibleInv) { - if (it?.Item != null && it.Item.HasComponent()) - result.Add(it); + if (it?.Item != null) + { + var baseType = gameController.Files.BaseItemTypes.Translate(it.Item.Path); + var classID = baseType?.ClassName ?? string.Empty; + if (classID.Contains("Map") || it.Item.Path.Contains("Map")) + result.Add(it); + } } } } @@ -80,6 +93,7 @@ public override bool Initialise() BadModsDictionary = LoadConfigBadMod(); gameController = GameController; ingameState = gameController.IngameState; + PluginSettings = Settings; _inventoryItems = new TimeCache>(GetInventoryItems, Settings.InventoryCacheInterval); _stashItems = new TimeCache<(int stashIndex, List)>(GetStashItems, Settings.StashCacheInterval); return true; @@ -144,7 +158,6 @@ public override bool Initialise() public void RenderItem(NormalInventoryItem Item, Entity Entity, bool isInventory = false, int mapNum = 0) { - var pushedColors = 0; var entity = Entity; var item = Item; if (entity.Address != 0 && entity.IsValid) @@ -153,7 +166,7 @@ public void RenderItem(NormalInventoryItem Item, Entity Entity, bool isInventory var baseType = gameController.Files.BaseItemTypes.Translate(entity.Path); var classID = baseType.ClassName ?? string.Empty; // Not map, heist or watchstone or normal rarity heist - if (!entity.HasComponent() && !classID.Equals(string.Empty) && + if (!classID.Contains("Map") && !classID.Equals(string.Empty) && !entity.Path.Contains("BreachFragment") && !entity.Path.Contains("CurrencyElderFragment") && !entity.Path.Contains("ShaperFragment") && @@ -211,190 +224,390 @@ public void RenderItem(NormalInventoryItem Item, Entity Entity, bool isInventory } - // create the imgui faux tooltip - var _opened = true; - // Color background - pushedColors += 1; - ImGui.PushStyleColor(ImGuiCol.WindowBg, 0xFF3F3F3F); - if (ImGui.Begin($"{entity.Address}", ref _opened, - ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | - ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoNavInputs)) + // ---------- build card content (LootLens minimal style) ---------- + var white = new nuVector4(1f, 1f, 1f, 1f); + var red = new nuVector4(1f, 0f, 0f, 1f); + var mavenPurple = new nuVector4(0.9f, 0f, 0.77f, 1f); + var bossDone = new nuVector4(0f, 1f, 0f, 1f); + var bossPending = new nuVector4(1f, 0.8f, 0.8f, 1f); + + string headerText = null; + var headerColor = ItemDetails.ItemColor; + var subParts = new List<(string Text, nuVector4 Color)>(); + var rows = new List<(string Text, nuVector4 Color, bool Accent)>(); + + var isHeistOrStone = classID.Contains("HeistContract") || classID.Contains("HeistBlueprint") || classID.Contains("AtlasRegionUpgradeItem"); + var isMavenItem = classID.Contains("QuestItem") || classID.Contains("MiscMapItem") || classID.Contains("MapFragment"); + + if (!isHeistOrStone && !isMavenItem && (isInventory || Settings.ShowMapName)) { - ImGui.BeginGroup(); - if (!classID.Contains("HeistContract") && - !classID.Contains("MapFragment") && - !classID.Contains("HeistBlueprint") && - !classID.Contains("AtlasRegionUpgradeItem") && - !classID.Contains("QuestItem") && !classID.Contains("MiscMapItem")) + // map only stuff, zana always needs to show name for ease + headerText = ItemDetails.MapName; + if (!ItemDetails.LacksCompletion && Settings.ShowCompletion) { - // map only stuff, zana always needs to show name for ease - if (isInventory || Settings.ShowMapName) + if (!ItemDetails.Completed) { - if (ItemDetails.LacksCompletion || !Settings.ShowCompletion) - ImGui.TextColored(ItemDetails.ItemColor, $"{ItemDetails.MapName}"); - else - { - ImGui.TextColored(ItemDetails.ItemColor, $"{ItemDetails.MapName}"); - if (!ItemDetails.Completed) - { - ImGui.TextColored(new nuVector4(1f, 0f, 0f, 1f), $"C"); - ImGui.SameLine(); ImGui.TextColored(new nuVector4(1f, 0f, 0f, 1f), $"B"); - } - else - { - if (!ItemDetails.Bonus) - { - ImGui.TextColored(new nuVector4(1f, 0f, 0f, 1f), $"B"); - } - } - if (ItemDetails.MavenDetails.MavenCompletion) - { - ImGui.TextColored(new nuVector4(0.9f, 0f, 0.77f, 1f), $"Witnessed"); - } - if (ItemDetails.MavenDetails.MavenUncharted) - { - ImGui.TextColored(new nuVector4(0.0f, 0.9f, 0.77f, 1f), $"Uncharted"); - } - ImGui.PushStyleColor(ImGuiCol.Separator, new nuVector4(1f, 1f, 1f, 0.2f)); - pushedColors += 1; - } - if (Settings.ShowMapRegion) - { - var regionColor = new nuVector4(1f, 1f, 1f, 1f); - if (Settings.TargetRegions && CheckRegionTarget(ItemDetails.MapRegion)) - regionColor = new nuVector4(1f, 0f, 1f, 1f); - ImGui.TextColored(regionColor, $"{ItemDetails.MapRegion}"); - } + subParts.Add(("C", red)); + subParts.Add(("B", red)); } + else if (!ItemDetails.Bonus) + subParts.Add(("B", red)); + if (ItemDetails.MavenDetails.MavenCompletion) + subParts.Add(("Witnessed", mavenPurple)); + if (ItemDetails.MavenDetails.MavenUncharted) + subParts.Add(("Uncharted", new nuVector4(0.0f, 0.9f, 0.77f, 1f))); } - - // Maven boss list - if (classID.Contains("QuestItem") || classID.Contains("MiscMapItem") || classID.Contains("MapFragment")) + if (ItemDetails.Bricked) + subParts.Add(("BRICKED", red)); + if (Settings.ShowMapRegion) { - ImGui.TextColored(new nuVector4(0.9f, 0f, 0.77f, 1f), $"{ItemDetails.MapName}"); - if (!Settings.NonUnchartedList && !Entity.Path.Contains("MavenMapVoid") && !Entity.Path.Contains("MapFragment")) - ImGui.TextColored(new nuVector4(0f, 1f, 0f, 1f), $"{ItemDetails.MavenDetails.MavenBosses.Count} Bosses Witnessed"); - else - { - foreach (var boss in ItemDetails.MavenDetails.MavenBosses) - if (boss.Complete) - ImGui.TextColored(new nuVector4(0f, 1f, 0f, 1f), $"{boss.Boss}"); - else - ImGui.TextColored(new nuVector4(1f, 0.8f, 0.8f, 1f), $"{boss.Boss}"); - } + var regionColor = white; + if (Settings.TargetRegions && CheckRegionTarget(ItemDetails.MapRegion)) + regionColor = new nuVector4(1f, 0f, 1f, 1f); + subParts.Add((ItemDetails.MapRegion, regionColor)); } - else if (ItemDetails.MavenDetails.MavenRegion != string.Empty && Input.GetKeyState(System.Windows.Forms.Keys.Menu)) + } + + // Maven boss list + if (isMavenItem) + { + headerText = ItemDetails.MapName; + headerColor = mavenPurple; + if (!Settings.NonUnchartedList && !Entity.Path.Contains("MavenMapVoid") && !Entity.Path.Contains("MapFragment")) + rows.Add(($"{ItemDetails.MavenDetails.MavenBosses.Count} Bosses Witnessed", bossDone, false)); + else foreach (var boss in ItemDetails.MavenDetails.MavenBosses) - if (boss.Complete) - ImGui.TextColored(new nuVector4(0f, 1f, 0f, 1f), $"{boss.Boss}"); - else - ImGui.TextColored(new nuVector4(1f, 0.8f, 0.8f, 1f), $"{boss.Boss}"); + rows.Add((boss.Boss, boss.Complete ? bossDone : bossPending, false)); + } + else if (ItemDetails.MavenDetails.MavenRegion != string.Empty && Input.GetKeyState(System.Windows.Forms.Keys.Menu)) + foreach (var boss in ItemDetails.MavenDetails.MavenBosses) + rows.Add((boss.Boss, boss.Complete ? bossDone : bossPending, false)); - // Zana Mod - if (isInventory) - { - var bCol = GetObjectiveColor(ItemDetails.ZanaMissionType); - if (bCol.HasValue) - if (Settings.StyleTextForBorder) - ImGui.TextColored(bCol.Value, $"{ItemDetails.ZanaMod?.Text ?? "Zana Mod was null!"}"); - else - ImGui.TextColored(Settings.DefaultBorderTextColor, $"{ItemDetails.ZanaMod?.Text ?? "Zana Mod was null!"}"); - else - ImGui.TextColored(new nuVector4(0.9f, 0.85f, 0.65f, 1f), $"{ItemDetails.ZanaMod?.Text ?? "Zana Mod was null!"}"); - } + // Zana Mod + if (isInventory) + { + var zCol = GetObjectiveColor(ItemDetails.ZanaMissionType); + var zanaText = ItemDetails.ZanaMod?.Text ?? "Zana Mod was null!"; + if (zCol.HasValue) + rows.Add((zanaText, Settings.StyleTextForBorder ? zCol.Value : Settings.DefaultBorderTextColor, false)); + else + rows.Add((zanaText, new nuVector4(0.9f, 0.85f, 0.65f, 1f), false)); + } - // Quantity and Packsize for maps - if (!classID.Contains("HeistContract") && !classID.Contains("HeistBlueprint") && !classID.Contains("AtlasRegionUpgradeItem")) + // Quantity and Packsize for maps + if (!isHeistOrStone) + { + var qCol = white; + if (Settings.ColorQuantityPercent) + qCol = ItemDetails.Quantity < Settings.ColorQuantity + ? new nuVector4(1f, 0.4f, 0.4f, 1f) + : new nuVector4(0.4f, 1f, 0.4f, 1f); + if (Settings.ShowQuantityPercent && ItemDetails.Quantity != 0 && Settings.ShowPackSizePercent && ItemDetails.PackSize != 0) + rows.Add(($"{ItemDetails.Quantity}% Quant · {ItemDetails.PackSize}% Pack Size", qCol, false)); + else if (Settings.ShowQuantityPercent && ItemDetails.Quantity != 0) + rows.Add(($"{ItemDetails.Quantity}% Quantity", qCol, false)); + else if (Settings.ShowPackSizePercent && ItemDetails.PackSize != 0) + rows.Add(($"{ItemDetails.PackSize}% Pack Size", white, false)); + } + + // Count Mods + if (Settings.ShowModCount && ItemDetails.ModCount != 0 && !classID.Contains("AtlasRegionUpgradeItem")) + { + var corrupted = entity.GetComponent().isCorrupted; + var shownCount = isInventory ? ItemDetails.ModCount - 1 : ItemDetails.ModCount; + rows.Add((corrupted ? $"{shownCount} Mods, Corrupted" : $"{shownCount} Mods", corrupted ? red : white, false)); + } + + // Mod StyledTexts, separated from the info rows by a muted line + var separatorIndex = -1; + if (Settings.ShowModWarnings && ItemDetails.ActiveWarnings.Count > 0) + { + if (Settings.HorizontalLines && !isHeistOrStone && rows.Count > 0 && + (Settings.ShowLineForZanaMaps && isInventory || !isInventory)) + separatorIndex = rows.Count; + foreach (var styled in ItemDetails.ActiveWarnings.OrderBy(x => x.Color.ToString()).ToList()) + rows.Add((styled.Text, SharpToNu(styled.Color), true)); + } + + if (headerText == null && subParts.Count == 0 && rows.Count == 0) return; + + // ---------- layout ---------- + var font = ImGui.GetFont(); + var baseFontSize = ImGui.GetFontSize(); + var uiScale = Settings.TooltipScale.Value / 100f; + var pad = 10f * uiScale; + var headerScale = 1.02f * uiScale; + var subScale = 0.70f * uiScale; + var rowScale = 0.79f * uiScale; + var rowHeight = baseFontSize * rowScale + 7f * uiScale; + + var width = 150f * uiScale; + if (headerText != null) + width = Math.Max(width, ImGui.CalcTextSize(headerText).X * headerScale + pad * 2); + if (subParts.Count > 0) + { + var subWidth = 0f; + foreach (var part in subParts) + subWidth += ImGui.CalcTextSize(part.Text).X * subScale + 8f * uiScale; + width = Math.Max(width, subWidth + pad * 2); + } + foreach (var row in rows) + width = Math.Max(width, ImGui.CalcTextSize(row.Text).X * rowScale + pad * 2 + (row.Accent ? 8f * uiScale : 0f)); + width = Math.Min(width, 420f * uiScale); + + var headerHeight = 0f; + if (headerText != null) + headerHeight = baseFontSize * headerScale + 6f * uiScale + (subParts.Count > 0 ? baseFontSize * subScale + 6f * uiScale : 0f); + var height = 6f * uiScale + headerHeight + rows.Count * rowHeight + (separatorIndex >= 0 ? 7f * uiScale : 0f) + 6f * uiScale; + + // padding when parsing an inventory + if (isInventory) + { + boxSize.X += width + 2; + if (maxSize < height) + maxSize = height; + lastCol = mapNum; + } + + // Detect and adjust for edges + var origin = boxOrigin; + if (origin.X + width > windowArea.Width) + origin = new nuVector2(boxOrigin.X - (boxOrigin.X + width - windowArea.Width) - 4, boxOrigin.Y + 24); + + // card border colour: bricked, zana objective or completion state + nuVector4? borderCol = null; + if (ItemDetails.Bricked || classID.Contains("Map") && (isInventory || Settings.AlwaysShowCompletionBorder)) + { + var bcol = GetObjectiveColor(ItemDetails.ZanaMissionType); + if (ItemDetails.Bricked) + borderCol = Settings.Bricked; + else if (ItemDetails.ZanaMissionType != ObjectiveType.None && bcol.HasValue) + borderCol = bcol.Value; + else if (Settings.CompletionBorder && !ItemDetails.Completed) + borderCol = Settings.Incomplete; + else if (Settings.CompletionBorder && !ItemDetails.Bonus) + borderCol = Settings.BonusIncomplete; + else if (Settings.CompletionBorder && !ItemDetails.Awakened) + borderCol = Settings.AwakenedIncomplete; + else if (isInventory) + borderCol = new nuVector4(0.29f, 0.29f, 0.29f, 1f); + } + + // ---------- draw the card ---------- + var draw = ImGui.GetForegroundDrawList(); + var topLeft = origin; + var bottomRight = new nuVector2(origin.X + width, origin.Y + height); + const float rounding = 7f; + draw.AddRectFilled(topLeft + new nuVector2(3f, 4f), bottomRight + new nuVector2(3f, 4f), ColorToUint(new nuVector4(0f, 0f, 0f, 0.53f)), rounding); + draw.AddRectFilled(topLeft, bottomRight, ColorToUint(new nuVector4(0f, 0f, 0f, 0.93f)), rounding); + if (borderCol.HasValue) + draw.AddRect(topLeft, bottomRight, ColorToUint(borderCol.Value), rounding, ImDrawFlags.None, Settings.BorderThickness.Value); + + draw.PushClipRect(topLeft + new nuVector2(1f, 1f), bottomRight - new nuVector2(1f, 1f), true); + var cy = origin.Y + 6f * uiScale; + if (headerText != null) + { + draw.AddText(font, baseFontSize * headerScale, new nuVector2(origin.X + pad, cy), ColorToUint(headerColor), headerText); + cy += baseFontSize * headerScale + 6f * uiScale; + if (subParts.Count > 0) { - // Quantity and Pack Size - var qCol = new nuVector4(1f, 1f, 1f, 1f); - if (Settings.ColorQuantityPercent) - if - (ItemDetails.Quantity < Settings.ColorQuantity) qCol = new nuVector4(1f, 0.4f, 0.4f, 1f); - else - qCol = new nuVector4(0.4f, 1f, 0.4f, 1f); - if (Settings.ShowQuantityPercent && ItemDetails.Quantity != 0 && Settings.ShowPackSizePercent && ItemDetails.PackSize != 0) + var sx = origin.X + pad; + foreach (var part in subParts) { - ImGui.TextColored(qCol, $"{ItemDetails.Quantity}%% Quant"); - ImGui.SameLine(); - ImGui.TextColored(new nuVector4(1f, 1f, 1f, 1f), $"{ItemDetails.PackSize}%% Pack Size"); + draw.AddText(font, baseFontSize * subScale, new nuVector2(sx, cy), ColorToUint(part.Color), part.Text); + sx += ImGui.CalcTextSize(part.Text).X * subScale + 8f * uiScale; } - else if (Settings.ShowQuantityPercent && ItemDetails.Quantity != 0) - ImGui.TextColored(qCol, $"{ItemDetails.Quantity}%% Quantity"); - else if (Settings.ShowPackSizePercent && ItemDetails.PackSize != 0) - ImGui.TextColored(new nuVector4(1f, 1f, 1f, 1f), $"{ItemDetails.PackSize}%% Pack Size"); + cy += baseFontSize * subScale + 6f * uiScale; + } + } - // Separator - if (Settings.HorizontalLines && ItemDetails.ActiveWarnings.Count > 0 && (Settings.ShowModCount || Settings.ShowModWarnings)) - { - if (Settings.ShowLineForZanaMaps && isInventory || !isInventory) - ImGui.Separator(); - } + for (var i = 0; i < rows.Count; i++) + { + if (i == separatorIndex) + { + draw.AddLine(new nuVector2(origin.X + pad, cy + 3f), new nuVector2(bottomRight.X - pad, cy + 3f), ColorToUint(new nuVector4(0.25f, 0.27f, 0.30f, 0.63f)), 1f); + cy += 7f * uiScale; } - // Count Mods - if (Settings.ShowModCount && ItemDetails.ModCount != 0 && !classID.Contains("AtlasRegionUpgradeItem")) - if (entity.GetComponent().isCorrupted) - ImGui.TextColored(new nuVector4(1f, 0f, 0f, 1f), $"{(isInventory ? ItemDetails.ModCount - 1 : ItemDetails.ModCount)} Mods, Corrupted"); - else - ImGui.TextColored(new nuVector4(1f, 1f, 1f, 1f), $"{(isInventory ? ItemDetails.ModCount - 1 : ItemDetails.ModCount)} Mods"); - - // Mod StyledTexts - if (Settings.ShowModWarnings) - foreach (var StyledText in ItemDetails.ActiveWarnings.OrderBy(x => x.Color.ToString()).ToList()) - ImGui.TextColored(SharpToNu(StyledText.Color), StyledText.Text); - ImGui.EndGroup(); - - // border for most notable maps in inventory - if (ItemDetails.Bricked || entity.HasComponent() && (isInventory || Settings.AlwaysShowCompletionBorder)) + var row = rows[i]; + var textX = origin.X + pad; + if (row.Accent) { - var min = ImGui.GetItemRectMin(); - min.X -= 8; - min.Y -= 8; - var max = ImGui.GetItemRectMax(); - max.X += 8; - max.Y += 8; - var bcol = GetObjectiveColor(ItemDetails.ZanaMissionType); - - if (ItemDetails.Bricked) - ImGui.GetForegroundDrawList().AddRect(min, max, ColorToUint(Settings.Bricked), 0f, 0, Settings.BorderThickness.Value); - else if (ItemDetails.ZanaMissionType != ObjectiveType.None && bcol.HasValue) - ImGui.GetForegroundDrawList().AddRect(min, max, ColorToUint(bcol.Value), 0f, 0, Settings.BorderThickness.Value); - else if (Settings.CompletionBorder && !ItemDetails.Completed) - ImGui.GetForegroundDrawList().AddRect(min, max, ColorToUint(Settings.Incomplete)); - else if (Settings.CompletionBorder && !ItemDetails.Bonus) - ImGui.GetForegroundDrawList().AddRect(min, max, ColorToUint(Settings.BonusIncomplete)); - else if (Settings.CompletionBorder && !ItemDetails.Awakened) - ImGui.GetForegroundDrawList().AddRect(min, max, ColorToUint(Settings.AwakenedIncomplete)); - else if (isInventory) - ImGui.GetForegroundDrawList().AddRect(min, max, 0xFF4A4A4A); - + // coloured accent bar in front of warning rows, like LootLens mod rows + draw.AddRectFilled(new nuVector2(textX, cy + 2f), new nuVector2(textX + 2f, cy + rowHeight - 2f), ColorToUint(row.Color), 1f); + textX += 8f * uiScale; } + draw.AddText(font, baseFontSize * rowScale, new nuVector2(textX, cy + 2f), ColorToUint(row.Color), row.Text); + cy += rowHeight; + } + draw.PopClipRect(); + } + } + } - // Detect and adjust for edges - var size = ImGui.GetWindowSize(); - var pos = ImGui.GetWindowPos(); - if (boxOrigin.X + size.X > windowArea.Width) - ImGui.SetWindowPos(new nuVector2(boxOrigin.X - (boxOrigin.X + size.X - windowArea.Width) - 4, boxOrigin.Y + 24), ImGuiCond.Always); - else - ImGui.SetWindowPos(boxOrigin, ImGuiCond.Always); + private static bool ItemMatchesSearch(ItemDetails itemDetails, Entity entity, out List matchedMods) + { + matchedMods = new List(); + var filters = new List(); + if (!string.IsNullOrWhiteSpace(searchFilter)) filters.Add(searchFilter.Trim()); + var saved = PluginSettings?.SavedSearchFilters; + if (saved != null) + foreach (var f in saved) + if (!string.IsNullOrWhiteSpace(f)) + filters.Add(f.Trim()); + if (filters.Count == 0) return false; + + var matched = false; + var baseName = entity?.GetComponent()?.Name; + var itemMods = entity?.GetComponent()?.ItemMods; + var styledMods = (itemDetails.ActiveWarnings ?? new List()) + .Concat(itemDetails.ActiveBadMods ?? new List()); + foreach (var filter in filters) + { + bool Has(string text) => !string.IsNullOrEmpty(text) && text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; + + if (Has(itemDetails.MapName) || Has(baseName)) + matched = true; + + foreach (var styled in styledMods) + if (Has(styled.Text)) + { + matched = true; + if (!matchedMods.Any(x => x.Text == styled.Text)) + matchedMods.Add(styled); + } - // padding when parsing an inventory - if (isInventory) + if (itemMods == null) continue; + foreach (var mod in itemMods) + { + if (!Has(mod.RawName)) continue; + matched = true; + // prefer the friendly warning text/colour for the label if one is configured + var display = WarningDictionary.FirstOrDefault(w => mod.RawName.Contains(w.Key)).Value + ?? new StyledText { Text = mod.RawName, Color = new Vector4(1f, 1f, 1f, 1f) }; + if (!matchedMods.Any(x => x.Text == display.Text)) + matchedMods.Add(display); + } + } + return matched; + } + + private void DrawSearchMatchLabels(RectangleF itemRect, List matchedMods) + { + if (matchedMods.Count == 0) return; + var totalHeight = 0f; + var sizes = new nuVector2[matchedMods.Count]; + for (var i = 0; i < matchedMods.Count; i++) + { + sizes[i] = Graphics.MeasureText(matchedMods[i].Text); + totalHeight += sizes[i].Y; + } + + var y = Math.Max(itemRect.Top - totalHeight - 2f, 0f); + for (var i = 0; i < matchedMods.Count; i++) + { + var size = sizes[i]; + var pos = new nuVector2(itemRect.Center.X - size.X / 2f, y); + Graphics.DrawBox(pos - new nuVector2(2f, 0f), pos + new nuVector2(size.X + 2f, size.Y), new Color(0f, 0f, 0f, 0.85f)); + var c = matchedMods[i].Color; + Graphics.DrawText(matchedMods[i].Text, pos, new Color(c.X, c.Y, c.Z, c.W)); + y += size.Y; + } + } + + private void DrawInventorySearch() + { + if (!Settings.ShowInventorySearch) return; + var playerInv = ingameState.IngameUi.InventoryPanel[InventoryIndex.PlayerInventory]; + if (playerInv == null) return; + Settings.SavedSearchFilters ??= new List(); + var invRect = playerInv.GetClientRect(); + + // default position above the inventory grid; the window is movable, drag any empty spot + if (Settings.SearchBoxX <= 0f && Settings.SearchBoxY <= 0f) + { + Settings.SearchBoxX = invRect.X; + Settings.SearchBoxY = Math.Max(invRect.Y - 70f, 0f); + } + ImGui.SetNextWindowPos(new nuVector2(Settings.SearchBoxX, Settings.SearchBoxY), ImGuiCond.FirstUseEver); + ImGui.PushStyleColor(ImGuiCol.WindowBg, 0xFF3F3F3F); + if (ImGui.Begin("##MapNotifySearch", + ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | + ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings | + ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoFocusOnAppearing)) + { + var winPos = ImGui.GetWindowPos(); + Settings.SearchBoxX = winPos.X; + Settings.SearchBoxY = winPos.Y; + + ImGui.TextDisabled("::"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(180f); + if (ImGui.InputTextWithHint("##MapNotifySearchInput", "search maps... (Enter saves)", ref searchFilter, 64, + ImGuiInputTextFlags.EnterReturnsTrue)) + { + var trimmed = searchFilter.Trim(); + if (trimmed.Length > 0 && + !Settings.SavedSearchFilters.Any(x => x.Equals(trimmed, StringComparison.OrdinalIgnoreCase))) + Settings.SavedSearchFilters.Add(trimmed); + searchFilter = string.Empty; + ImGui.SetKeyboardFocusHere(-1); + } + if (!string.IsNullOrWhiteSpace(searchFilter) || Settings.SavedSearchFilters.Count > 0) + { + ImGui.SameLine(); + ImGui.TextColored(Settings.SearchHighlight, $"{lastSearchMatchCount} found"); + } + + if (Settings.SavedSearchFilters.Count > 0) + { + var removeIndex = -1; + if (Settings.SavedSearchFilters.Count > 2) + { + // more than 2 saved filters: collapse them into a popout so the bar stays compact + if (ImGui.SmallButton($"Filters ({Settings.SavedSearchFilters.Count})##MapNotifyFilterList")) + ImGui.OpenPopup("MapNotifyFilterPopup"); + ImGui.SameLine(); + if (ImGui.SmallButton("Clear All##MapNotifyFilterClearAll")) { - boxSize.X += (int)size.X + 2; - if (maxSize < size.Y) - maxSize = size.Y; - lastCol = mapNum; + Settings.SavedSearchFilters.Clear(); + searchFilter = string.Empty; + } + if (ImGui.BeginPopup("MapNotifyFilterPopup")) + { + // one filter per line, click to remove it + for (var i = 0; i < Settings.SavedSearchFilters.Count; i++) + if (ImGui.SmallButton($"{Settings.SavedSearchFilters[i]} x##MapNotifyFilter{i}")) + removeIndex = i; + ImGui.EndPopup(); } } - ImGui.End(); - ImGui.PopStyleColor(pushedColors); + else + { + // one small button per saved filter, click to remove it + for (var i = 0; i < Settings.SavedSearchFilters.Count; i++) + { + if (i > 0) ImGui.SameLine(); + if (ImGui.SmallButton($"{Settings.SavedSearchFilters[i]} x##MapNotifyFilter{i}")) + removeIndex = i; + } + ImGui.SameLine(); + if (ImGui.SmallButton("Clear All##MapNotifyFilterClearAll")) + { + Settings.SavedSearchFilters.Clear(); + searchFilter = string.Empty; + } + } + if (removeIndex >= 0 && removeIndex < Settings.SavedSearchFilters.Count) + Settings.SavedSearchFilters.RemoveAt(removeIndex); } } + ImGui.End(); + ImGui.PopStyleColor(); } public override void Render() { + var searchMatches = 0; if (ingameState.IngameUi.Atlas.IsVisible) AtlasRender(); @@ -431,9 +644,12 @@ public override void Render() if (ingameState.IngameUi.InventoryPanel.IsVisible) { + DrawInventorySearch(); foreach (var item in _inventoryItems.Value) { - if (!item.Item.HasComponent()) + var baseType = gameController.Files.BaseItemTypes.Translate(item.Item.Path); + var classID = baseType?.ClassName ?? string.Empty; + if (!classID.Contains("Map") && !item.Item.Path.Contains("Map")) continue; var rect = item.GetClientRectCache; @@ -446,36 +662,55 @@ public override void Render() var itemDetails = item.Item.GetHudComponent() ?? new ItemDetails(item, item.Item); item.Item.SetHudComponent(itemDetails); - // Checking if we have Warning Setting or Bad Mods - if (Settings.BoxForMapWarnings) + // Bricked maps always draw red, never the green warnings border + if (Settings.BoxForBricked && itemDetails.Bricked) + { + if (Settings.MapBorderStyle) + Graphics.DrawBox(rect, Settings.Bricked.ToSharpColor(), Settings.BorderThicknessMap); + else + Graphics.DrawFrame(rect, Settings.Bricked.ToSharpColor(), Settings.BorderThicknessMap); + } + else { - // Check if the item is "bricked" or has certain mods with warnings - if ((itemDetails.Bricked || itemDetails.ModCount > 0) && item.Item.GetComponent()?.ItemMods - .Where(x => !x.Group.Contains("MapAtlasInfluence")) - .Any(mod => WarningDictionary.Any(warning => mod.RawName.Contains(warning.Key))) == true) + // Checking if we have Warning Setting or Bad Mods + if (Settings.BoxForMapWarnings) { - // Draw a red frame around the item's bounding rectangle - if (Settings.MapBorderStyle) - Graphics.DrawBox(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); - else - Graphics.DrawFrame(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); + // green border only for non-bricked maps at or above the warn-below quantity percentage + if (!itemDetails.Bricked && itemDetails.Quantity >= Settings.ColorQuantity && + itemDetails.ModCount > 0 && item.Item.GetComponent()?.ItemMods + .Where(x => !x.Group.Contains("MapAtlasInfluence")) + .Any(mod => WarningDictionary.Any(warning => mod.RawName.Contains(warning.Key))) == true) + { + if (Settings.MapBorderStyle) + Graphics.DrawBox(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); + else + Graphics.DrawFrame(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); + } } - } - if (Settings.BoxForMapBadWarnings) - { - // Check if the item is "bricked" or has certain mods with warnings - if ((itemDetails.Bricked || itemDetails.ModCount > 0) && item.Item.GetComponent()?.ItemMods - .Where(x => !x.Group.Contains("MapAtlasInfluence")) - .Any(mod => BadModsDictionary.Any(bad => mod.RawName.Contains(bad.Key))) == true) + if (Settings.BoxForMapBadWarnings) { - // Draw a red frame around the item's bounding rectangle - if (Settings.MapBorderStyle) - Graphics.DrawBox(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); - else - Graphics.DrawFrame(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); + // Check if the item has certain bad mods + if (itemDetails.ModCount > 0 && item.Item.GetComponent()?.ItemMods + .Where(x => !x.Group.Contains("MapAtlasInfluence")) + .Any(mod => BadModsDictionary.Any(bad => mod.RawName.Contains(bad.Key))) == true) + { + if (Settings.MapBorderStyle) + Graphics.DrawBox(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); + else + Graphics.DrawFrame(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); + } } } + + // Highlight maps matching the search filters and label the matching mods + if (ItemMatchesSearch(itemDetails, item.Item, out var matchedMods)) + { + searchMatches++; + var fullRect = item.GetClientRectCache; + Graphics.DrawFrame(fullRect, Settings.SearchHighlight.ToSharpColor(), Settings.BorderThicknessMap); + DrawSearchMatchLabels(fullRect, matchedMods); + } } } @@ -486,7 +721,9 @@ public override void Render() foreach (var item in _stashItems.Value.Item2) { // Skip the item if it doesn't have a "Map" component - if (!item.Item.HasComponent()) + var baseType = gameController.Files.BaseItemTypes.Translate(item.Item.Path); + var classID = baseType?.ClassName ?? string.Empty; + if (!classID.Contains("Map") && !item.Item.Path.Contains("Map")) continue; // Assuming `item.GetClientRectCache` returns a Rectangle object. @@ -506,39 +743,56 @@ public override void Render() var itemDetails = item.Item.GetHudComponent() ?? new ItemDetails(item, item.Item); item.Item.SetHudComponent(itemDetails); - // Checking if we have Warning Setting or Bad Mods - if(Settings.BoxForMapWarnings) + // Bricked maps always draw red, never the green warnings border + if (Settings.BoxForBricked && itemDetails.Bricked) { - // Check if the item is "bricked" or has certain mods with warnings - if ((itemDetails.Bricked || itemDetails.ModCount > 0) && item.Item.GetComponent()?.ItemMods - .Where(x => !x.Group.Contains("MapAtlasInfluence")) - .Any(mod => WarningDictionary.Any(warning => mod.RawName.Contains(warning.Key))) == true) + if (Settings.MapBorderStyle) + Graphics.DrawBox(rect, Settings.Bricked.ToSharpColor(), Settings.BorderThicknessMap); + else + Graphics.DrawFrame(rect, Settings.Bricked.ToSharpColor(), Settings.BorderThicknessMap); + } + else + { + // Checking if we have Warning Setting or Bad Mods + if (Settings.BoxForMapWarnings) { - // Draw a red frame around the item's bounding rectangle - if (Settings.MapBorderStyle) - Graphics.DrawBox(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); - else - Graphics.DrawFrame(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); + // green border only for non-bricked maps at or above the warn-below quantity percentage + if (!itemDetails.Bricked && itemDetails.Quantity >= Settings.ColorQuantity && + itemDetails.ModCount > 0 && item.Item.GetComponent()?.ItemMods + .Where(x => !x.Group.Contains("MapAtlasInfluence")) + .Any(mod => WarningDictionary.Any(warning => mod.RawName.Contains(warning.Key))) == true) + { + if (Settings.MapBorderStyle) + Graphics.DrawBox(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); + else + Graphics.DrawFrame(rect, Settings.MapBorderWarnings.ToSharpColor(), Settings.BorderThicknessMap); + } } - } - if (Settings.BoxForMapBadWarnings) - { - // Check if the item is "bricked" or has certain mods with warnings - if ((itemDetails.Bricked || itemDetails.ModCount > 0) && item.Item.GetComponent()?.ItemMods - .Where(x => !x.Group.Contains("MapAtlasInfluence")) - .Any(mod => BadModsDictionary.Any(bad => mod.RawName.Contains(bad.Key))) == true) + if (Settings.BoxForMapBadWarnings) { - // Draw a red frame around the item's bounding rectangle - if (Settings.MapBorderStyle) - Graphics.DrawBox(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); - else - Graphics.DrawFrame(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); + // Check if the item has certain bad mods + if (itemDetails.ModCount > 0 && item.Item.GetComponent()?.ItemMods + .Where(x => !x.Group.Contains("MapAtlasInfluence")) + .Any(mod => BadModsDictionary.Any(bad => mod.RawName.Contains(bad.Key))) == true) + { + if (Settings.MapBorderStyle) + Graphics.DrawBox(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); + else + Graphics.DrawFrame(rect, Settings.MapBorderBad.ToSharpColor(), Settings.BorderThicknessMap); + } } } + + // Highlight maps matching the search filters (mod labels are only drawn for inventory maps) + if (ItemMatchesSearch(itemDetails, item.Item, out _)) + { + searchMatches++; + Graphics.DrawFrame(item.GetClientRectCache, Settings.SearchHighlight.ToSharpColor(), Settings.BorderThicknessMap); + } } } - + lastSearchMatchCount = searchMatches; } } diff --git a/MapNotify.csproj b/MapNotify.csproj index e549398..40a07ef 100644 --- a/MapNotify.csproj +++ b/MapNotify.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows Library true x64 @@ -30,6 +30,10 @@ $(exapiPackage)\ExileCore.dll False + + $(exapiPackage)\GameOffsets.dll + False + $(exapiPackage)\ImGui.NET.dll False diff --git a/Settings/DrawSettings.cs b/Settings/DrawSettings.cs index 7dcc026..958125f 100644 --- a/Settings/DrawSettings.cs +++ b/Settings/DrawSettings.cs @@ -147,6 +147,8 @@ public override void DrawSettings() Settings.BoxForBricked.Value = Checkbox("Border on bricked maps in inventory", Settings.BoxForBricked); Settings.BoxForMapWarnings.Value = Checkbox("Border on Map Mod Warnings in inventory and Stash", Settings.BoxForMapWarnings); Settings.BoxForMapBadWarnings.Value = Checkbox("Border on Bad Map Mods in inventory and Stash", Settings.BoxForMapBadWarnings); + Settings.ShowInventorySearch.Value = Checkbox("Show Search Box when Inventory is Open", Settings.ShowInventorySearch); + ImGui.SameLine(); HelpMarker("Adds a search box above your inventory.\nMaps in your inventory and the visible stash tab whose name or mods contain the text get a highlight border.\nPress Enter to save the text as a filter - saved filters stack, persist, and can be removed one by one or with Clear All.\nThe matching mods are shown above each matching map in your inventory."); SelectFile(); ImGui.Text($"Bad Mod Warnings File: {Settings.BadModWarningsLoader.Value}"); // ImGui.SameLine(); HelpMarker("Add ';true' after a line in the config files to mark it as a bricked mod."); @@ -155,6 +157,8 @@ public override void DrawSettings() if (ImGui.TreeNodeEx("Map Tooltip Settings", ImGuiTreeNodeFlags.CollapsingHeader)) { + Settings.TooltipScale.Value = IntSlider("Tooltip Text Size %", Settings.TooltipScale); + ImGui.SameLine(); HelpMarker("Scales the whole map tooltip card, 100 = normal size."); Settings.ShowMapName.Value = Checkbox("Show Map Name", Settings.ShowMapName); Settings.ShowCompletion.Value = Checkbox("Show Completion Status", Settings.ShowCompletion); if (Settings.ShowCompletion) Settings.ShowMapName.Value = true; @@ -220,8 +224,7 @@ public override void DrawSettings() Settings.Bricked = ColorButton("Bricked Map", Settings.Bricked); Settings.MapBorderWarnings = ColorButton("Show MapWarningBorder", Settings.MapBorderWarnings); Settings.MapBorderBad = ColorButton("Show BadMapBorder", Settings.MapBorderBad); - Settings.MapQuantSetting.Value = IntSlider("##MapQuantSetting", Settings.MapQuantSetting); - Settings.MapPackSetting.Value = IntSlider("##MapPackSetting", Settings.MapPackSetting); + Settings.SearchHighlight = ColorButton("Search Match Highlight", Settings.SearchHighlight); Settings.BorderThicknessMap.Value = IntSlider("Border Thickness for maps##BorderThickness Maps", Settings.BorderThicknessMap); } @@ -263,16 +266,8 @@ public override void DrawSettings() if (comp) { - ImGui.Text($"Bonus ({ingameState.ServerData.BonusCompletedAreas.Count}): "); - foreach (var map in ingameState.ServerData.BonusCompletedAreas) - { - ImGui.TextColored(new nuVector4(0.5F, 0.5F, 1.2F, 1F), $"{map.Name}"); - } - ImGui.Text($"Completion ({ingameState.ServerData.CompletedAreas.Count}): "); - foreach (var map in ingameState.ServerData.CompletedAreas) - { - ImGui.TextColored(new nuVector4(0.5F, 0.5F, 1.2F, 1F), $"{map.Name}"); - } + // ServerData.BonusCompletedAreas and CompletedAreas no longer exist in ExileCore + ImGui.Text("Completion tracking is not available in this version of ExileCore"); } DebugHover(); ImGui.Text("Last Hovered item's mods:"); diff --git a/Settings/MapNotifySettings.cs b/Settings/MapNotifySettings.cs index 89c195f..840c7d3 100644 --- a/Settings/MapNotifySettings.cs +++ b/Settings/MapNotifySettings.cs @@ -1,5 +1,6 @@ using ExileCore.Shared.Interfaces; using ExileCore.Shared.Nodes; +using System.Collections.Generic; using Vector4 = System.Numerics.Vector4; namespace MapNotify @@ -17,6 +18,8 @@ public class MapNotifySettings : ISettings public float AtlasX { get; set; } public float AtlasY { get; set; } + public float SearchBoxX { get; set; } + public float SearchBoxY { get; set; } public ToggleNode Enable { get; set; } = new(true); public RangeNode InventoryCacheInterval { get; set; } = new(50, 1, 2000); @@ -52,6 +55,9 @@ public class MapNotifySettings : ISettings public ToggleNode BoxForBricked { get; set; } = new(true); public ToggleNode BoxForMapWarnings { get; set; } = new(true); public ToggleNode BoxForMapBadWarnings { get; set; } = new(true); + public ToggleNode ShowInventorySearch { get; set; } = new(true); + public List SavedSearchFilters { get; set; } = new(); + public RangeNode TooltipScale { get; set; } = new(150, 70, 200); public ListNode BadModWarningsLoader { get; set; } = new(); public Vector4 ElderGuardian { get; set; } = new(0.32f, 0.55f, 0.78f, 1f); @@ -79,6 +85,7 @@ public class MapNotifySettings : ISettings public Vector4 MapBorderWarnings { get; set; } = new(0f, 1f, 0f, 1f); public Vector4 MapBorderBad { get; set; } = new(1f, 0f, 0f, 1f); + public Vector4 SearchHighlight { get; set; } = new(1f, 0.85f, 0f, 1f); public RangeNode BorderThicknessMap { get; set; } = new(2, 1, 6); } }