diff --git a/Mappy/Data/EquipmentConditionHelper.cs b/Mappy/Data/EquipmentConditionHelper.cs new file mode 100644 index 0000000..705d4a8 --- /dev/null +++ b/Mappy/Data/EquipmentConditionHelper.cs @@ -0,0 +1,43 @@ +using System; +using FFXIVClientStructs.FFXIV.Client.Game; + +namespace Mappy.Data; + +/// +/// Returns the condition percentage of the player's most damaged equipped item. +/// Uses the same logic as RepairMe (chalkos/RepairMe): raw condition / 300. +/// +public static class EquipmentConditionHelper +{ + private const uint EquipmentContainerSize = 13; + + /// + /// Gets the lowest condition percent among equipped items (0–100+). + /// Returns 100 if no gear or unavailable (e.g. not logged in). + /// + public static unsafe float GetLowestConditionPercent() + { + var inventoryManager = InventoryManager.Instance(); + if (inventoryManager == null) return 100f; + + var equipmentContainer = inventoryManager->GetInventoryContainer(InventoryType.EquippedItems); + if (equipmentContainer == null) return 100f; + + var inventoryItem = equipmentContainer->GetInventorySlot(0); + if (inventoryItem == null) return 100f; + + ushort lowestCondition = 60000; // max raw condition is 30000 + var foundAny = false; + + for (var i = 0; i < EquipmentContainerSize; i++, inventoryItem++) + { + if (inventoryItem->ItemId == 0) continue; + + foundAny = true; + if (lowestCondition > inventoryItem->Condition) + lowestCondition = inventoryItem->Condition; + } + + return foundAny ? lowestCondition / 300f : 100f; + } +} diff --git a/Mappy/Data/SystemConfig.cs b/Mappy/Data/SystemConfig.cs index 227400b..51fe42f 100644 --- a/Mappy/Data/SystemConfig.cs +++ b/Mappy/Data/SystemConfig.cs @@ -122,6 +122,36 @@ public class SystemConfig : CharacterConfiguration public bool MinimapShowPlayersAndNpcs = true; /// Icon ID for other players on the minimap (default 60403, distinct from party 60421). Override if you prefer a different look. public uint MinimapOtherPlayerIconId = 60403; + /// Show current map info (region, map, area, sub-area) at the top of the minimap. + public bool MinimapShowMapInfoBar = true; + /// Order of map info: 0=Region, 1=Map, 2=Area, 3=SubArea. e.g. {0,1,2,3} = Region, Map, Area, SubArea. + public int[] MinimapMapInfoOrder = [0, 1, 2, 3]; + /// Font scale multiplier for map info bar (0.5-2.0). + public float MinimapMapInfoFontScale = 1.0f; + /// Text color for map info bar. + public Vector4 MinimapMapInfoColor = KnownColor.White.Vector(); + /// Font type for map info: 0=Default, 1=Axis12, 2=Axis18. + public int MinimapMapInfoFontType = 0; + /// Background color (RGBA) for map info bar. Alpha = opacity. + public Vector4 MinimapMapInfoBarBackground = new(0f, 0f, 0f, 0.2f); + /// Show player coordinates at the bottom of the minimap. + public bool MinimapShowCoordinateBar = true; + /// Show coordinates in the bottom bar. + public bool MinimapCoordBarShowCoordinates = true; + /// Show local time in the bottom bar. + public bool MinimapCoordBarShowTime = true; + /// Show repair % (lowest equipped item condition) in the bottom bar. + public bool MinimapCoordBarShowRepairPercent = true; + /// Order of bottom bar elements: 0=Coordinates, 1=Repair %, 2=Local Time. + public int[] MinimapBottomBarOrder = [0, 1, 2]; + /// Font scale multiplier for coordinate bar (0.5-2.0). + public float MinimapCoordBarFontScale = 1.0f; + /// Text color for coordinate bar. + public Vector4 MinimapCoordBarColor = KnownColor.White.Vector(); + /// Font type for coord bar: 0=Default, 1=Axis12, 2=Axis18. + public int MinimapCoordBarFontType = 0; + /// Background color (RGBA) for coordinate bar. Alpha = opacity. + public Vector4 MinimapCoordBarBackground = new(0f, 0f, 0f, 0.2f); // Movement Trail (Carbonite-style: show where you've been) /// Draw a red trail of dots on the map showing where you've been. diff --git a/Mappy/MapRenderer/MapRenderer.Core.cs b/Mappy/MapRenderer/MapRenderer.Core.cs index 85aa7ce..0b9b543 100644 --- a/Mappy/MapRenderer/MapRenderer.Core.cs +++ b/Mappy/MapRenderer/MapRenderer.Core.cs @@ -145,6 +145,21 @@ public unsafe partial class MapRenderer : IDisposable /// public bool HasMinimapCacheFor(uint mapId) => _minimapCache.ContainsKey(mapId); + /// + /// Returns the map transform (offsetX, offsetY, sizeFactor) for the current minimap map, for coordinate display. + /// Returns null if no cache exists for CurrentMapId. + /// + public (int offsetX, int offsetY, uint sizeFactor)? GetCurrentMinimapTransform() + { + var agent = AgentMap.Instance(); + var currentMapId = agent->CurrentMapId; + if (currentMapId == 0 || !_minimapCache.TryGetValue(currentMapId, out var entry)) + return null; + var sizeFactor = (uint)Math.Round(entry.ScaleFactor * 100f); + if (sizeFactor == 0) sizeFactor = 100; + return ((int)entry.OffsetX, (int)entry.OffsetY, sizeFactor); + } + /// /// Try to load map texture and transform from Lumina (Map sheet) so the minimap can draw without opening the area map. /// Uses game map path conventions (ui/map/...) and Map.SizeFactor, Map.OffsetX/Y. On success, fills the cache for this map. diff --git a/Mappy/Mappy.csproj b/Mappy/Mappy.csproj index 445cae7..6a7c78b 100644 --- a/Mappy/Mappy.csproj +++ b/Mappy/Mappy.csproj @@ -4,7 +4,7 @@ HSMappy HSMappy Knack117 - 1.0.0.14 + 1.0.0.15 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 905cdbb..da64115 100644 --- a/Mappy/MappyPlugin.cs +++ b/Mappy/MappyPlugin.cs @@ -28,6 +28,9 @@ public sealed class MappyPlugin : IDalamudPlugin BaseSkewStrength = 16f, }); + System.MinimapAxis12FontHandle = Service.PluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(new GameFontStyle(GameFontFamilyAndSize.Axis12)); + System.MinimapAxis18FontHandle = Service.PluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(new GameFontStyle(GameFontFamilyAndSize.Axis18)); + System.SystemConfig = SystemConfig.Load(); System.IconConfig = IconConfig.Load(); System.FlagConfig = FlagConfig.Load(); diff --git a/Mappy/System.cs b/Mappy/System.cs index 298670d..622f499 100644 --- a/Mappy/System.cs +++ b/Mappy/System.cs @@ -45,4 +45,6 @@ public static class System public static AetheryteAethernetCache AetheryteAethernetCache { get; set; } = new(); public static IFontHandle LargeAxisFontHandle { get; set; } + public static IFontHandle MinimapAxis12FontHandle { get; set; } + public static IFontHandle MinimapAxis18FontHandle { get; set; } } \ No newline at end of file diff --git a/Mappy/Windows/ConfigurationWindow.cs b/Mappy/Windows/ConfigurationWindow.cs index 1381c1d..e4a68a5 100644 --- a/Mappy/Windows/ConfigurationWindow.cs +++ b/Mappy/Windows/ConfigurationWindow.cs @@ -278,7 +278,7 @@ public class MinimapOptionsTab : ITabItem ImGuiHelpers.ScaledDummy(5.0f); - configChanged |= ImGui.DragFloat("Size", ref System.SystemConfig.MinimapSize, 5.0f, 80.0f, 400.0f, "%.0f"); + configChanged |= ImGui.DragFloat("Size", ref System.SystemConfig.MinimapSize, 5.0f, 200.0f, 400.0f, "%.0f"); if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) ImGui.SetTooltip("Minimap size. Resize only via this setting (no corner grip)."); configChanged |= ImGui.DragFloat2("Position", ref System.SystemConfig.MinimapPosition); @@ -312,12 +312,110 @@ public class MinimapOptionsTab : ITabItem if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) ImGui.SetTooltip("When enabled, the minimap hides during NPC dialogue, object interaction, and when the game hides nameplates (same as the main map). When disabled, the minimap stays visible in those situations."); configChanged |= ImGui.Checkbox("Lock Position", ref System.SystemConfig.MinimapLockPosition); + configChanged |= ImGui.Checkbox("Show Top Info Bar", ref System.SystemConfig.MinimapShowMapInfoBar); + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip("Show current map info (region, map, area, sub-area) at the top of the minimap. Respects the main map label settings (Show Region/Map/Area/Sub-Area Text)."); + + if (System.SystemConfig.MinimapShowMapInfoBar) { + using (ImRaii.PushIndent()) { + configChanged |= DrawMapInfoOrderControls(); + configChanged |= ImGuiTweaks.ColorEditWithDefault("Top Info Bar Text Color", ref System.SystemConfig.MinimapMapInfoColor, KnownColor.White.Vector()); + configChanged |= ImGui.DragFloat("Top Info Bar Font Scale", ref System.SystemConfig.MinimapMapInfoFontScale, 0.05f, 0.5f, 2.0f, "%.2f"); + configChanged |= ImGuiTweaks.ColorEditWithDefault("Top Info Bar Background", ref System.SystemConfig.MinimapMapInfoBarBackground, new Vector4(0f, 0f, 0f, 0.2f)); + var mapInfoFont = System.SystemConfig.MinimapMapInfoFontType; + if (ImGui.Combo("Top Info Bar Font", ref mapInfoFont, "Default\0Game Font (Axis 12pt)\0Game Font (Axis 18pt)\0")) { + System.SystemConfig.MinimapMapInfoFontType = mapInfoFont; + configChanged = true; + } + } + } + + configChanged |= ImGui.Checkbox("Show Bottom Info Bar", ref System.SystemConfig.MinimapShowCoordinateBar); + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip("Show player coordinates, repair % (most damaged item condition), and local time at the bottom of the minimap."); + + if (System.SystemConfig.MinimapShowCoordinateBar) { + using (ImRaii.PushIndent()) { + configChanged |= DrawBottomBarOrderControls(); + configChanged |= ImGui.Checkbox("Show Coordinates", ref System.SystemConfig.MinimapCoordBarShowCoordinates); + configChanged |= ImGui.Checkbox("Show Local Time", ref System.SystemConfig.MinimapCoordBarShowTime); + configChanged |= ImGui.Checkbox("Show Repair %", ref System.SystemConfig.MinimapCoordBarShowRepairPercent); + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip("Show condition % of your most damaged equipped item."); + configChanged |= ImGuiTweaks.ColorEditWithDefault("Bottom Info Bar Text Color", ref System.SystemConfig.MinimapCoordBarColor, KnownColor.White.Vector()); + configChanged |= ImGuiTweaks.ColorEditWithDefault("Bottom Info Bar Background", ref System.SystemConfig.MinimapCoordBarBackground, new Vector4(0f, 0f, 0f, 0.2f)); + configChanged |= ImGui.DragFloat("Bottom Info Bar Font Scale", ref System.SystemConfig.MinimapCoordBarFontScale, 0.05f, 0.5f, 2.0f, "%.2f"); + var coordFont = System.SystemConfig.MinimapCoordBarFontType; + if (ImGui.Combo("Bottom Info Bar Font", ref coordFont, "Default\0Game Font (Axis 12pt)\0Game Font (Axis 18pt)\0")) { + System.SystemConfig.MinimapCoordBarFontType = coordFont; + configChanged = true; + } + } + } } if (configChanged) { SystemConfig.Save(); } } + + private static bool DrawMapInfoOrderControls() + { + var configChanged = false; + var order = System.SystemConfig.MinimapMapInfoOrder; + if (order.Length != 4) { + System.SystemConfig.MinimapMapInfoOrder = [0, 1, 2, 3]; + order = System.SystemConfig.MinimapMapInfoOrder; + } + var labels = new[] { "Region", "Map", "Area", "SubArea" }; + if (ImGui.TreeNode("Top Info Bar Order (line 1: first two, line 2: next two)")) { + for (var i = 0; i < 4; i++) { + var idx = Math.Clamp(order[i], 0, 3); + ImGui.Text($"{(i + 1)}. {labels[idx]}"); + ImGui.SameLine(120f * ImGuiHelpers.GlobalScale); + if (ImGui.Button($"↑##up{i}") && i > 0) { + (order[i], order[i - 1]) = (order[i - 1], order[i]); + configChanged = true; + } + ImGui.SameLine(); + if (ImGui.Button($"↓##down{i}") && i < 3) { + (order[i], order[i + 1]) = (order[i + 1], order[i]); + configChanged = true; + } + } + ImGui.TreePop(); + } + return configChanged; + } + + private static bool DrawBottomBarOrderControls() + { + var configChanged = false; + var order = System.SystemConfig.MinimapBottomBarOrder; + if (order.Length != 3) { + System.SystemConfig.MinimapBottomBarOrder = [0, 1, 2]; + order = System.SystemConfig.MinimapBottomBarOrder; + } + var labels = new[] { "Coordinates", "Repair %", "Local Time" }; + if (ImGui.TreeNode("Bottom Info Bar Order")) { + for (var i = 0; i < 3; i++) { + var idx = Math.Clamp(order[i], 0, 2); + ImGui.Text($"{(i + 1)}. {labels[idx]}"); + ImGui.SameLine(140f * ImGuiHelpers.GlobalScale); + if (ImGui.Button($"↑##bottomup{i}") && i > 0) { + (order[i], order[i - 1]) = (order[i - 1], order[i]); + configChanged = true; + } + ImGui.SameLine(); + if (ImGui.Button($"↓##bottomdown{i}") && i < 2) { + (order[i], order[i + 1]) = (order[i + 1], order[i]); + configChanged = true; + } + } + ImGui.TreePop(); + } + return configChanged; + } } public class PlayerOptionsTab : ITabItem diff --git a/Mappy/Windows/MinimapWindow.cs b/Mappy/Windows/MinimapWindow.cs index 29a997e..15226d8 100644 --- a/Mappy/Windows/MinimapWindow.cs +++ b/Mappy/Windows/MinimapWindow.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; using System.Numerics; using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; using KamiLib.Window; +using Lumina.Excel.Sheets; using Mappy.Controllers; using Mappy.Data; +using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using Map = Lumina.Excel.Sheets.Map; namespace Mappy.Windows; @@ -63,46 +69,252 @@ public class MinimapWindow : Window UpdateStyle(); UpdateSizePosition(); - // Compensate for window padding: draw the minimap child so it fills the full window (no black bands). + // Compensate for window padding (minimap gets zero padding from plugin) var padding = ImGui.GetStyle().WindowPadding; - var winSize = ImGui.GetWindowSize(); ImGui.SetCursorPos(new Vector2(-padding.X, -padding.Y)); - var contentSize = winSize; + // Use actual window size so content scales when resized + var contentSize = ImGui.GetContentRegionAvail(); if (contentSize.X <= 0 || contentSize.Y <= 0) return; + var totalWidth = contentSize.X; + var totalHeight = contentSize.Y; + + // Compute bar heights: top bar fits server info on one line; bottom bar = coord text + 2px + float topBarHeight = 0f; + float bottomBarHeight = 0f; + if (System.SystemConfig.MinimapShowMapInfoBar) + topBarHeight = ComputeMapInfoBarHeight(totalWidth); + if (System.SystemConfig.MinimapShowCoordinateBar) + bottomBarHeight = ComputeCoordinateBarHeight(totalWidth, totalHeight, topBarHeight); + + var minimapSide = Math.Min(totalWidth, totalHeight - topBarHeight - bottomBarHeight); + minimapSide = Math.Max(1f, minimapSide); + var scale = minimapSide / 200f; + + // Top bar (outside edge, above minimap) + if (System.SystemConfig.MinimapShowMapInfoBar && topBarHeight > 0) { + DrawMapInfoBar(totalWidth, topBarHeight, scale); + } + + // Minimap (square, sized to fit available space) + var minimapSize = new Vector2(minimapSide, minimapSide); using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, System.SystemConfig.MinimapOpacity)) using (ImRaii.PushStyle(ImGuiStyleVar.ChildBorderSize, 0f)) using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, Vector2.Zero)) - using (var child = ImRaii.Child("minimap_render", contentSize, false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)) + using (var child = ImRaii.Child("minimap_render", minimapSize, false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)) { if (child) { - // Use window size so map fills the full window; renderer clamps draw position so map always covers the view. - System.MapRenderer.DrawMinimapContents(contentSize); - // Mouse wheel over minimap: zoom in/out, and consume wheel so the window doesn't scroll + System.MapRenderer.DrawMinimapContents(minimapSize); if (ImGui.IsItemHovered()) { var io = ImGui.GetIO(); var wheel = io.MouseWheel; if (wheel != 0) { var zoom = System.SystemConfig.MinimapZoom; - zoom -= wheel * 0.012f; // Small step so zoom is incremental between max out (0.1) and max in (0.03) + zoom -= wheel * 0.012f; System.SystemConfig.MinimapZoom = Math.Clamp(zoom, 0.03f, 0.112f); SystemConfig.Save(); } - // Consume wheel so the window doesn't scroll when at min/max zoom or when we handled it io.MouseWheel = 0f; io.MouseWheelH = 0f; } } } + // Bottom bar (outside edge, below minimap) + if (System.SystemConfig.MinimapShowCoordinateBar && bottomBarHeight > 0) { + DrawCoordinateBar(totalWidth, bottomBarHeight, scale); + } + // Restore default padding for the next window is done in plugin Draw callback (PopStyleVar after all windows). } public override void OnOpen() { ImGui.SetWindowPos(System.SystemConfig.MinimapPosition); - ImGui.SetWindowSize(new Vector2(System.SystemConfig.MinimapSize, System.SystemConfig.MinimapSize)); + var size = GetMinimapWindowSize(); + ImGui.SetWindowSize(size); + } + + private unsafe float ComputeMapInfoBarHeight(float width) + { + var (line1, line2) = GetMapInfoLines(); + if (string.IsNullOrEmpty(line1) && string.IsNullOrEmpty(line2)) return 0f; + var fontScale = ComputeMapInfoFontScale(width, line1, line2); + ImGui.SetWindowFontScale(fontScale); + var h1 = string.IsNullOrEmpty(line1) ? 0f : ImGui.CalcTextSize(line1).Y; + var h2 = string.IsNullOrEmpty(line2) ? 0f : ImGui.CalcTextSize(line2).Y; + var lineSpacing = line1.Length > 0 && line2.Length > 0 ? 2f : 0f; + ImGui.SetWindowFontScale(1f); + return h1 + h2 + lineSpacing + 6f * ImGuiHelpers.GlobalScale; + } + + private static float ComputeMapInfoFontScale(float width, string line1, string line2) + { + var horzPad = 8f * ImGuiHelpers.GlobalScale; + var maxWidth = Math.Max(1f, width - horzPad); + ImGui.SetWindowFontScale(1f); + var w1 = string.IsNullOrEmpty(line1) ? 0f : ImGui.CalcTextSize(line1).X; + var w2 = string.IsNullOrEmpty(line2) ? 0f : ImGui.CalcTextSize(line2).X; + var maxLineWidth = Math.Max(w1, w2); + if (maxLineWidth <= 0) return 1f; + var autoScale = Math.Clamp(maxWidth / maxLineWidth, 0.5f, 1f); + var mult = Math.Clamp(System.SystemConfig.MinimapMapInfoFontScale, 0.5f, 2f); + return Math.Clamp(autoScale * mult, 0.3f, 1.5f); + } + + private unsafe (string line1, string line2) GetMapInfoLines() + { + var agent = AgentMap.Instance(); + var currentMapId = agent->CurrentMapId; + if (currentMapId == 0) return (string.Empty, string.Empty); + var mapData = Service.DataManager.GetExcelSheet().GetRow(currentMapId); + if (mapData.RowId == 0) return (string.Empty, string.Empty); + var rawParts = new string?[4]; + if (System.SystemConfig.ShowRegionLabel) + rawParts[0] = mapData.PlaceNameRegion.Value.Name.ExtractText(); + if (System.SystemConfig.ShowMapLabel) + rawParts[1] = mapData.PlaceName.Value.Name.ExtractText(); + if (agent->CurrentMapId == currentMapId) { + if (TerritoryInfo.Instance()->AreaPlaceNameId is not 0 && System.SystemConfig.ShowAreaLabel) { + var areaLabel = Service.DataManager.GetExcelSheet().GetRow(TerritoryInfo.Instance()->AreaPlaceNameId); + rawParts[2] = areaLabel.Name.ExtractText(); + } + if (TerritoryInfo.Instance()->SubAreaPlaceNameId is not 0 && System.SystemConfig.ShowSubAreaLabel) { + var subAreaLabel = Service.DataManager.GetExcelSheet().GetRow(TerritoryInfo.Instance()->SubAreaPlaceNameId); + rawParts[3] = subAreaLabel.Name.ExtractText(); + } + } + var order = System.SystemConfig.MinimapMapInfoOrder; + if (order.Length != 4) order = [0, 1, 2, 3]; + var ordered = new List(); + foreach (var idx in order) { + var i = Math.Clamp(idx, 0, 3); + if (rawParts[i] is { } s) + ordered.Add(s); + } + var line1 = ordered.Count >= 2 ? string.Join(" - ", ordered[0], ordered[1]) : (ordered.Count == 1 ? ordered[0] : string.Empty); + var line2 = ordered.Count >= 4 ? string.Join(" - ", ordered[2], ordered[3]) : (ordered.Count == 3 ? ordered[2] : string.Empty); + return (line1, line2); + } + + private static IDisposable? PushMinimapFont(int fontType) + { + return fontType switch { + 1 when System.MinimapAxis12FontHandle != null => System.MinimapAxis12FontHandle.Push(), + 2 when System.MinimapAxis18FontHandle != null => System.MinimapAxis18FontHandle.Push(), + _ => null + }; + } + + private unsafe void DrawMapInfoBar(float width, float height, float _) + { + using var childBg = ImRaii.PushColor(ImGuiCol.ChildBg, System.SystemConfig.MinimapMapInfoBarBackground); + using var child = ImRaii.Child("minimap_mapinfo_bar", new Vector2(width, height), false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse); + if (!child) return; + + var (line1, line2) = GetMapInfoLines(); + if (string.IsNullOrEmpty(line1) && string.IsNullOrEmpty(line2)) return; + + using var _font = PushMinimapFont(System.SystemConfig.MinimapMapInfoFontType); + var fontScale = ComputeMapInfoFontScale(width, line1, line2); + ImGui.SetWindowFontScale(fontScale); + try { + var color = System.SystemConfig.MinimapMapInfoColor; + var totalHeight = 0f; + if (line1.Length > 0) totalHeight += ImGui.CalcTextSize(line1).Y; + if (line2.Length > 0) totalHeight += (line1.Length > 0 ? 2f : 0f) + ImGui.CalcTextSize(line2).Y; + var y = (height - totalHeight) * 0.5f; + if (line1.Length > 0) { + var sz = ImGui.CalcTextSize(line1); + ImGui.SetCursorPos(new Vector2((width - sz.X) * 0.5f, y)); + ImGui.TextColored(color, line1); + y += sz.Y + 2f; + } + if (line2.Length > 0) { + var sz = ImGui.CalcTextSize(line2); + ImGui.SetCursorPos(new Vector2((width - sz.X) * 0.5f, y)); + ImGui.TextColored(color, line2); + } + } finally { + ImGui.SetWindowFontScale(1f); + } + } + + private float ComputeCoordinateBarHeight(float totalWidth, float totalHeight, float topBarHeight) + { + var showCoords = System.SystemConfig.MinimapCoordBarShowCoordinates; + var showTime = System.SystemConfig.MinimapCoordBarShowTime; + var showRepair = System.SystemConfig.MinimapCoordBarShowRepairPercent; + if (!showCoords && !showTime && !showRepair) return 0f; + + var minimapSide = Math.Min(totalWidth, totalHeight - topBarHeight - 20f); + minimapSide = Math.Max(1f, minimapSide); + var baseScale = Math.Clamp(minimapSide / 200f, 0.15f, 1f); + var mult = Math.Clamp(System.SystemConfig.MinimapCoordBarFontScale, 0.5f, 2f); + var fontScale = Math.Clamp(baseScale * mult, 0.15f, 1.5f); + ImGui.SetWindowFontScale(fontScale); + var h = 0f; + if (showCoords) h = Math.Max(h, ImGui.CalcTextSize(" 99.9 99.9 ").Y); + if (showTime) h = Math.Max(h, ImGui.CalcTextSize(DateTime.Now.ToString("h:mm tt")).Y); + if (showRepair) h = Math.Max(h, ImGui.CalcTextSize("100%").Y); + ImGui.SetWindowFontScale(1f); + return h + 8f * ImGuiHelpers.GlobalScale; + } + + private void DrawCoordinateBar(float width, float height, float scale) + { + var showCoords = System.SystemConfig.MinimapCoordBarShowCoordinates; + var showTime = System.SystemConfig.MinimapCoordBarShowTime; + var showRepair = System.SystemConfig.MinimapCoordBarShowRepairPercent; + if (!showCoords && !showTime && !showRepair) return; + + using var childBg = ImRaii.PushColor(ImGuiCol.ChildBg, System.SystemConfig.MinimapCoordBarBackground); + using var child = ImRaii.Child("minimap_coord_bar", new Vector2(width, height), false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse); + if (!child) return; + + var mult = Math.Clamp(System.SystemConfig.MinimapCoordBarFontScale, 0.5f, 2f); + var fontScale = Math.Clamp(scale * mult, 0.15f, 1.5f); + ImGui.SetWindowFontScale(fontScale); + using var _font = PushMinimapFont(System.SystemConfig.MinimapCoordBarFontType); + try { + var color = System.SystemConfig.MinimapCoordBarColor; + var horzPad = 6f * ImGuiHelpers.GlobalScale; + var lineHeight = ImGui.GetTextLineHeight(); + var yPos = Math.Max(0f, (height - lineHeight) * 0.5f - 2f); + var gap = 8f * ImGuiHelpers.GlobalScale; + + var order = System.SystemConfig.MinimapBottomBarOrder; + if (order.Length != 3) order = [0, 1, 2]; + + var texts = new Dictionary(); + if (showCoords && System.MapRenderer.GetCurrentMinimapTransform() is { } transform) { + var pos = Service.ObjectTable.LocalPlayer?.Position ?? Vector3.Zero; + var mapCoord = MapUtil.WorldToMap(new Vector2(pos.X, pos.Z), transform.Item1, transform.Item2, transform.Item3); + texts[0] = $"{mapCoord.X:F1} {mapCoord.Y:F1}"; + } + if (showRepair) texts[1] = $"{EquipmentConditionHelper.GetLowestConditionPercent():F0}%"; + if (showTime) texts[2] = DateTime.Now.ToString("h:mm tt"); + + var visibleOrder = new List(); + foreach (var idx in order) { + var i = Math.Clamp(idx, 0, 2); + if (texts.TryGetValue(i, out var t)) visibleOrder.Add(t); + } + + var cursorX = horzPad; + for (var j = 0; j < visibleOrder.Count; j++) { + var text = visibleOrder[j]; + var sz = ImGui.CalcTextSize(text); + var isLast = j == visibleOrder.Count - 1; + var x = isLast ? width - horzPad - sz.X : cursorX; + ImGui.SetCursorPos(new Vector2(x, yPos)); + ImGui.TextColored(color, text); + if (!isLast) cursorX += sz.X + gap; + } + } finally { + ImGui.SetWindowFontScale(1f); + } } private void UpdateStyle() @@ -113,16 +325,27 @@ public class MinimapWindow : Window Flags &= ~ImGuiWindowFlags.NoMove; } + private unsafe Vector2 GetMinimapWindowSize() + { + var minimapSizePx = System.SystemConfig.MinimapSize; + var topBarHeight = System.SystemConfig.MinimapShowMapInfoBar ? ComputeMapInfoBarHeight(minimapSizePx) : 0f; + var bottomBarHeight = System.SystemConfig.MinimapShowCoordinateBar + ? ComputeCoordinateBarHeight(minimapSizePx, minimapSizePx + topBarHeight + 24f, topBarHeight) + : 0f; + var totalHeight = minimapSizePx + topBarHeight + bottomBarHeight; + return new Vector2(minimapSizePx, totalHeight); + } + private void UpdateSizePosition() { var config = System.SystemConfig; var windowPosition = ImGui.GetWindowPos(); var windowSize = ImGui.GetWindowSize(); - var configSize = config.MinimapSize; + var expectedSize = GetMinimapWindowSize(); // Size is config-only (set in Mappy settings); always apply config size to window. - if (Math.Abs(windowSize.X - configSize) > 0.1f || Math.Abs(windowSize.Y - configSize) > 0.1f) - ImGui.SetWindowSize(new Vector2(configSize, configSize)); + if (Math.Abs(windowSize.X - expectedSize.X) > 0.1f || Math.Abs(windowSize.Y - expectedSize.Y) > 0.1f) + ImGui.SetWindowSize(expectedSize); if (!ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)) { // Not focused: apply config position to window diff --git a/Mappy/pluginmaster.json b/Mappy/pluginmaster.json index f459354..f21de5d 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.14: Movement Trail (Carbonite-style) - red dots show where you've been on map and minimap; configurable distance, fade time, max points; Clear Trail in context menu. 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.14","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.14/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.14/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.14/latest.zip","LastUpdate":"1772326614"}] +[{"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.15: Top/Bottom Info Bars: renamed from Map Info/Coordinate; configurable order for both; Repair % (most damaged item); font, size, color, background for both bars; right-align last bottom bar element. 1.0.0.14: Movement Trail (Carbonite-style) - red dots show where you've been on map and minimap; configurable distance, fade time, max points; Clear Trail in context menu. 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.15","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.15/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.15/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.15/latest.zip","LastUpdate":"1772365750"}]