1 Commits

Author SHA1 Message Date
KnackAtNite 542da3a71b v1.0.0.13: User-placed map notes with Title/Description; custom icon; notes on minimap
Made-with: Cursor
2026-03-01 00:12:37 -05:00
12 changed files with 383 additions and 17 deletions
+59 -7
View File
@@ -38,6 +38,9 @@ public static class DrawHelpers
public const uint QuestionMarkIcon = 60071;
/// <summary>Sentinel IconId for user-placed map notes (custom-drawn white page with writing).</summary>
public const uint MapNoteIconId = 65000;
/// <summary>
/// Offset Vector of SelectedX, SelectedY, scaled with SelectedSizeFactor
/// </summary>
@@ -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
}
}
/// <summary>Draw a custom "white page with writing" icon for map notes, centered at the given position.</summary>
public static void DrawMapNoteIconCentered(Vector2 center, Vector2 size, Vector4 tint) =>
DrawMapNoteIcon(center - size * 0.5f, size, tint);
/// <summary>Draw a custom "white page with writing" icon for map notes.</summary>
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
{
@@ -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<FlagHistoryWindow>() is null)) {
System.WindowManager.AddWindow(new FlagHistoryWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn);
}
if (ImGui.MenuItem("Open Note List", false, System.WindowManager.GetWindow<MapNoteListWindow>() is null)) {
System.WindowManager.AddWindow(new MapNoteListWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn);
}
}
}
+70
View File
@@ -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<Map>().GetRow(Map);
public TerritoryType GetTerritoryType() => Service.DataManager.GetExcelSheet<TerritoryType>().GetRow(Territory);
public string GetIdString() => $"{Territory}_{Map}_{X}_{Y}_{Title.GetHashCode()}";
/// <summary>Tooltip text for map/minimap: Title with Description on second line if present.</summary>
public string GetTooltipText()
{
var t = Title ?? "";
var d = Description ?? "";
return string.IsNullOrWhiteSpace(d) ? t : $"{t}\n{d}";
}
public Vector2 GetCoordinate() => new(X, Y);
/// <summary>Returns the stored map coordinates for display (same space as Place Note/Flag).</summary>
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<MapNote> Notes { get; set; } = [];
public int MaxNotes { get; set; } = 100;
public static MapNoteConfig Load() => Service.PluginInterface.LoadConfigFile<MapNoteConfig>("MapNotes.data.json");
public void Save() => Service.PluginInterface.SaveConfigFile("MapNotes.data.json", System.MapNoteConfig);
/// <summary>Finds a note at or near the given map coordinates (same space as Place Note).</summary>
/// <param name="threshold">Distance in map coordinate space; typical icon is ~200500 units, so 500 covers right-clicks on the icon.</param>
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;
}
}
+1
View File
@@ -376,6 +376,7 @@ public unsafe partial class MapRenderer : IDisposable
DrawFieldMarkers();
DrawPlayer();
DrawStaticTextMarkers();
DrawMapNotes();
DrawFlag();
}
}
+35
View File
@@ -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(),
});
}
}
}
@@ -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<float, float, Vector2> 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<float, float, Vector2> texToContent, float scaleFactor, float offsetX, float offsetY)
{
var agent = AgentMap.Instance();
+1 -1
View File
@@ -4,7 +4,7 @@
<Name>HSMappy</Name>
<InternalName>HSMappy</InternalName>
<Author>Knack117</Author>
<Version>1.0.0.12</Version>
<Version>1.0.0.13</Version>
<Punchline>A more versatile in-game map.</Punchline>
<Description>Replaces the in-game map with an ImGui implementation with several additional features. Fork with minimap improvements, quest radius on minimap, and more.</Description>
<RepoUrl>http://brassnet.ddns.net:33983/KnackAtNite/HSMappy</RepoUrl>
+9
View File
@@ -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<FlagHistoryWindow>()?.Close(),
ToggleDelegate = _ => System.WindowManager.GetWindow<FlagHistoryWindow>()?.UnCollapseOrToggle(),
});
System.CommandManager.RegisterCommand(new ToggleCommandHandler
{
BaseActivationPath = "/notelist",
EnableDelegate = _ => System.WindowManager.OpenOrCreateUnique<MapNoteListWindow>(WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn),
DisableDelegate = _ => System.WindowManager.GetWindow<MapNoteListWindow>()?.Close(),
ToggleDelegate = _ => System.WindowManager.GetWindow<MapNoteListWindow>()?.UnCollapseOrToggle(),
});
}
private unsafe void OpenMapWindow() => AgentMap.Instance()->Show();
+1
View File
@@ -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; }
+105
View File
@@ -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);
}
}
+50
View File
@@ -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()
+1 -1
View File
@@ -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"}]