Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 340030f826 | |||
| e160af2ab5 | |||
| b4638eb60b |
@@ -36,6 +36,10 @@ public unsafe class MapContextMenu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Clear Movement Trail")) {
|
||||||
|
System.MovementTrailConfig.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Place Flag")) {
|
if (ImGui.MenuItem("Place Flag")) {
|
||||||
AgentMap.Instance()->FlagMarkerCount = 0;
|
AgentMap.Instance()->FlagMarkerCount = 0;
|
||||||
AgentMap.Instance()->SetFlagMapMarker(AgentMap.Instance()->SelectedTerritoryId, AgentMap.Instance()->SelectedMapId, scaledResult.X, scaledResult.Y);
|
AgentMap.Instance()->SetFlagMapMarker(AgentMap.Instance()->SelectedTerritoryId, AgentMap.Instance()->SelectedMapId, scaledResult.X, scaledResult.Y);
|
||||||
|
|||||||
@@ -173,6 +173,18 @@ public unsafe class IntegrationsController : IDisposable
|
|||||||
// so quest turn-in and objective progression during suppression still trigger refresh when suppression ends.
|
// so quest turn-in and objective progression during suppression still trigger refresh when suppression ends.
|
||||||
_lastTempMarkerCount = tempCount;
|
_lastTempMarkerCount = tempCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Movement trail: record player position when enabled (Carbonite-style)
|
||||||
|
if (System.SystemConfig.ShowMovementTrail && Service.ObjectTable.LocalPlayer is { } localPlayer) {
|
||||||
|
try {
|
||||||
|
var agent = AgentMap.Instance();
|
||||||
|
System.MovementTrailConfig.TryAddPoint(
|
||||||
|
agent->CurrentTerritoryId,
|
||||||
|
agent->CurrentMapId,
|
||||||
|
localPlayer.Position.X,
|
||||||
|
localPlayer.Position.Z);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Build a string of (QuestId, Sequence) for each active quest so we can detect step advances.</summary>
|
/// <summary>Build a string of (QuestId, Sequence) for each active quest so we can detect step advances.</summary>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
|
||||||
|
namespace Mappy.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the condition percentage of the player's most damaged equipped item.
|
||||||
|
/// Uses the same logic as RepairMe (chalkos/RepairMe): raw condition / 300.
|
||||||
|
/// </summary>
|
||||||
|
public static class EquipmentConditionHelper
|
||||||
|
{
|
||||||
|
private const uint EquipmentContainerSize = 13;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the lowest condition percent among equipped items (0–100+).
|
||||||
|
/// Returns 100 if no gear or unavailable (e.g. not logged in).
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Mappy.Data;
|
||||||
|
|
||||||
|
/// <summary>Single point on the movement trail (Carbonite-style).</summary>
|
||||||
|
public record MovementTrailPoint(uint Territory, uint Map, float X, float Y, double TimeStamp);
|
||||||
|
|
||||||
|
/// <summary>Config and storage for the movement trail (where you've been on the map).</summary>
|
||||||
|
public class MovementTrailConfig
|
||||||
|
{
|
||||||
|
private readonly List<MovementTrailPoint> _points = [];
|
||||||
|
private int _nextIndex;
|
||||||
|
private float _lastX = float.MinValue;
|
||||||
|
private float _lastY = float.MinValue;
|
||||||
|
private uint _lastTerritory;
|
||||||
|
|
||||||
|
public int MaxPoints { get; set; } = 100;
|
||||||
|
public float MinDistance { get; set; } = 2f;
|
||||||
|
public float FadeTimeSeconds { get; set; } = 60f;
|
||||||
|
public Vector4 TrailColor { get; set; } = new(1f, 0f, 0f, 0.9f); // Red like Carbonite
|
||||||
|
|
||||||
|
public IReadOnlyList<MovementTrailPoint> Points => _points;
|
||||||
|
|
||||||
|
private float EffectiveMinDistance => Mappy.System.SystemConfig?.MovementTrailMinDistance ?? MinDistance;
|
||||||
|
private float EffectiveFadeTime => Mappy.System.SystemConfig?.MovementTrailFadeTimeSeconds ?? FadeTimeSeconds;
|
||||||
|
private int EffectiveMaxPoints => Mappy.System.SystemConfig?.MovementTrailMaxPoints ?? MaxPoints;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_points.Clear();
|
||||||
|
_nextIndex = 0;
|
||||||
|
_lastX = float.MinValue;
|
||||||
|
_lastY = float.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Record a new trail point if the player has moved enough. Call from Framework.Update.</summary>
|
||||||
|
public void TryAddPoint(uint territory, uint map, float x, float y)
|
||||||
|
{
|
||||||
|
// Reset when changing territory
|
||||||
|
if (territory != _lastTerritory)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
_lastTerritory = territory;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dx = x - _lastX;
|
||||||
|
var dy = y - _lastY;
|
||||||
|
var moveDist = MathF.Sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (moveDist < EffectiveMinDistance && _lastX > float.MinValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastX = x;
|
||||||
|
_lastY = y;
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||||
|
|
||||||
|
var max = EffectiveMaxPoints;
|
||||||
|
if (_points.Count < max)
|
||||||
|
{
|
||||||
|
_points.Add(new MovementTrailPoint(territory, map, x, y, now));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_points[_nextIndex] = new MovementTrailPoint(territory, map, x, y, now);
|
||||||
|
_nextIndex = (_nextIndex + 1) % max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get points for the current map that haven't faded yet.</summary>
|
||||||
|
public IEnumerable<MovementTrailPoint> GetVisiblePoints(uint territoryId, uint mapId)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||||
|
var fade = EffectiveFadeTime;
|
||||||
|
return _points
|
||||||
|
.Where(p => p.Territory == territoryId && p.Map == mapId && (now - p.TimeStamp) < fade)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,6 +122,48 @@ public class SystemConfig : CharacterConfiguration
|
|||||||
public bool MinimapShowPlayersAndNpcs = true;
|
public bool MinimapShowPlayersAndNpcs = true;
|
||||||
/// <summary>Icon ID for other players on the minimap (default 60403, distinct from party 60421). Override if you prefer a different look.</summary>
|
/// <summary>Icon ID for other players on the minimap (default 60403, distinct from party 60421). Override if you prefer a different look.</summary>
|
||||||
public uint MinimapOtherPlayerIconId = 60403;
|
public uint MinimapOtherPlayerIconId = 60403;
|
||||||
|
/// <summary>Draw minimap underneath other plugin UI (e.g. HSUI). Uses ImGui BringWindowToDisplayBack.</summary>
|
||||||
|
public bool MinimapDrawUnderOtherUI = true;
|
||||||
|
/// <summary>Show current map info (region, map, area, sub-area) at the top of the minimap.</summary>
|
||||||
|
public bool MinimapShowMapInfoBar = true;
|
||||||
|
/// <summary>Order of map info: 0=Region, 1=Map, 2=Area, 3=SubArea. e.g. {0,1,2,3} = Region, Map, Area, SubArea.</summary>
|
||||||
|
public int[] MinimapMapInfoOrder = [0, 1, 2, 3];
|
||||||
|
/// <summary>Font scale multiplier for map info bar (0.5-2.0).</summary>
|
||||||
|
public float MinimapMapInfoFontScale = 1.0f;
|
||||||
|
/// <summary>Text color for map info bar.</summary>
|
||||||
|
public Vector4 MinimapMapInfoColor = KnownColor.White.Vector();
|
||||||
|
/// <summary>Font type for map info: 0=Default, 1=Axis12, 2=Axis18.</summary>
|
||||||
|
public int MinimapMapInfoFontType = 0;
|
||||||
|
/// <summary>Background color (RGBA) for map info bar. Alpha = opacity.</summary>
|
||||||
|
public Vector4 MinimapMapInfoBarBackground = new(0f, 0f, 0f, 0.2f);
|
||||||
|
/// <summary>Show player coordinates at the bottom of the minimap.</summary>
|
||||||
|
public bool MinimapShowCoordinateBar = true;
|
||||||
|
/// <summary>Show coordinates in the bottom bar.</summary>
|
||||||
|
public bool MinimapCoordBarShowCoordinates = true;
|
||||||
|
/// <summary>Show local time in the bottom bar.</summary>
|
||||||
|
public bool MinimapCoordBarShowTime = true;
|
||||||
|
/// <summary>Show repair % (lowest equipped item condition) in the bottom bar.</summary>
|
||||||
|
public bool MinimapCoordBarShowRepairPercent = true;
|
||||||
|
/// <summary>Order of bottom bar elements: 0=Coordinates, 1=Repair %, 2=Local Time.</summary>
|
||||||
|
public int[] MinimapBottomBarOrder = [0, 1, 2];
|
||||||
|
/// <summary>Font scale multiplier for coordinate bar (0.5-2.0).</summary>
|
||||||
|
public float MinimapCoordBarFontScale = 1.0f;
|
||||||
|
/// <summary>Text color for coordinate bar.</summary>
|
||||||
|
public Vector4 MinimapCoordBarColor = KnownColor.White.Vector();
|
||||||
|
/// <summary>Font type for coord bar: 0=Default, 1=Axis12, 2=Axis18.</summary>
|
||||||
|
public int MinimapCoordBarFontType = 0;
|
||||||
|
/// <summary>Background color (RGBA) for coordinate bar. Alpha = opacity.</summary>
|
||||||
|
public Vector4 MinimapCoordBarBackground = new(0f, 0f, 0f, 0.2f);
|
||||||
|
|
||||||
|
// Movement Trail (Carbonite-style: show where you've been)
|
||||||
|
/// <summary>Draw a red trail of dots on the map showing where you've been.</summary>
|
||||||
|
public bool ShowMovementTrail = false;
|
||||||
|
/// <summary>Minimum distance (world units) before adding a new trail point.</summary>
|
||||||
|
public float MovementTrailMinDistance = 2f;
|
||||||
|
/// <summary>How long (seconds) trail points stay visible before fading out.</summary>
|
||||||
|
public float MovementTrailFadeTimeSeconds = 60f;
|
||||||
|
/// <summary>Maximum number of trail points to keep.</summary>
|
||||||
|
public int MovementTrailMaxPoints = 100;
|
||||||
|
|
||||||
// Do not persist this setting
|
// Do not persist this setting
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
|||||||
@@ -145,6 +145,21 @@ public unsafe partial class MapRenderer : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasMinimapCacheFor(uint mapId) => _minimapCache.ContainsKey(mapId);
|
public bool HasMinimapCacheFor(uint mapId) => _minimapCache.ContainsKey(mapId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the map transform (offsetX, offsetY, sizeFactor) for the current minimap map, for coordinate display.
|
||||||
|
/// Returns null if no cache exists for CurrentMapId.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to load map texture and transform from Lumina (Map sheet) so the minimap can draw without opening the area map.
|
/// 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.
|
/// Uses game map path conventions (ui/map/...) and Map.SizeFactor, Map.OffsetX/Y. On success, fills the cache for this map.
|
||||||
@@ -377,6 +392,7 @@ public unsafe partial class MapRenderer : IDisposable
|
|||||||
DrawPlayer();
|
DrawPlayer();
|
||||||
DrawStaticTextMarkers();
|
DrawStaticTextMarkers();
|
||||||
DrawMapNotes();
|
DrawMapNotes();
|
||||||
|
DrawMovementTrail();
|
||||||
DrawFlag();
|
DrawFlag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,6 +62,8 @@ public partial class MapRenderer
|
|||||||
DrawMinimapFlag(contentTopLeft, TexToContent, scaleFactor, offsetX, offsetY);
|
DrawMinimapFlag(contentTopLeft, TexToContent, scaleFactor, offsetX, offsetY);
|
||||||
// User map notes
|
// User map notes
|
||||||
DrawMinimapMapNotes(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
DrawMinimapMapNotes(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
||||||
|
// Movement trail
|
||||||
|
DrawMinimapMovementTrail(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
||||||
// Temporary (quest objectives, etc.)
|
// Temporary (quest objectives, etc.)
|
||||||
DrawMinimapTempMarkers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY, scale);
|
DrawMinimapTempMarkers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY, scale);
|
||||||
// Field markers (waymarks)
|
// Field markers (waymarks)
|
||||||
@@ -729,6 +731,33 @@ public partial class MapRenderer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void DrawMinimapMovementTrail(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY)
|
||||||
|
{
|
||||||
|
if (!System.SystemConfig.ShowMovementTrail) return;
|
||||||
|
|
||||||
|
var agent = AgentMap.Instance();
|
||||||
|
var territoryId = agent->CurrentTerritoryId;
|
||||||
|
var mapId = agent->CurrentMapId;
|
||||||
|
var now = DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||||
|
var fadeTime = System.SystemConfig.MovementTrailFadeTimeSeconds;
|
||||||
|
|
||||||
|
foreach (var pt in System.MovementTrailConfig.GetVisiblePoints(territoryId, mapId).ToList()) {
|
||||||
|
var age = now - pt.TimeStamp;
|
||||||
|
var alpha = (float)((fadeTime - age) / fadeTime * 0.9);
|
||||||
|
if (alpha <= 0f) continue;
|
||||||
|
|
||||||
|
var tx = 1024.0f + (pt.X - offsetX) * scaleFactor;
|
||||||
|
var ty = 1024.0f + (pt.Y - offsetY) * scaleFactor;
|
||||||
|
var contentPos = texToContent(tx, ty);
|
||||||
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
||||||
|
|
||||||
|
var centerScreen = contentPos + contentTopLeft;
|
||||||
|
var trailSize = 4f * 0.75f;
|
||||||
|
var color = System.MovementTrailConfig.TrailColor with { W = alpha };
|
||||||
|
ImGui.GetWindowDrawList().AddCircleFilled(centerScreen, trailSize, ImGui.GetColorU32(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe void DrawMinimapMapNotes(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY)
|
private unsafe void DrawMinimapMapNotes(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY)
|
||||||
{
|
{
|
||||||
var agent = AgentMap.Instance();
|
var agent = AgentMap.Instance();
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using Mappy.Classes;
|
||||||
|
using Mappy.Data;
|
||||||
|
|
||||||
|
namespace Mappy.MapRenderer;
|
||||||
|
|
||||||
|
public partial class MapRenderer
|
||||||
|
{
|
||||||
|
private unsafe void DrawMovementTrail()
|
||||||
|
{
|
||||||
|
if (!System.SystemConfig.ShowMovementTrail) return;
|
||||||
|
|
||||||
|
var agent = AgentMap.Instance();
|
||||||
|
var territoryId = agent->SelectedTerritoryId;
|
||||||
|
var mapId = agent->SelectedMapId;
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||||
|
var fadeTime = System.SystemConfig.MovementTrailFadeTimeSeconds;
|
||||||
|
|
||||||
|
foreach (var pt in System.MovementTrailConfig.GetVisiblePoints(territoryId, mapId).ToList()) {
|
||||||
|
var age = now - pt.TimeStamp;
|
||||||
|
var alpha = (float)((fadeTime - age) / fadeTime * 0.9);
|
||||||
|
if (alpha <= 0f) continue;
|
||||||
|
|
||||||
|
// Same coordinate space as map notes: world X,Z
|
||||||
|
var pos = new Vector2(pt.X, pt.Y) * Scale * DrawHelpers.GetMapScaleFactor() + DrawHelpers.GetCombinedOffsetVector() * Scale;
|
||||||
|
|
||||||
|
var size = Math.Clamp(4 * Scale, 3f, 25f);
|
||||||
|
var screenPos = ImGui.GetWindowPos() + DrawPosition + pos;
|
||||||
|
|
||||||
|
var color = System.MovementTrailConfig.TrailColor with { W = alpha };
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
drawList.AddCircleFilled(screenPos, size, ImGui.GetColorU32(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<Name>HSMappy</Name>
|
<Name>HSMappy</Name>
|
||||||
<InternalName>HSMappy</InternalName>
|
<InternalName>HSMappy</InternalName>
|
||||||
<Author>Knack117</Author>
|
<Author>Knack117</Author>
|
||||||
<Version>1.0.0.13</Version>
|
<Version>1.0.0.16</Version>
|
||||||
<Punchline>A more versatile in-game map.</Punchline>
|
<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>
|
<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>
|
<RepoUrl>http://brassnet.ddns.net:33983/KnackAtNite/HSMappy</RepoUrl>
|
||||||
|
|||||||
@@ -28,10 +28,14 @@ public sealed class MappyPlugin : IDalamudPlugin
|
|||||||
BaseSkewStrength = 16f,
|
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.SystemConfig = SystemConfig.Load();
|
||||||
System.IconConfig = IconConfig.Load();
|
System.IconConfig = IconConfig.Load();
|
||||||
System.FlagConfig = FlagConfig.Load();
|
System.FlagConfig = FlagConfig.Load();
|
||||||
System.MapNoteConfig = MapNoteConfig.Load();
|
System.MapNoteConfig = MapNoteConfig.Load();
|
||||||
|
System.MovementTrailConfig = new MovementTrailConfig();
|
||||||
|
|
||||||
System.Teleporter = new Teleporter(Service.PluginInterface);
|
System.Teleporter = new Teleporter(Service.PluginInterface);
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public static class System
|
|||||||
public static IconConfig IconConfig { get; set; }
|
public static IconConfig IconConfig { get; set; }
|
||||||
public static FlagConfig FlagConfig { get; set; }
|
public static FlagConfig FlagConfig { get; set; }
|
||||||
public static MapNoteConfig MapNoteConfig { get; set; }
|
public static MapNoteConfig MapNoteConfig { get; set; }
|
||||||
|
public static MovementTrailConfig MovementTrailConfig { get; set; }
|
||||||
public static WindowManager WindowManager { get; set; }
|
public static WindowManager WindowManager { get; set; }
|
||||||
public static MapWindow MapWindow { get; set; }
|
public static MapWindow MapWindow { get; set; }
|
||||||
public static MinimapWindow MinimapWindow { get; set; }
|
public static MinimapWindow MinimapWindow { get; set; }
|
||||||
@@ -44,4 +45,6 @@ public static class System
|
|||||||
public static AetheryteAethernetCache AetheryteAethernetCache { get; set; } = new();
|
public static AetheryteAethernetCache AetheryteAethernetCache { get; set; } = new();
|
||||||
|
|
||||||
public static IFontHandle LargeAxisFontHandle { get; set; }
|
public static IFontHandle LargeAxisFontHandle { get; set; }
|
||||||
|
public static IFontHandle MinimapAxis12FontHandle { get; set; }
|
||||||
|
public static IFontHandle MinimapAxis18FontHandle { get; set; }
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,18 @@ public class MapFunctionsTab : ITabItem
|
|||||||
configChanged |= ImGui.Checkbox("Center on Quest", ref System.SystemConfig.CenterOnQuest);
|
configChanged |= ImGui.Checkbox("Center on Quest", ref System.SystemConfig.CenterOnQuest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGuiTweaks.Header("Movement Trail (Carbonite-style)");
|
||||||
|
using (ImRaii.PushIndent()) {
|
||||||
|
configChanged |= ImGui.Checkbox("Show Movement Trail", ref System.SystemConfig.ShowMovementTrail);
|
||||||
|
ImGui.TextDisabled("Draw a trail of red dots showing where you've been on the map.");
|
||||||
|
if (System.SystemConfig.ShowMovementTrail) {
|
||||||
|
configChanged |= ImGui.SliderFloat("Min Distance (world units)", ref System.SystemConfig.MovementTrailMinDistance, 0.5f, 10f);
|
||||||
|
configChanged |= ImGui.SliderFloat("Fade Time (seconds)", ref System.SystemConfig.MovementTrailFadeTimeSeconds, 10f, 300f);
|
||||||
|
configChanged |= ImGui.SliderInt("Max Points", ref System.SystemConfig.MovementTrailMaxPoints, 20, 500);
|
||||||
|
}
|
||||||
|
ImGuiHelpers.ScaledDummy(5.0f);
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiTweaks.Header("Misc Options");
|
ImGuiTweaks.Header("Misc Options");
|
||||||
using (ImRaii.PushIndent()) {
|
using (ImRaii.PushIndent()) {
|
||||||
configChanged |= ImGui.Checkbox("Show Misc Tooltips", ref System.SystemConfig.ShowMiscTooltips);
|
configChanged |= ImGui.Checkbox("Show Misc Tooltips", ref System.SystemConfig.ShowMiscTooltips);
|
||||||
@@ -266,7 +278,7 @@ public class MinimapOptionsTab : ITabItem
|
|||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5.0f);
|
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))
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
ImGui.SetTooltip("Minimap size. Resize only via this setting (no corner grip).");
|
ImGui.SetTooltip("Minimap size. Resize only via this setting (no corner grip).");
|
||||||
configChanged |= ImGui.DragFloat2("Position", ref System.SystemConfig.MinimapPosition);
|
configChanged |= ImGui.DragFloat2("Position", ref System.SystemConfig.MinimapPosition);
|
||||||
@@ -300,12 +312,113 @@ public class MinimapOptionsTab : ITabItem
|
|||||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
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.");
|
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("Lock Position", ref System.SystemConfig.MinimapLockPosition);
|
||||||
|
configChanged |= ImGui.Checkbox("Draw Under Other UI", ref System.SystemConfig.MinimapDrawUnderOtherUI);
|
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
|
ImGui.SetTooltip("When enabled, the minimap draws underneath other plugin UI (e.g. HSUI). Disable to draw the minimap on top.");
|
||||||
|
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) {
|
if (configChanged) {
|
||||||
SystemConfig.Save();
|
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
|
public class PlayerOptionsTab : ITabItem
|
||||||
|
|||||||
+239
-13
@@ -1,11 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Utility;
|
||||||
using KamiLib.Window;
|
using KamiLib.Window;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
using Mappy.Controllers;
|
using Mappy.Controllers;
|
||||||
using Mappy.Data;
|
using Mappy.Data;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using Map = Lumina.Excel.Sheets.Map;
|
||||||
|
|
||||||
namespace Mappy.Windows;
|
namespace Mappy.Windows;
|
||||||
|
|
||||||
@@ -40,6 +46,9 @@ public class MinimapWindow : Window
|
|||||||
|
|
||||||
protected override unsafe void DrawContents()
|
protected override unsafe void DrawContents()
|
||||||
{
|
{
|
||||||
|
if (System.SystemConfig.MinimapDrawUnderOtherUI)
|
||||||
|
ImGuiP.BringWindowToDisplayBack(ImGuiP.GetCurrentWindow());
|
||||||
|
|
||||||
var agent = AgentMap.Instance();
|
var agent = AgentMap.Instance();
|
||||||
// Try loading from Lumina first so minimap can show without ever opening the area map
|
// 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)
|
if (!System.MapRenderer.HasMinimapCacheFor(agent->CurrentMapId) && agent->SelectedMapId != agent->CurrentMapId)
|
||||||
@@ -63,46 +72,252 @@ public class MinimapWindow : Window
|
|||||||
UpdateStyle();
|
UpdateStyle();
|
||||||
UpdateSizePosition();
|
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 padding = ImGui.GetStyle().WindowPadding;
|
||||||
var winSize = ImGui.GetWindowSize();
|
|
||||||
ImGui.SetCursorPos(new Vector2(-padding.X, -padding.Y));
|
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;
|
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.Alpha, System.SystemConfig.MinimapOpacity))
|
||||||
using (ImRaii.PushStyle(ImGuiStyleVar.ChildBorderSize, 0f))
|
using (ImRaii.PushStyle(ImGuiStyleVar.ChildBorderSize, 0f))
|
||||||
using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, Vector2.Zero))
|
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) {
|
if (child) {
|
||||||
// Use window size so map fills the full window; renderer clamps draw position so map always covers the view.
|
System.MapRenderer.DrawMinimapContents(minimapSize);
|
||||||
System.MapRenderer.DrawMinimapContents(contentSize);
|
|
||||||
// Mouse wheel over minimap: zoom in/out, and consume wheel so the window doesn't scroll
|
|
||||||
if (ImGui.IsItemHovered()) {
|
if (ImGui.IsItemHovered()) {
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
var wheel = io.MouseWheel;
|
var wheel = io.MouseWheel;
|
||||||
if (wheel != 0) {
|
if (wheel != 0) {
|
||||||
var zoom = System.SystemConfig.MinimapZoom;
|
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);
|
System.SystemConfig.MinimapZoom = Math.Clamp(zoom, 0.03f, 0.112f);
|
||||||
SystemConfig.Save();
|
SystemConfig.Save();
|
||||||
}
|
}
|
||||||
// Consume wheel so the window doesn't scroll when at min/max zoom or when we handled it
|
|
||||||
io.MouseWheel = 0f;
|
io.MouseWheel = 0f;
|
||||||
io.MouseWheelH = 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).
|
// Restore default padding for the next window is done in plugin Draw callback (PopStyleVar after all windows).
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
{
|
{
|
||||||
ImGui.SetWindowPos(System.SystemConfig.MinimapPosition);
|
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<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()
|
private void UpdateStyle()
|
||||||
@@ -113,16 +328,27 @@ public class MinimapWindow : Window
|
|||||||
Flags &= ~ImGuiWindowFlags.NoMove;
|
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()
|
private void UpdateSizePosition()
|
||||||
{
|
{
|
||||||
var config = System.SystemConfig;
|
var config = System.SystemConfig;
|
||||||
var windowPosition = ImGui.GetWindowPos();
|
var windowPosition = ImGui.GetWindowPos();
|
||||||
var windowSize = ImGui.GetWindowSize();
|
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.
|
// 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)
|
if (Math.Abs(windowSize.X - expectedSize.X) > 0.1f || Math.Abs(windowSize.Y - expectedSize.Y) > 0.1f)
|
||||||
ImGui.SetWindowSize(new Vector2(configSize, configSize));
|
ImGui.SetWindowSize(expectedSize);
|
||||||
|
|
||||||
if (!ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)) {
|
if (!ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)) {
|
||||||
// Not focused: apply config position to window
|
// Not focused: apply config position to window
|
||||||
|
|||||||
@@ -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.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"}]
|
[{"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.16: Draw minimap underneath other UI (HSUI etc); Draw Under Other UI config option. 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.16","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.16/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.16/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.16/latest.zip","LastUpdate":"1772366577"}]
|
||||||
|
|||||||
Reference in New Issue
Block a user