diff --git a/Mappy/Classes/DrawHelpers.cs b/Mappy/Classes/DrawHelpers.cs index f79acf4..9b3fb36 100644 --- a/Mappy/Classes/DrawHelpers.cs +++ b/Mappy/Classes/DrawHelpers.cs @@ -38,6 +38,9 @@ public static class DrawHelpers public const uint QuestionMarkIcon = 60071; + /// Sentinel IconId for user-placed map notes (custom-drawn white page with writing). + public const uint MapNoteIconId = 65000; + /// /// Offset Vector of SelectedX, SelectedY, scaled with SelectedSizeFactor /// @@ -143,9 +146,7 @@ public static class DrawHelpers private static void DrawIcon(MarkerInfo markerInfo) { - var texture = Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty(); var scale = System.SystemConfig.ScaleWithZoom ? markerInfo.Scale : 1.0f; - var iconScale = System.SystemConfig.IconScale; if (markerInfo.IconId is 60401 or 60402) { @@ -158,11 +159,27 @@ public static class DrawHelpers iconScale = 0.42f; } - ImGui.SetCursorPos(markerInfo.Position + markerInfo.Offset - texture.Size * iconScale / 2.0f * scale * System.IconConfig.IconSettingMap[markerInfo.IconId].Scale); - var cursorScreenPos = ImGui.GetCursorScreenPos(); - var iconSize = texture.Size * scale * iconScale * System.IconConfig.IconSettingMap[markerInfo.IconId].Scale; + var setting = System.IconConfig.IconSettingMap[markerInfo.IconId]; + var sizeMultiplier = scale * iconScale * setting.Scale; - ImGui.Image(texture.Handle, iconSize, Vector2.Zero, Vector2.One, System.IconConfig.IconSettingMap[markerInfo.IconId].Color); + Vector2 iconSize; + Vector2 cursorScreenPos; + + if (markerInfo.IconId == MapNoteIconId) { + // Custom-drawn white page with writing icon (roughly 24x30 aspect) + iconSize = new Vector2(20f, 26f) * sizeMultiplier; + ImGui.SetCursorPos(markerInfo.Position + markerInfo.Offset - iconSize / 2f); + cursorScreenPos = ImGui.GetCursorScreenPos(); + ImGui.InvisibleButton($"mapnote_{markerInfo.Position.X}_{markerInfo.Position.Y}", iconSize); + DrawMapNoteIcon(cursorScreenPos, iconSize, setting.Color); + } + else { + var texture = Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty(); + iconSize = texture.Size * sizeMultiplier; + ImGui.SetCursorPos(markerInfo.Position + markerInfo.Offset - iconSize / 2f); + cursorScreenPos = ImGui.GetCursorScreenPos(); + ImGui.Image(texture.Handle, iconSize, Vector2.Zero, Vector2.One, setting.Color); + } if (DebugMode) { foreach (var x in Enumerable.Range(-1, 3)) { @@ -241,7 +258,14 @@ public static class DrawHelpers if (markerInfo.PrimaryText?.Invoke() is { Length: > 0 } primaryText) { using var tooltip = ImRaii.Tooltip(); - ImGui.Image(Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty().Handle, ImGuiHelpers.ScaledVector2(32.0f, 32.0f)); + if (markerInfo.IconId == MapNoteIconId) { + var iconSize = ImGuiHelpers.ScaledVector2(24f, 31f); + DrawMapNoteIcon(ImGui.GetCursorScreenPos(), iconSize, Vector4.One); + ImGui.Dummy(iconSize); + } + else { + ImGui.Image(Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty().Handle, ImGuiHelpers.ScaledVector2(32.0f, 32.0f)); + } ImGui.SameLine(); ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 7.5f * ImGuiHelpers.GlobalScale); @@ -257,6 +281,34 @@ public static class DrawHelpers } } + /// Draw a custom "white page with writing" icon for map notes, centered at the given position. + public static void DrawMapNoteIconCentered(Vector2 center, Vector2 size, Vector4 tint) => + DrawMapNoteIcon(center - size * 0.5f, size, tint); + + /// Draw a custom "white page with writing" icon for map notes. + private static void DrawMapNoteIcon(Vector2 topLeft, Vector2 size, Vector4 tint) + { + var drawList = ImGui.GetWindowDrawList(); + var p1 = topLeft; + var p2 = topLeft + size; + var rounding = MathF.Min(size.X * 0.15f, size.Y * 0.12f); + var white = ImGui.GetColorU32(new Vector4(0.98f, 0.98f, 0.96f, tint.W)); + var border = ImGui.GetColorU32(new Vector4(0.7f, 0.7f, 0.65f, tint.W)); + var writing = ImGui.GetColorU32(new Vector4(0.25f, 0.22f, 0.2f, tint.W)); + var padding = size * 0.12f; + var lineHeight = (size.Y - padding.Y * 2f) / 4f; + var lineLeft = p1.X + padding.X; + var lineRight = p2.X - padding.X; + + drawList.AddRectFilled(p1, p2, white, rounding); + drawList.AddRect(p1, p2, border, rounding, 0, 1.2f); + for (var i = 0; i < 3; i++) { + var y = p1.Y + padding.Y + lineHeight * (i + 0.5f); + var w = (i == 1 ? 0.9f : 0.7f) * (lineRight - lineLeft); + drawList.AddLine(new Vector2(lineLeft, y), new Vector2(lineLeft + w, y), writing, 1.5f); + } + } + public static bool IsDisallowedIcon(uint iconId) => iconId switch { diff --git a/Mappy/Classes/MapWindowComponents/MapContextMenu.cs b/Mappy/Classes/MapWindowComponents/MapContextMenu.cs index 6b5e7a0..e368804 100644 --- a/Mappy/Classes/MapWindowComponents/MapContextMenu.cs +++ b/Mappy/Classes/MapWindowComponents/MapContextMenu.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; @@ -17,14 +17,26 @@ public unsafe class MapContextMenu if (!contextMenu) return; - if (ImGui.MenuItem("Place Flag")) { - var cursorPosition = ImGui.GetMousePosOnOpeningCurrentPopup(); // Get initial cursor position (screen relative) - var mapChildOffset = mapDrawOffset; // Get the screen position we started drawing the map at - var mapDrawPositionOffset = System.MapRenderer.DrawPosition; // Get the map texture top left offset vector - var textureClickLocation = (cursorPosition - mapChildOffset - mapDrawPositionOffset) / MapRenderer.MapRenderer.Scale; // Math - var result = textureClickLocation - new Vector2(1024.0f, 1024.0f); // One of our vectors made the map centered, undo it. - var scaledResult = result / DrawHelpers.GetMapScaleFactor() + DrawHelpers.GetRawMapOffsetVector(); // Apply offset x/y and scalefactor + var cursorPosition = ImGui.GetMousePosOnOpeningCurrentPopup(); + var textureClickLocation = (cursorPosition - mapDrawOffset - System.MapRenderer.DrawPosition) / MapRenderer.MapRenderer.Scale; + var result = textureClickLocation - new Vector2(1024.0f, 1024.0f); + var scaledResult = result / DrawHelpers.GetMapScaleFactor() + DrawHelpers.GetRawMapOffsetVector(); + var noteAtCursor = System.MapNoteConfig.FindNoteAt( + AgentMap.Instance()->SelectedTerritoryId, AgentMap.Instance()->SelectedMapId, scaledResult.X, scaledResult.Y); + if (ImGui.MenuItem("Place Note")) { + MapWindow.PendingMapNotePosition = (AgentMap.Instance()->SelectedTerritoryId, AgentMap.Instance()->SelectedMapId, scaledResult.X, scaledResult.Y); + MapWindow.RequestOpenAddNotePopup(); + } + + if (ImGui.MenuItem("Remove Note", false, noteAtCursor is not null)) { + if (noteAtCursor is { } note) { + System.MapNoteConfig.Notes.Remove(note); + System.MapNoteConfig.Save(); + } + } + + if (ImGui.MenuItem("Place Flag")) { AgentMap.Instance()->FlagMarkerCount = 0; AgentMap.Instance()->SetFlagMapMarker(AgentMap.Instance()->SelectedTerritoryId, AgentMap.Instance()->SelectedMapId, scaledResult.X, scaledResult.Y); AgentChatLog.Instance()->InsertTextCommandParam(1048, false); @@ -65,5 +77,9 @@ public unsafe class MapContextMenu if (ImGui.MenuItem("Open Flag List", false, System.WindowManager.GetWindow() is null)) { System.WindowManager.AddWindow(new FlagHistoryWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn); } + + if (ImGui.MenuItem("Open Note List", false, System.WindowManager.GetWindow() is null)) { + System.WindowManager.AddWindow(new MapNoteListWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn); + } } } \ No newline at end of file diff --git a/Mappy/Data/MapNoteConfig.cs b/Mappy/Data/MapNoteConfig.cs new file mode 100644 index 0000000..0ba073c --- /dev/null +++ b/Mappy/Data/MapNoteConfig.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface.Textures.TextureWraps; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using KamiLib.Configuration; +using Lumina.Excel.Sheets; +using Mappy.Classes.SelectionWindowComponents; + +namespace Mappy.Data; + +public unsafe record MapNote(uint Territory, uint Map, float X, float Y, string Title, string Description) +{ + public IDalamudTextureWrap? GetMapTexture() => MapDrawableOption.GetMapTexture(Map); + + public Map GetMap() => Service.DataManager.GetExcelSheet().GetRow(Map); + + public TerritoryType GetTerritoryType() => Service.DataManager.GetExcelSheet().GetRow(Territory); + + public string GetIdString() => $"{Territory}_{Map}_{X}_{Y}_{Title.GetHashCode()}"; + + /// Tooltip text for map/minimap: Title with Description on second line if present. + public string GetTooltipText() + { + var t = Title ?? ""; + var d = Description ?? ""; + return string.IsNullOrWhiteSpace(d) ? t : $"{t}\n{d}"; + } + + public Vector2 GetCoordinate() => new(X, Y); + + /// Returns the stored map coordinates for display (same space as Place Note/Flag). + public Vector2 GetMapCoordinate() => new(X, Y); + + public void Focus() + { + System.SystemConfig.FollowPlayer = false; + System.IntegrationsController.OpenMap(Map); + System.MapRenderer.CenterOnCoordinate(GetCoordinate()); + } +} + +public class MapNoteConfig +{ + public List Notes { get; set; } = []; + + public int MaxNotes { get; set; } = 100; + + public static MapNoteConfig Load() => Service.PluginInterface.LoadConfigFile("MapNotes.data.json"); + + public void Save() => Service.PluginInterface.SaveConfigFile("MapNotes.data.json", System.MapNoteConfig); + + /// Finds a note at or near the given map coordinates (same space as Place Note). + /// Distance in map coordinate space; typical icon is ~200–500 units, so 500 covers right-clicks on the icon. + public MapNote? FindNoteAt(uint territoryId, uint mapId, float x, float y, float threshold = 500f) + { + MapNote? closest = null; + var minDistSq = threshold * threshold; + foreach (var n in Notes.Where(n => n.Territory == territoryId && n.Map == mapId)) { + var dx = x - n.X; + var dy = y - n.Y; + var distSq = dx * dx + dy * dy; + if (distSq < minDistSq) { + minDistSq = distSq; + closest = n; + } + } + return closest; + } +} diff --git a/Mappy/MapRenderer/MapRenderer.Core.cs b/Mappy/MapRenderer/MapRenderer.Core.cs index 6b9726b..55171a7 100644 --- a/Mappy/MapRenderer/MapRenderer.Core.cs +++ b/Mappy/MapRenderer/MapRenderer.Core.cs @@ -376,6 +376,7 @@ public unsafe partial class MapRenderer : IDisposable DrawFieldMarkers(); DrawPlayer(); DrawStaticTextMarkers(); + DrawMapNotes(); DrawFlag(); } } \ No newline at end of file diff --git a/Mappy/MapRenderer/MapRenderer.MapNotes.cs b/Mappy/MapRenderer/MapRenderer.MapNotes.cs new file mode 100644 index 0000000..b944f58 --- /dev/null +++ b/Mappy/MapRenderer/MapRenderer.MapNotes.cs @@ -0,0 +1,35 @@ +using System.Linq; +using System.Numerics; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using Mappy.Classes; +using Mappy.Data; + +namespace Mappy.MapRenderer; + +public partial class MapRenderer +{ + private unsafe void DrawMapNotes() + { + var agent = AgentMap.Instance(); + var territoryId = agent->SelectedTerritoryId; + var mapId = agent->SelectedMapId; + + foreach (var note in System.MapNoteConfig.Notes.Where(n => n.Territory == territoryId && n.Map == mapId).ToList()) { + var pos = new Vector2(note.X, note.Y) * Scale * DrawHelpers.GetMapScaleFactor() + DrawHelpers.GetCombinedOffsetVector() * Scale; + + DrawHelpers.DrawMapMarker(new MarkerInfo + { + Position = pos, + Offset = DrawPosition, + Scale = Scale, + IconId = DrawHelpers.MapNoteIconId, + PrimaryText = () => note.Title ?? "", + SecondaryText = () => string.IsNullOrWhiteSpace(note.Description) + ? note.GetTerritoryType().PlaceNameZone.Value.Name.ExtractText() + : $"{note.Description}\n{note.GetTerritoryType().PlaceNameZone.Value.Name.ExtractText()}", + OnLeftClicked = () => note.Focus(), + }); + } + } + +} diff --git a/Mappy/MapRenderer/MapRenderer.MinimapMarkers.cs b/Mappy/MapRenderer/MapRenderer.MinimapMarkers.cs index dbd6591..7ff8a2d 100644 --- a/Mappy/MapRenderer/MapRenderer.MinimapMarkers.cs +++ b/Mappy/MapRenderer/MapRenderer.MinimapMarkers.cs @@ -16,6 +16,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.Interop; using Lumina.Excel.Sheets; using Mappy.Classes; +using Mappy.Data; using Mappy.Extensions; using KamiLib.Extensions; using FieldMarkerSheet = Lumina.Excel.Sheets.FieldMarker; @@ -59,6 +60,8 @@ public partial class MapRenderer DrawMinimapGameObjects(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY); // User flag DrawMinimapFlag(contentTopLeft, TexToContent, scaleFactor, offsetX, offsetY); + // User map notes + DrawMinimapMapNotes(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY); // Temporary (quest objectives, etc.) DrawMinimapTempMarkers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY, scale); // Field markers (waymarks) @@ -726,6 +729,30 @@ public partial class MapRenderer } } + private unsafe void DrawMinimapMapNotes(Vector2 contentTopLeft, Func texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY) + { + var agent = AgentMap.Instance(); + var territoryId = agent->CurrentTerritoryId; + var mapId = agent->CurrentMapId; + + foreach (var note in System.MapNoteConfig.Notes.Where(n => n.Territory == territoryId && n.Map == mapId)) { + if (System.IconConfig.IconSettingMap.TryGetValue(DrawHelpers.MapNoteIconId, out var setting) && setting.Hide) continue; + + var tx = 1024.0f + (note.X - offsetX) * scaleFactor; + var ty = 1024.0f + (note.Y - offsetY) * scaleFactor; + var contentPos = texToContent(tx, ty); + if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue; + + var centerScreen = contentPos + contentTopLeft; + var iconSize = new Vector2(22f, 28f) * (MinimapIconScaleFromConfig * 0.75f); + var col = setting?.Color ?? Vector4.One; + DrawHelpers.DrawMapNoteIconCentered(centerScreen, iconSize, col); + + if (!string.IsNullOrEmpty(note.GetTooltipText()) && ImGui.IsMouseHoveringRect(centerScreen - iconSize / 2f, centerScreen + iconSize / 2f)) + ImGui.SetTooltip(note.GetTooltipText()); + } + } + private unsafe void DrawMinimapFlag(Vector2 contentTopLeft, Func texToContent, float scaleFactor, float offsetX, float offsetY) { var agent = AgentMap.Instance(); diff --git a/Mappy/Mappy.csproj b/Mappy/Mappy.csproj index 4e97848..cc6eeee 100644 --- a/Mappy/Mappy.csproj +++ b/Mappy/Mappy.csproj @@ -4,7 +4,7 @@ HSMappy HSMappy Knack117 - 1.0.0.12 + 1.0.0.13 A more versatile in-game map. Replaces the in-game map with an ImGui implementation with several additional features. Fork with minimap improvements, quest radius on minimap, and more. http://brassnet.ddns.net:33983/KnackAtNite/HSMappy diff --git a/Mappy/MappyPlugin.cs b/Mappy/MappyPlugin.cs index b733cdf..c189b0b 100644 --- a/Mappy/MappyPlugin.cs +++ b/Mappy/MappyPlugin.cs @@ -31,6 +31,7 @@ public sealed class MappyPlugin : IDalamudPlugin System.SystemConfig = SystemConfig.Load(); System.IconConfig = IconConfig.Load(); System.FlagConfig = FlagConfig.Load(); + System.MapNoteConfig = MapNoteConfig.Load(); System.Teleporter = new Teleporter(Service.PluginInterface); @@ -86,6 +87,14 @@ public sealed class MappyPlugin : IDalamudPlugin DisableDelegate = _ => System.WindowManager.GetWindow()?.Close(), ToggleDelegate = _ => System.WindowManager.GetWindow()?.UnCollapseOrToggle(), }); + + System.CommandManager.RegisterCommand(new ToggleCommandHandler + { + BaseActivationPath = "/notelist", + EnableDelegate = _ => System.WindowManager.OpenOrCreateUnique(WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn), + DisableDelegate = _ => System.WindowManager.GetWindow()?.Close(), + ToggleDelegate = _ => System.WindowManager.GetWindow()?.UnCollapseOrToggle(), + }); } private unsafe void OpenMapWindow() => AgentMap.Instance()->Show(); diff --git a/Mappy/System.cs b/Mappy/System.cs index 9add6e6..71d482d 100644 --- a/Mappy/System.cs +++ b/Mappy/System.cs @@ -17,6 +17,7 @@ public static class System public static SystemConfig SystemConfig { get; set; } public static IconConfig IconConfig { get; set; } public static FlagConfig FlagConfig { get; set; } + public static MapNoteConfig MapNoteConfig { get; set; } public static WindowManager WindowManager { get; set; } public static MapWindow MapWindow { get; set; } public static MinimapWindow MinimapWindow { get; set; } diff --git a/Mappy/Windows/MapNoteListWindow.cs b/Mappy/Windows/MapNoteListWindow.cs new file mode 100644 index 0000000..525a626 --- /dev/null +++ b/Mappy/Windows/MapNoteListWindow.cs @@ -0,0 +1,105 @@ +using System.Collections.Immutable; +using System.Drawing; +using System.Linq; +using System.Numerics; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using KamiLib.Window; +using Mappy.Data; + +namespace Mappy.Windows; + +public class MapNoteListWindow : Window +{ + private static float NoteElementHeight => 95.0f * ImGuiHelpers.GlobalScale; + + public MapNoteListWindow() : base("HSMappy Note List Window", new Vector2(400.0f, 400.0f)) + { + AdditionalInfoTooltip = "Shows user-placed map notes for the current map"; + } + + protected override unsafe void DrawContents() + { + var agent = AgentMap.Instance(); + var territoryId = agent->SelectedTerritoryId; + var mapId = agent->SelectedMapId; + var notesForMap = System.MapNoteConfig.Notes + .Where(n => n.Territory == territoryId && n.Map == mapId) + .ToImmutableList(); + + ImGuiClip.ClippedDraw(notesForMap, DrawNote, NoteElementHeight); + } + + private void DrawNote(MapNote note) + { + using var id = ImRaii.PushId(note.GetIdString()); + + using (ImRaii.Child("note_container", new Vector2(ImGui.GetContentRegionAvail().X, NoteElementHeight - ImGui.GetStyle().FramePadding.Y * 2.0f))) { + using (ImRaii.Child("note_image_container", new Vector2(155.0f * ImGuiHelpers.GlobalScale, ImGui.GetContentRegionAvail().Y))) { + DrawNoteImage(note); + } + + ImGui.SameLine(); + + using (ImRaii.Child("note_contents_container", ImGui.GetContentRegionAvail())) { + var buttonHeight = 28f * ImGuiHelpers.GlobalScale; + var textHeight = ImGui.GetContentRegionAvail().Y - buttonHeight; + using (ImRaii.Child("note_text_area", new Vector2(ImGui.GetContentRegionAvail().X, textHeight))) { + DrawNoteData(note); + } + DrawButtons(note); + } + } + + ImGui.Spacing(); + } + + private void DrawNoteImage(MapNote note) + { + var texture = note.GetMapTexture(); + if (texture is not null) { + ImGui.Image(texture.Handle, ImGui.GetContentRegionAvail(), new Vector2(0.15f, 0.15f), new Vector2(0.85f, 0.85f)); + } + else { + ImGuiHelpers.ScaledDummy(ImGui.GetContentRegionAvail()); + } + } + + private void DrawNoteData(MapNote note) + { + ImGui.TextUnformatted(note.Title ?? ""); + if (!string.IsNullOrWhiteSpace(note.Description)) { + ImGui.TextColored(KnownColor.Gray.Vector().Lighten(0.20f), note.Description); + } + + ImGui.TextColored(KnownColor.Gray.Vector().Lighten(0.20f), note.GetMap().PlaceName.Value.Name.ExtractText()); + + var coord = note.GetMapCoordinate(); + var zoneName = note.GetTerritoryType().PlaceNameZone.Value.Name.ExtractText(); + ImGui.TextColored(KnownColor.Gray.Vector(), $"{zoneName} • {coord.X:F1}, {coord.Y:F1}"); + } + + private void DrawButtons(MapNote note) + { + var buttonSize = ImGuiHelpers.ScaledVector2(80.0f, 24.0f); + + ImGui.SetCursorPos(new Vector2(0.0f, ImGui.GetContentRegionMax().Y - buttonSize.Y)); + if (ImGui.Button("Focus", buttonSize)) { + note.Focus(); + } + + ImGui.SameLine(); + if (ImGui.Button("Remove", buttonSize)) { + System.MapNoteConfig.Notes.Remove(note); + System.MapNoteConfig.Save(); + } + } + + public override void OnClose() + { + System.WindowManager.RemoveWindow(this); + } +} diff --git a/Mappy/Windows/MapWindow.cs b/Mappy/Windows/MapWindow.cs index 2309e2b..9cd2c6e 100644 --- a/Mappy/Windows/MapWindow.cs +++ b/Mappy/Windows/MapWindow.cs @@ -22,6 +22,13 @@ namespace Mappy.Windows; public class MapWindow : Window { + public static (uint Territory, uint Map, float X, float Y)? PendingMapNotePosition; + private static string _pendingNoteTitle = string.Empty; + private static string _pendingNoteDescription = string.Empty; + private static bool _requestOpenAddNotePopup; + + public static void RequestOpenAddNotePopup() => _requestOpenAddNotePopup = true; + public Vector2 MapDrawOffset { get; private set; } public HoverFlags HoveredFlags { get; private set; } public bool ProcessingCommand { get; set; } @@ -243,6 +250,49 @@ public class MapWindow : Window // Draw Context Menu mapContextMenu.Draw(MapDrawOffset); + + DrawAddMapNotePopup(); + } + + private static void DrawAddMapNotePopup() + { + if (_requestOpenAddNotePopup) { + ImGui.OpenPopup("AddMapNote"); + _requestOpenAddNotePopup = false; + } + + if (!ImGui.BeginPopup("AddMapNote")) return; + + ImGui.Text("Note Title:"); + ImGui.SetNextItemWidth(250f * ImGuiHelpers.GlobalScale); + ImGui.InputText("##notetitle", ref _pendingNoteTitle, 256); + + ImGui.Text("Note Description:"); + ImGui.SetNextItemWidth(250f * ImGuiHelpers.GlobalScale); + ImGui.InputText("##notedesc", ref _pendingNoteDescription, 512); + + if (ImGui.Button("Cancel")) { + PendingMapNotePosition = null; + _pendingNoteTitle = string.Empty; + _pendingNoteDescription = string.Empty; + ImGui.CloseCurrentPopup(); + } + ImGui.SameLine(); + if (ImGui.Button("Add")) { + if (PendingMapNotePosition is { } pos && !string.IsNullOrWhiteSpace(_pendingNoteTitle)) { + System.MapNoteConfig.Notes.Add(new MapNote(pos.Territory, pos.Map, pos.X, pos.Y, _pendingNoteTitle.Trim(), _pendingNoteDescription?.Trim() ?? "")); + if (System.MapNoteConfig.Notes.Count > System.MapNoteConfig.MaxNotes) { + System.MapNoteConfig.Notes.RemoveAt(0); + } + System.MapNoteConfig.Save(); + } + PendingMapNotePosition = null; + _pendingNoteTitle = string.Empty; + _pendingNoteDescription = string.Empty; + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); } private unsafe void UpdateStyle() diff --git a/Mappy/pluginmaster.json b/Mappy/pluginmaster.json index 1504114..9880775 100644 --- a/Mappy/pluginmaster.json +++ b/Mappy/pluginmaster.json @@ -1 +1 @@ -[{"Author":"Knack117","Name":"HSMappy","Punchline":"A more versatile in-game map.","Description":"Replaces the in-game map with an ImGui implementation with several additional features. Fork with minimap improvements, quest radius on minimap, white gradient player cone, and more.","Changelog":"1.0.0.12: Other players on minimap use distinct icon (60403) from party markers. 1.0.0.11: Player/NPC tracking on minimap with Show Players and NPCs toggle. 1.0.0.10: Release build. Suppress silent refresh at start of OnOpenMapHook; remove debug logging. 1.0.0.9: Duty List quest click: don't Hide() when viewing quest map (SelectedMapId != CurrentMapId). 1.0.0.8: Cancel silent refresh when opening map from Duty List so it doesn't immediately close. 1.0.0.7: Duty List quest click opens Area Map even when Hide With Game GUI would block it. 1.0.0.6: Minimap stays open after client restart (restore on login). 1.0.0.5: Fix crash when map texture path is invalid (ArgumentOutOfRangeException in Lumina GetFileHash). 1.0.0.4: Temp marker circle refreshes when quest objective is progressed. 1.0.0.3: Fix marker cache refresh after quest turn-in; invalidate temp cache so old markers don't persist. 1.0.0.2: Red direction arrow on minimap pointing to player flag. 1.0.0.1: Duty List quest click keeps Area Map open; player flags show on minimap. 1.0.0.0: Initial HSMappy release. Minimap: quest radius circle (orange, transparent), tooltip; cone drawn under markers; white gradient cone; /hsmappy commands.","InternalName":"HSMappy","AssemblyVersion":"1.0.0.12","RepoUrl":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy","ApplicableVersion":"any","Tags":["map","mapping","overlay","utility"],"CategoryTags":["jobs"],"DalamudApiLevel":14,"DownloadLinkInstall":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.12/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.12/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.12/latest.zip","LastUpdate":"1772313500"}] +[{"Author":"Knack117","Name":"HSMappy","Punchline":"A more versatile in-game map.","Description":"Replaces the in-game map with an ImGui implementation with several additional features. Fork with minimap improvements, quest radius on minimap, white gradient player cone, and more.","Changelog":"1.0.0.13: User-placed map notes with Title/Description; custom white-page icon; notes on minimap; Remove Note via context menu; Note List layout fix. 1.0.0.12: Other players on minimap use distinct icon (60403) from party markers. 1.0.0.11: Player/NPC tracking on minimap with Show Players and NPCs toggle. 1.0.0.10: Release build. Suppress silent refresh at start of OnOpenMapHook; remove debug logging. 1.0.0.9: Duty List quest click: don't Hide() when viewing quest map (SelectedMapId != CurrentMapId). 1.0.0.8: Cancel silent refresh when opening map from Duty List so it doesn't immediately close. 1.0.0.7: Duty List quest click opens Area Map even when Hide With Game GUI would block it. 1.0.0.6: Minimap stays open after client restart (restore on login). 1.0.0.5: Fix crash when map texture path is invalid (ArgumentOutOfRangeException in Lumina GetFileHash). 1.0.0.4: Temp marker circle refreshes when quest objective is progressed. 1.0.0.3: Fix marker cache refresh after quest turn-in; invalidate temp cache so old markers don't persist. 1.0.0.2: Red direction arrow on minimap pointing to player flag. 1.0.0.1: Duty List quest click keeps Area Map open; player flags show on minimap. 1.0.0.0: Initial HSMappy release. Minimap: quest radius circle (orange, transparent), tooltip; cone drawn under markers; white gradient cone; /hsmappy commands.","InternalName":"HSMappy","AssemblyVersion":"1.0.0.13","RepoUrl":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy","ApplicableVersion":"any","Tags":["map","mapping","overlay","utility"],"CategoryTags":["jobs"],"DalamudApiLevel":14,"DownloadLinkInstall":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.13/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.13/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.13/latest.zip","LastUpdate":"1760400000"}]