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