diff --git a/Modules/PinRenderer.cs b/Modules/PinRenderer.cs index cd7dae6..f231816 100644 --- a/Modules/PinRenderer.cs +++ b/Modules/PinRenderer.cs @@ -1,9 +1,13 @@ -using DieselExileTools; +using System; +using DieselExileTools; using GameHelper; using GameHelper.RemoteEnums; using GameHelper.RemoteObjects.Components; using GameHelper.RemoteObjects.States.InGameStateObjects; using ImGuiNET; +using Newtonsoft.Json; +using System.IO; +using System.Linq; using System.Numerics; using System.Text.RegularExpressions; using SColor = System.Drawing.Color; @@ -174,9 +178,10 @@ private void RenderPins() { } } } - public void UpdateMatchedPins(bool force = false) { + public void UpdateMatchedPins(bool force = false, bool allowLearn = true) { if (_areaInstance == null) return; if (_pathFinder == null) return; + if (LoadedPins == null || LoadedPins.Count == 0) return; var player = _areaInstance.Player; if (!player.TryGetComponent(out var playerRender)) return; @@ -223,6 +228,12 @@ public void UpdateMatchedPins(bool force = false) { } } + bool hasRenderablePins = newMatches.Any(match => match.Pin.Enabled && match.TilePositions.Count > 0); + if (allowLearn && !hasRenderablePins && TryLearnBossPin()) { + UpdateMatchedPins(true, false); + return; + } + // If counts differ, mark as changed @@ -251,6 +262,98 @@ public void UpdateMatchedPins(bool force = false) { } } + private bool TryLearnBossPin() { + if (!Settings.Pin.LearnMapBossArena) return false; + if (_areaInstance == null) return false; + if (LoadedPins == null) return false; + if (string.IsNullOrEmpty(_currentArea)) return false; + if (string.IsNullOrWhiteSpace(Settings.Pin.SelectedFilename)) return false; + + var matchingTiles = new List<(string Key, SVector2 Position)>(); + + foreach (var tileEntry in _areaInstance.TgtTilesLocations) { + if (string.IsNullOrEmpty(tileEntry.Key)) continue; + if (!tileEntry.Key.Contains("boss", StringComparison.OrdinalIgnoreCase) && + !tileEntry.Key.Contains("arena", StringComparison.OrdinalIgnoreCase)) continue; + + foreach (var position in tileEntry.Value) { + matchingTiles.Add((tileEntry.Key, position)); + } + } + + if (matchingTiles.Count == 0) return false; + + var center = new SVector2( + (float)matchingTiles.Average(tile => tile.Position.X), + (float)matchingTiles.Average(tile => tile.Position.Y)); + + var bestTile = matchingTiles + .OrderBy(tile => Vector2.DistanceSquared(tile.Position, center)) + .First(); + + if (!LoadedPins.TryGetValue(_currentArea, out var areaPins)) { + areaPins = new List(); + LoadedPins[_currentArea] = areaPins; + } + + if (areaPins.Any(pin => string.Equals(pin.Label, "Boss", StringComparison.OrdinalIgnoreCase))) { + return false; + } + + var existingPin = areaPins + .FirstOrDefault(pin => string.Equals(pin.Path, bestTile.Key, StringComparison.OrdinalIgnoreCase)); + if (existingPin != null) { + if (string.Equals(existingPin.Label, "Boss", StringComparison.OrdinalIgnoreCase)) { + return false; + } + + var previousLabel = existingPin.Label; + existingPin.Label = "Boss"; + if (SaveLoadedPinsToFile()) { + DXT.Log($"Learned Boss pin for area {_currentArea}: {bestTile.Key}", false); + return true; + } + + existingPin.Label = previousLabel; + return false; + } + + var newPin = new Pin { + Path = bestTile.Key, + Label = "Boss", + }; + + areaPins.Add(newPin); + if (SaveLoadedPinsToFile()) { + DXT.Log($"Learned Boss pin for area {_currentArea}: {bestTile.Key}", false); + return true; + } + + areaPins.Remove(newPin); + if (areaPins.Count == 0) { + LoadedPins.Remove(_currentArea); + } + return false; + } + + private bool SaveLoadedPinsToFile() { + if (LoadedPins == null) return false; + var fileName = Settings.Pin.SelectedFilename; + if (string.IsNullOrWhiteSpace(fileName)) return false; + + try { + Directory.CreateDirectory(Plugin.PinPath); + var filePath = Path.Combine(Plugin.PinPath, fileName); + var json = JsonConvert.SerializeObject(LoadedPins, Formatting.Indented); + File.WriteAllText(filePath, json); + return true; + } + catch (Exception ex) { + DXT.Log($"Failed to save learned Boss pin: {ex.Message}", false); + return false; + } + } + // | Pathfinding |---------------------------------------------------------------------------------------------------- private SVector2? _lastPlayerPathGridPos = null; public void RenderPaths() { diff --git a/Modules/SettingsUI.cs b/Modules/SettingsUI.cs index c691fea..a065de4 100644 --- a/Modules/SettingsUI.cs +++ b/Modules/SettingsUI.cs @@ -370,6 +370,14 @@ private void Render_PinSettings(string panelID) { ImGui.SameLine(); DXT.Label.Draw("Color Cycle", checkboxLabelOptions); + ImGui.SameLine(); + DXT.Checkbox.Draw("Pin.LearnMapBossArena", ref Settings.Pin.LearnMapBossArena, new() { + Height = controlHeight, + Tooltip = new("Learn Map Boss/Arena", "Automatically add a Boss pin from detected boss or arena tiles when no pins are configured for the area.") + }); + ImGui.SameLine(); + DXT.Label.Draw("Learn Map Boss/Arena", checkboxLabelOptions); + ImGui.SameLine(); DXT.Slider.Draw("Pin.PathThickness", ref Settings.Pin.PathThickness, new() { Width = controlWidth, Height = controlHeight, Min = 1, Max = 5, diff --git a/Settings.cs b/Settings.cs index 701bd3b..81b1a4f 100644 --- a/Settings.cs +++ b/Settings.cs @@ -65,6 +65,7 @@ public sealed class PinSettings public bool Enabled = true; public bool DrawPaths = true; public bool OverrideColorPaths = false; + public bool LearnMapBossArena = false; public int PathThickness = 1; public bool EditorWindowOpen = false;