Files
HSMappy/Mappy/Windows/MinimapWindow.cs
T

366 lines
16 KiB
C#

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;
public class MinimapWindow : Window
{
private bool _wasLoggedIn;
public MinimapWindow() : base("HSMappy Minimap###HSMappyMinimap", new Vector2(200.0f, 200.0f))
{
DisableWindowSounds = true;
Flags |= ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollWithMouse;
}
public override bool DrawConditions() =>
IntegrationsController.ShouldShowMinimap() && System.SystemConfig.ShowMinimap;
public override void PreOpenCheck()
{
var isLoggedIn = Service.ClientState is { IsLoggedIn: true, IsPvP: false };
if (!isLoggedIn)
{
IsOpen = false;
_wasLoggedIn = false;
return;
}
// Restore minimap when transitioning from login screen to in-game (ShowMinimap persists in config)
if (!_wasLoggedIn && System.SystemConfig.ShowMinimap)
UnCollapseOrShow();
_wasLoggedIn = true;
}
protected override unsafe void DrawContents()
{
if (System.SystemConfig.MinimapDrawUnderOtherUI)
ImGuiP.BringWindowToDisplayBack(ImGuiP.GetCurrentWindow());
var agent = AgentMap.Instance();
// Try loading from Lumina first so minimap can show without ever opening the area map
if (!System.MapRenderer.HasMinimapCacheFor(agent->CurrentMapId) && agent->SelectedMapId != agent->CurrentMapId)
System.MapRenderer.TryEnsureLuminaCacheFor(agent->CurrentMapId);
var mapLoaded = agent->SelectedMapId == agent->CurrentMapId || System.MapRenderer.HasMinimapCacheFor(agent->CurrentMapId);
if (!mapLoaded)
{
// Map data could not be loaded for this area (no Lumina path matched). Minimap shows automatically when data is available.
const string hint = "Map unavailable for this area.";
var textSize = ImGui.CalcTextSize(hint);
var pos = (ImGui.GetWindowSize() - textSize) * 0.5f;
ImGui.SetCursorPos(new Vector2(Math.Max(0, pos.X), Math.Max(20, pos.Y)));
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.7f, 0.9f), hint);
UpdateStyle();
UpdateSizePosition();
return;
}
UpdateStyle();
UpdateSizePosition();
// Compensate for window padding (minimap gets zero padding from plugin)
var padding = ImGui.GetStyle().WindowPadding;
ImGui.SetCursorPos(new Vector2(-padding.X, -padding.Y));
// 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", minimapSize, false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{
if (child) {
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;
System.SystemConfig.MinimapZoom = Math.Clamp(zoom, 0.03f, 0.112f);
SystemConfig.Save();
}
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);
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<Map>().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<PlaceName>().GetRow(TerritoryInfo.Instance()->AreaPlaceNameId);
rawParts[2] = areaLabel.Name.ExtractText();
}
if (TerritoryInfo.Instance()->SubAreaPlaceNameId is not 0 && System.SystemConfig.ShowSubAreaLabel) {
var subAreaLabel = Service.DataManager.GetExcelSheet<PlaceName>().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<string>();
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<int, string>();
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<string>();
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()
{
if (System.SystemConfig.MinimapLockPosition)
Flags |= ImGuiWindowFlags.NoMove;
else
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 expectedSize = GetMinimapWindowSize();
// Size is config-only (set in Mappy settings); always apply config size to window.
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
if (windowPosition != config.MinimapPosition)
ImGui.SetWindowPos(config.MinimapPosition);
} else {
// Focused: save window position to config (size is changed only via settings)
if (config.MinimapPosition != windowPosition) {
config.MinimapPosition = windowPosition;
SystemConfig.Save();
}
}
}
}