3afa7af645
Made-with: Cursor
865 lines
42 KiB
C#
865 lines
42 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Game.Text;
|
|
using Dalamud.Interface.Textures;
|
|
using Dalamud.Interface.Textures.TextureWraps;
|
|
using Dalamud.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Fate;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using FFXIVClientStructs.Interop;
|
|
using Lumina.Excel.Sheets;
|
|
using Mappy.Classes;
|
|
using Mappy.Extensions;
|
|
using KamiLib.Extensions;
|
|
using FieldMarkerSheet = Lumina.Excel.Sheets.FieldMarker;
|
|
|
|
namespace Mappy.MapRenderer;
|
|
|
|
/// <summary>
|
|
/// Draws map markers (POI, FATEs, quests, gathering, flag, etc.) on the minimap using the same data sources as the main map.
|
|
/// </summary>
|
|
public partial class MapRenderer
|
|
{
|
|
private const float MinimapIconSize = 28f;
|
|
private const float MinimapIconScaleFromConfig = 1.0f;
|
|
|
|
/// <summary>
|
|
/// Draw all marker layers on the minimap. Call after the map texture and player are drawn.
|
|
/// </summary>
|
|
private void DrawMinimapMarkers(
|
|
Vector2 contentTopLeft,
|
|
Vector2 drawPosition,
|
|
float scale,
|
|
Vector2 size,
|
|
float offsetX,
|
|
float offsetY,
|
|
float scaleFactor)
|
|
{
|
|
// Convert texture-space position (0..2048) to minimap content position.
|
|
Vector2 TexToContent(float tx, float ty) => drawPosition + new Vector2(tx, ty) * scale;
|
|
|
|
// Static POI / map markers (aetherytes, quest NPCs, etc.)
|
|
DrawMinimapStaticMarkers(contentTopLeft, TexToContent, size);
|
|
// FATEs from FateManager so they update without opening the Area Map
|
|
DrawMinimapFatesFromFateManager(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
|
// Event markers (non-FATE; FATEs drawn above from FateManager)
|
|
DrawMinimapEventMarkers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY, scale);
|
|
// Gathering points
|
|
DrawMinimapGatheringMarkers(contentTopLeft, TexToContent, size);
|
|
// Party / alliance members (same marker as main map)
|
|
DrawMinimapGroupMembers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
|
// Player / NPC tracking (other players, enemies, bosses, etc.)
|
|
DrawMinimapGameObjects(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
|
// User flag
|
|
DrawMinimapFlag(contentTopLeft, TexToContent, scaleFactor, offsetX, offsetY);
|
|
// Temporary (quest objectives, etc.)
|
|
DrawMinimapTempMarkers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY, scale);
|
|
// Field markers (waymarks)
|
|
DrawMinimapFieldMarkers(contentTopLeft, TexToContent, size, scaleFactor, offsetX, offsetY);
|
|
}
|
|
|
|
/// <summary>Max number of quest/objective direction arrows to show on the minimap (to avoid clutter).</summary>
|
|
private const int MaximapQuestArrowLimit = 8;
|
|
|
|
/// <summary>Same orange as the quest direction arrow, for the minimap quest radius circle (more transparent).</summary>
|
|
private static readonly Vector4 MinimapQuestCircleFill = new(0.95f, 0.78f, 0.4f, 0.48f);
|
|
private static readonly Vector4 MinimapQuestCircleOutline = new(0.45f, 0.28f, 0.08f, 0.5f);
|
|
|
|
/// <summary>
|
|
/// Draws direction arrows at the edge of the minimap pointing toward quest objectives and waymark in the current zone.
|
|
/// Only shown when the target is off the minimap. Includes the quest/objective marker icon and default-style arrow shape.
|
|
/// </summary>
|
|
private unsafe void DrawMinimapQuestDirectionArrow(
|
|
Vector2 contentTopLeft,
|
|
Vector2 drawPosition,
|
|
float scale,
|
|
Vector2 size,
|
|
Vector2 centerOffset,
|
|
float offsetX,
|
|
float offsetY,
|
|
float scaleFactor,
|
|
uint currentMapId)
|
|
{
|
|
var targets = GetAllQuestDirectionTargets(currentMapId, offsetX, offsetY, scaleFactor);
|
|
if (targets.Count == 0) return;
|
|
|
|
var radius = Math.Min(size.X, size.Y) * 0.5f;
|
|
var arrowDist = radius - 4f;
|
|
var centerScreen = contentTopLeft + centerOffset;
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
// Default UI-style arrowhead: slightly larger, fill + crisp outline (no shaft)
|
|
const float arrowSize = 20f;
|
|
const float baseHalf = 8f;
|
|
const float headDepth = 5f;
|
|
var colorHead = ImGui.GetColorU32(new Vector4(0.95f, 0.78f, 0.4f, 0.95f));
|
|
var colorOutline = ImGui.GetColorU32(new Vector4(0.45f, 0.28f, 0.08f, 1f));
|
|
|
|
var drawn = 0;
|
|
foreach (var (tx, ty, arrowTooltip, questMarkerIconId) in targets) {
|
|
if (drawn >= MaximapQuestArrowLimit) break;
|
|
|
|
var targetInContent = drawPosition + new Vector2(tx, ty) * scale;
|
|
var toTarget = targetInContent - centerOffset;
|
|
var distToTarget = toTarget.Length();
|
|
if (distToTarget < 0.01f) continue;
|
|
if (distToTarget <= radius) continue; // Target visible on minimap, no arrow
|
|
|
|
var direction = toTarget / distToTarget;
|
|
var arrowPos = centerScreen + direction * arrowDist;
|
|
var cos = MathF.Cos(MathF.Atan2(direction.Y, direction.X));
|
|
var sin = MathF.Sin(MathF.Atan2(direction.Y, direction.X));
|
|
var perpX = -sin;
|
|
var perpY = cos;
|
|
var tipScreen = arrowPos + new Vector2(cos * arrowSize, sin * arrowSize);
|
|
var baseBack = arrowPos - new Vector2(cos * headDepth, sin * headDepth);
|
|
var base1Screen = baseBack + new Vector2(perpX * baseHalf, perpY * baseHalf);
|
|
var base2Screen = baseBack - new Vector2(perpX * baseHalf, perpY * baseHalf);
|
|
|
|
drawList.AddTriangleFilled(tipScreen, base1Screen, base2Screen, colorHead);
|
|
drawList.AddTriangle(tipScreen, base1Screen, base2Screen, colorOutline, 2f);
|
|
|
|
if (!string.IsNullOrEmpty(arrowTooltip)) {
|
|
var minX = Math.Min(tipScreen.X, Math.Min(base1Screen.X, base2Screen.X)) - 4f;
|
|
var minY = Math.Min(tipScreen.Y, Math.Min(base1Screen.Y, base2Screen.Y)) - 4f;
|
|
var maxX = Math.Max(tipScreen.X, Math.Max(base1Screen.X, base2Screen.X)) + 4f;
|
|
var maxY = Math.Max(tipScreen.Y, Math.Max(base1Screen.Y, base2Screen.Y)) + 4f;
|
|
if (ImGui.IsMouseHoveringRect(new Vector2(minX, minY), new Vector2(maxX, maxY)))
|
|
ImGui.SetTooltip(arrowTooltip);
|
|
}
|
|
drawn++;
|
|
}
|
|
}
|
|
|
|
/// <summary>Collects all quest/flag direction targets in the current zone (flag, temp markers, cached temps, journal objectives).</summary>
|
|
private unsafe List<(float tx, float ty, string? tooltip, uint? iconId)> GetAllQuestDirectionTargets(uint currentMapId, float offsetX, float offsetY, float scaleFactor)
|
|
{
|
|
var list = new List<(float tx, float ty, string? tooltip, uint? iconId)>();
|
|
var agent = AgentMap.Instance();
|
|
const float samePosEpsilon = 2f; // texture-space distance to consider same target
|
|
|
|
void AddIfNew(float tx, float ty, string? tooltip, uint? iconId)
|
|
{
|
|
foreach (var (otx, oty, _, _) in list)
|
|
if (Math.Abs(otx - tx) < samePosEpsilon && Math.Abs(oty - ty) < samePosEpsilon) return;
|
|
list.Add((tx, ty, tooltip, iconId));
|
|
}
|
|
|
|
// Flag excluded: drawn separately with red arrow via DrawMinimapFlagDirectionArrow
|
|
|
|
if (agent->TempMapMarkerCount > 0) {
|
|
var span = new Span<TempMapMarker>(Unsafe.AsPointer(ref agent->TempMapMarkers[0]), agent->TempMapMarkerCount);
|
|
var groups = span.ToArray().GroupBy(m => new Vector2(m.MapMarker.X, m.MapMarker.Y));
|
|
foreach (var group in groups) {
|
|
var first = group.First();
|
|
var iconId = group.FirstOrNull(m => m.MapMarker.IconId is not (60493 or 0))?.MapMarker.IconId ?? first.MapMarker.IconId;
|
|
if (iconId is 0 && group.Count() == 2 && first.Type == 4 && group.Last() is { Type: 6, MapMarker.IconId: 0 })
|
|
iconId = DrawHelpers.QuestionMarkIcon;
|
|
var tooltip = first.TooltipText.ToString();
|
|
var tx = 1024.0f + (first.MapMarker.X / 16.0f - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (first.MapMarker.Y / 16.0f - offsetY) * scaleFactor;
|
|
AddIfNew(tx, ty, tooltip, iconId is 0 ? null : iconId);
|
|
}
|
|
}
|
|
|
|
if (TempMarkerCache.TryGetValue(currentMapId, out var cached)) {
|
|
foreach (var m in cached) {
|
|
var tx = 1024.0f + (m.X / 16.0f - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (m.Y / 16.0f - offsetY) * scaleFactor;
|
|
AddIfNew(tx, ty, m.Tooltip, m.IconId);
|
|
}
|
|
}
|
|
|
|
foreach (var (tx, ty, tooltip) in GetAllJournalQuestObjectivesInZone(currentMapId, offsetX, offsetY, scaleFactor))
|
|
AddIfNew(tx, ty, tooltip, 60470u);
|
|
|
|
return list;
|
|
}
|
|
|
|
/// <summary>Get texture positions and tooltips for all quests in the journal that have an objective on the given map.</summary>
|
|
private static unsafe List<(float tx, float ty, string? tooltip)> GetAllJournalQuestObjectivesInZone(uint currentMapId, float offsetX, float offsetY, float scaleFactor)
|
|
{
|
|
var result = new List<(float tx, float ty, string? tooltip)>();
|
|
try
|
|
{
|
|
var questSheet = Service.DataManager.GetExcelSheet<Quest>();
|
|
if (questSheet is null) return result;
|
|
|
|
foreach (var quest in QuestManager.Instance()->NormalQuests)
|
|
{
|
|
if (quest.QuestId is 0) continue;
|
|
|
|
if (!questSheet.HasRow(quest.QuestId + 65536u)) continue;
|
|
var questData = questSheet.GetRow(quest.QuestId + 65536u);
|
|
|
|
var todoParam = questData.TodoParams.FirstOrDefault(p => p.ToDoCompleteSeq == quest.Sequence);
|
|
var location = todoParam.ToDoLocation.FirstOrDefault(loc => loc is not { RowId: 0, ValueNullable: null });
|
|
if (location.ValueNullable is null || location.Value.Map.RowId != currentMapId) continue;
|
|
|
|
var name = questData.Name.ExtractText();
|
|
if (string.IsNullOrWhiteSpace(name)) name = "Quest objective";
|
|
|
|
var worldX = location.Value.X;
|
|
var worldZ = location.Value.Z;
|
|
var tx = 1024.0f + (worldX - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (worldZ - offsetY) * scaleFactor;
|
|
result.Add((tx, ty, name));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore missing sheet, invalid rows, etc.
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Draws direction arrows for nearby FATEs at the edge of the minimap (purple). Only shown when the FATE is off the minimap.</summary>
|
|
private static unsafe void DrawMinimapFateDirectionArrows(
|
|
Vector2 contentTopLeft,
|
|
Vector2 drawPosition,
|
|
float scale,
|
|
Vector2 size,
|
|
Vector2 centerOffset,
|
|
float offsetX,
|
|
float offsetY,
|
|
float scaleFactor)
|
|
{
|
|
if (Service.FateTable.Length is 0) return;
|
|
|
|
var radius = Math.Min(size.X, size.Y) * 0.5f;
|
|
var arrowDist = radius - 4f;
|
|
var centerScreen = contentTopLeft + centerOffset;
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var fateColor = new Vector4(0.55f, 0.28f, 0.62f, 0.95f);
|
|
var fateOutline = new Vector4(0.28f, 0.14f, 0.32f, 1f);
|
|
// Same UI-style arrowhead as quest: larger, fill + crisp outline
|
|
const float arrowSize = 20f;
|
|
const float baseHalf = 8f;
|
|
const float headDepth = 5f;
|
|
|
|
for (var i = 0; i < Service.FateTable.Length; i++)
|
|
{
|
|
var fateData = FateManager.Instance()->Fates[i];
|
|
var fate = fateData.Value;
|
|
if (fate->IconId is 0) continue;
|
|
|
|
var pos = new Vector2(fate->Location.X, fate->Location.Z);
|
|
var tx = 1024.0f + (pos.X - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (pos.Y - offsetY) * scaleFactor;
|
|
var targetInContent = drawPosition + new Vector2(tx, ty) * scale;
|
|
var toTarget = targetInContent - centerOffset;
|
|
var distToTarget = toTarget.Length();
|
|
if (distToTarget < 0.01f) continue;
|
|
if (distToTarget <= radius) continue; // FATE visible on minimap, no arrow
|
|
|
|
var direction = toTarget / distToTarget;
|
|
var arrowPos = centerScreen + direction * arrowDist;
|
|
var cos = MathF.Cos(MathF.Atan2(direction.Y, direction.X));
|
|
var sin = MathF.Sin(MathF.Atan2(direction.Y, direction.X));
|
|
var perpX = -sin;
|
|
var perpY = cos;
|
|
var tipScreen = arrowPos + new Vector2(cos * arrowSize, sin * arrowSize);
|
|
var baseBack = arrowPos - new Vector2(cos * headDepth, sin * headDepth);
|
|
var base1Screen = baseBack + new Vector2(perpX * baseHalf, perpY * baseHalf);
|
|
var base2Screen = baseBack - new Vector2(perpX * baseHalf, perpY * baseHalf);
|
|
|
|
var fateColorU32 = ImGui.GetColorU32(fateColor);
|
|
var fateOutlineU32 = ImGui.GetColorU32(fateOutline);
|
|
drawList.AddTriangleFilled(tipScreen, base1Screen, base2Screen, fateColorU32);
|
|
drawList.AddTriangle(tipScreen, base1Screen, base2Screen, fateOutlineU32, 2f);
|
|
|
|
var tooltip = GetFateTooltip(fateData);
|
|
var minX = Math.Min(tipScreen.X, Math.Min(base1Screen.X, base2Screen.X)) - 4f;
|
|
var minY = Math.Min(tipScreen.Y, Math.Min(base1Screen.Y, base2Screen.Y)) - 4f;
|
|
var maxX = Math.Max(tipScreen.X, Math.Max(base1Screen.X, base2Screen.X)) + 4f;
|
|
var maxY = Math.Max(tipScreen.Y, Math.Max(base1Screen.Y, base2Screen.Y)) + 4f;
|
|
if (ImGui.IsMouseHoveringRect(new Vector2(minX, minY), new Vector2(maxX, maxY)))
|
|
ImGui.SetTooltip(tooltip);
|
|
}
|
|
}
|
|
|
|
/// <summary>Draws a red direction arrow at the edge of the minimap pointing toward the player flag. Only shown when the flag is off the minimap.</summary>
|
|
private unsafe void DrawMinimapFlagDirectionArrow(
|
|
Vector2 contentTopLeft,
|
|
Vector2 drawPosition,
|
|
float scale,
|
|
Vector2 size,
|
|
Vector2 centerOffset,
|
|
float offsetX,
|
|
float offsetY,
|
|
float scaleFactor)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
if (agent->FlagMarkerCount is 0) return;
|
|
ref var flag = ref agent->FlagMapMarkers[0];
|
|
if (flag.TerritoryId != agent->CurrentTerritoryId || flag.MapId != agent->CurrentMapId) return;
|
|
|
|
var radius = Math.Min(size.X, size.Y) * 0.5f;
|
|
var arrowDist = radius - 4f;
|
|
var centerScreen = contentTopLeft + centerOffset;
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
var tx = 1024.0f + (flag.XFloat - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (flag.YFloat - offsetY) * scaleFactor;
|
|
var targetInContent = drawPosition + new Vector2(tx, ty) * scale;
|
|
var toTarget = targetInContent - centerOffset;
|
|
var distToTarget = toTarget.Length();
|
|
if (distToTarget < 0.01f) return;
|
|
if (distToTarget <= radius) return; // Flag visible on minimap, no arrow
|
|
|
|
const float arrowSize = 20f;
|
|
const float baseHalf = 8f;
|
|
const float headDepth = 5f;
|
|
var colorHead = ImGui.GetColorU32(new Vector4(0.92f, 0.2f, 0.2f, 0.95f));
|
|
var colorOutline = ImGui.GetColorU32(new Vector4(0.45f, 0.08f, 0.08f, 1f));
|
|
|
|
var direction = toTarget / distToTarget;
|
|
var arrowPos = centerScreen + direction * arrowDist;
|
|
var cos = MathF.Cos(MathF.Atan2(direction.Y, direction.X));
|
|
var sin = MathF.Sin(MathF.Atan2(direction.Y, direction.X));
|
|
var perpX = -sin;
|
|
var perpY = cos;
|
|
var tipScreen = arrowPos + new Vector2(cos * arrowSize, sin * arrowSize);
|
|
var baseBack = arrowPos - new Vector2(cos * headDepth, sin * headDepth);
|
|
var base1Screen = baseBack + new Vector2(perpX * baseHalf, perpY * baseHalf);
|
|
var base2Screen = baseBack - new Vector2(perpX * baseHalf, perpY * baseHalf);
|
|
|
|
drawList.AddTriangleFilled(tipScreen, base1Screen, base2Screen, colorHead);
|
|
drawList.AddTriangle(tipScreen, base1Screen, base2Screen, colorOutline, 2f);
|
|
|
|
var tooltip = System.TooltipCache.GetValue(flag.MapMarker.IconId);
|
|
if (string.IsNullOrEmpty(tooltip)) tooltip = "Flag";
|
|
var minX = Math.Min(tipScreen.X, Math.Min(base1Screen.X, base2Screen.X)) - 4f;
|
|
var minY = Math.Min(tipScreen.Y, Math.Min(base1Screen.Y, base2Screen.Y)) - 4f;
|
|
var maxX = Math.Max(tipScreen.X, Math.Max(base1Screen.X, base2Screen.X)) + 4f;
|
|
var maxY = Math.Max(tipScreen.Y, Math.Max(base1Screen.Y, base2Screen.Y)) + 4f;
|
|
if (ImGui.IsMouseHoveringRect(new Vector2(minX, minY), new Vector2(maxX, maxY)))
|
|
ImGui.SetTooltip(tooltip);
|
|
}
|
|
|
|
private static bool IsInMinimapBounds(Vector2 contentPos, Vector2 size, float margin)
|
|
{
|
|
// Use a large margin so we don't cull markers that are panned slightly off (zoomed in).
|
|
return contentPos.X >= -margin && contentPos.Y >= -margin &&
|
|
contentPos.X <= size.X + margin && contentPos.Y <= size.Y + margin;
|
|
}
|
|
|
|
/// <summary>If the mouse is over the quest radius circle, show the quest tooltip.</summary>
|
|
private static void ShowQuestRadiusTooltipIfHovered(Vector2 centerScreen, float markerRadius, float mapScale, float sizeFactor, string? tooltip)
|
|
{
|
|
if (markerRadius <= 1.0f || string.IsNullOrEmpty(tooltip)) return;
|
|
var radiusPixels = markerRadius * mapScale * sizeFactor;
|
|
if (radiusPixels < 0.5f) return;
|
|
var mouse = ImGui.GetMousePos();
|
|
if ((mouse - centerScreen).Length() <= radiusPixels)
|
|
ImGui.SetTooltip(tooltip);
|
|
}
|
|
|
|
/// <summary>Large margin so markers aren't culled when panned.</summary>
|
|
private const float MinimapBoundsMargin = 200f;
|
|
|
|
/// <summary>Cached static POI (aetheryte, repair, moogle, etc.) per map so we can draw them on the minimap when the Area Map is closed.</summary>
|
|
private static readonly Dictionary<uint, List<CachedStaticMarker>> StaticMarkerCache = new();
|
|
|
|
/// <summary>Cached quest/objective (temp) markers per map; populated during silent refresh on quest accept/turn-in/objective update.</summary>
|
|
private static readonly Dictionary<uint, List<CachedTempMarker>> TempMarkerCache = new();
|
|
|
|
/// <summary>Clear the temp marker cache for a map so stale markers (e.g. from a turned-in quest) are not drawn until we refresh.</summary>
|
|
public static void InvalidateTempMarkerCache(uint mapId) => TempMarkerCache.Remove(mapId);
|
|
|
|
/// <summary>Cached non-FATE event markers per map; populated during silent refresh.</summary>
|
|
private static readonly Dictionary<uint, List<CachedEventMarker>> EventMarkerCache = new();
|
|
|
|
private readonly struct CachedStaticMarker
|
|
{
|
|
public readonly uint IconId;
|
|
public readonly int X;
|
|
public readonly int Y;
|
|
public readonly string? Tooltip;
|
|
|
|
public CachedStaticMarker(uint iconId, int x, int y, string? tooltip)
|
|
{
|
|
IconId = iconId;
|
|
X = x;
|
|
Y = y;
|
|
Tooltip = tooltip;
|
|
}
|
|
}
|
|
|
|
private readonly struct CachedTempMarker
|
|
{
|
|
public readonly uint IconId;
|
|
public readonly int X;
|
|
public readonly int Y;
|
|
public readonly float Radius;
|
|
public readonly string? Tooltip;
|
|
|
|
public CachedTempMarker(uint iconId, int x, int y, float radius, string? tooltip)
|
|
{
|
|
IconId = iconId;
|
|
X = x;
|
|
Y = y;
|
|
Radius = radius;
|
|
Tooltip = tooltip;
|
|
}
|
|
}
|
|
|
|
private readonly struct CachedEventMarker
|
|
{
|
|
public readonly uint IconId;
|
|
public readonly float MapX;
|
|
public readonly float MapY;
|
|
public readonly float Radius;
|
|
public readonly string? Tooltip;
|
|
|
|
public CachedEventMarker(uint iconId, float mapX, float mapY, float radius, string? tooltip)
|
|
{
|
|
IconId = iconId;
|
|
MapX = mapX;
|
|
MapY = mapY;
|
|
Radius = radius;
|
|
Tooltip = tooltip;
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawMinimapStaticMarkers(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
var mapId = agent->CurrentMapId;
|
|
|
|
// When we have live data, update the cache for this map so we can draw static POI later when the map is closed.
|
|
if (agent->MapMarkerCount > 0) {
|
|
var list = new List<CachedStaticMarker>();
|
|
for (var i = 0; i < agent->MapMarkerCount; i++) {
|
|
ref var marker = ref agent->MapMarkers[i];
|
|
if (marker.MapMarker.IconId is 0) continue;
|
|
var tooltipText = marker.MapMarker.Subtext.AsDalamudSeString();
|
|
var tooltip = (string.IsNullOrEmpty(tooltipText.TextValue) && System.SystemConfig.ShowMiscTooltips)
|
|
? System.TooltipCache.GetValue(marker.MapMarker.IconId)
|
|
: tooltipText.ToString();
|
|
list.Add(new CachedStaticMarker(marker.MapMarker.IconId, marker.MapMarker.X, marker.MapMarker.Y, tooltip));
|
|
}
|
|
StaticMarkerCache[mapId] = list;
|
|
}
|
|
|
|
// Draw from live data if available, otherwise from cache for current map.
|
|
if (agent->MapMarkerCount > 0) {
|
|
for (var i = 0; i < agent->MapMarkerCount; i++) {
|
|
ref var marker = ref agent->MapMarkers[i];
|
|
if (marker.MapMarker.IconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(marker.MapMarker.IconId, out var setting) && setting.Hide) continue;
|
|
|
|
var tx = 1024.0f + marker.MapMarker.X / 16.0f;
|
|
var ty = 1024.0f + marker.MapMarker.Y / 16.0f;
|
|
var contentPos = texToContent(tx, ty);
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
|
|
var tooltipText = marker.MapMarker.Subtext.AsDalamudSeString();
|
|
var tooltip = (string.IsNullOrEmpty(tooltipText.TextValue) && System.SystemConfig.ShowMiscTooltips)
|
|
? System.TooltipCache.GetValue(marker.MapMarker.IconId)
|
|
: tooltipText.ToString();
|
|
DrawMinimapIcon(marker.MapMarker.IconId, contentPos + contentTopLeft, sizeScale: 1.5f, tooltip);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!StaticMarkerCache.TryGetValue(mapId, out var cached))
|
|
return;
|
|
|
|
foreach (var m in cached) {
|
|
if (m.IconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(m.IconId, out var setting) && setting.Hide) continue;
|
|
|
|
var tx = 1024.0f + m.X / 16.0f;
|
|
var ty = 1024.0f + m.Y / 16.0f;
|
|
var contentPos = texToContent(tx, ty);
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
|
|
DrawMinimapIcon(m.IconId, contentPos + contentTopLeft, sizeScale: 1.5f, m.Tooltip);
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawMinimapEventMarkers(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY, float scale)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
var mapId = agent->CurrentMapId;
|
|
var groups = agent->EventMarkers.GroupBy(m => (m.DataId, m.Position.X, m.Position.Z));
|
|
var showRadius = System.SystemConfig.MinimapShowQuestAreaRadius;
|
|
// Use minimap scale so the circle scales with minimap zoom (radiusPixels = markerRadius * scale * scaleFactor).
|
|
|
|
var hasNonFate = false;
|
|
var cacheList = new List<CachedEventMarker>();
|
|
|
|
foreach (var group in groups) {
|
|
var first = group.First();
|
|
if ((MarkerType)first.MarkerType is MarkerType.Fate) continue;
|
|
hasNonFate = true;
|
|
var iconId = group.FirstOrNull(m => m.IconId is not 60493)?.IconId ?? first.IconId;
|
|
if (iconId is 0) continue;
|
|
|
|
var markerRadius = group.Max(m => m.Radius);
|
|
if (HousingManager.Instance()->CurrentTerritory is not null) markerRadius = 0f;
|
|
|
|
var pos = first.Position.AsMapVector();
|
|
var tooltip = GetEventMarkerTooltip(first);
|
|
cacheList.Add(new CachedEventMarker(iconId, pos.X, pos.Y, markerRadius, tooltip));
|
|
|
|
var tx = 1024.0f + (pos.X - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (pos.Y - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var centerScreen = contentPos + contentTopLeft;
|
|
|
|
if (showRadius)
|
|
DrawHelpers.DrawRadiusCircle(centerScreen, markerRadius, scale, scaleFactor, MinimapQuestCircleFill, MinimapQuestCircleOutline);
|
|
ShowQuestRadiusTooltipIfHovered(centerScreen, markerRadius, scale, scaleFactor, tooltip);
|
|
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(iconId, out var setting) && setting.Hide) continue;
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
|
|
DrawMinimapIcon(iconId, centerScreen, sizeScale: 1.5f, tooltip);
|
|
}
|
|
|
|
if (hasNonFate && cacheList.Count > 0)
|
|
EventMarkerCache[mapId] = cacheList;
|
|
else if (EventMarkerCache.TryGetValue(mapId, out var cached))
|
|
{
|
|
foreach (var m in cached) {
|
|
if (m.IconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(m.IconId, out var setting) && setting.Hide) continue;
|
|
var tx = 1024.0f + (m.MapX - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (m.MapY - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var centerScreen = contentPos + contentTopLeft;
|
|
|
|
if (showRadius)
|
|
DrawHelpers.DrawRadiusCircle(centerScreen, m.Radius, scale, scaleFactor, MinimapQuestCircleFill, MinimapQuestCircleOutline);
|
|
ShowQuestRadiusTooltipIfHovered(centerScreen, m.Radius, scale, scaleFactor, m.Tooltip);
|
|
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
DrawMinimapIcon(m.IconId, centerScreen, sizeScale: 1.5f, m.Tooltip);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Draw FATE markers from FateManager so they update without opening the Area Map.</summary>
|
|
private unsafe void DrawMinimapFatesFromFateManager(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY)
|
|
{
|
|
if (Service.FateTable.Length is 0) return;
|
|
for (var i = 0; i < Service.FateTable.Length; i++) {
|
|
var fateData = FateManager.Instance()->Fates[i];
|
|
var fate = fateData.Value;
|
|
if (fate->IconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(fate->IconId, out var setting) && setting.Hide) continue;
|
|
|
|
var pos = new Vector2(fate->Location.X, fate->Location.Z);
|
|
var tx = 1024.0f + (pos.X - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (pos.Y - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
|
|
var tooltip = GetFateTooltip(fateData);
|
|
DrawMinimapIcon(fate->IconId, contentPos + contentTopLeft, sizeScale: 1.5f, tooltip);
|
|
}
|
|
}
|
|
|
|
private static unsafe string GetFateTooltip(Pointer<FateContext> fateData)
|
|
{
|
|
try {
|
|
var fate = fateData.Value;
|
|
var name = fate->Name.ToString();
|
|
var title = !string.IsNullOrWhiteSpace(name) ? $"{name}\nLv. {fate->Level} FATE" : $"Lv. {fate->Level} FATE";
|
|
var timeRemaining = fateData.GetTimeRemaining();
|
|
if (fate->State is FateState.Running) {
|
|
var progressLine = $"Progress: {fate->Progress}%";
|
|
if (timeRemaining > TimeSpan.Zero)
|
|
title += $"\n{(fate->IsBonus ? "Exp Bonus! " : "")}{SeIconChar.Clock.ToIconString()} {timeRemaining:mm\\:ss} {progressLine}";
|
|
else
|
|
title += $"\n{progressLine}";
|
|
}
|
|
else if (fate->State is not FateState.Preparing) {
|
|
title += $"\n{fate->State}";
|
|
}
|
|
return title;
|
|
}
|
|
catch {
|
|
return "FATE";
|
|
}
|
|
}
|
|
|
|
private static unsafe string GetEventMarkerTooltip(MapMarkerData marker)
|
|
{
|
|
try {
|
|
// FATE path never touches marker.TooltipString (avoids AccessViolationException from stale pointers).
|
|
if ((MarkerType)marker.MarkerType is MarkerType.Fate) {
|
|
var fateData = FateManager.Instance()->Fates.FirstOrNull(fate => fate.Value->FateId == marker.DataId);
|
|
if (fateData is not null) {
|
|
var fatePtr = fateData.Value.Value;
|
|
var name = fatePtr->Name.ToString();
|
|
var title = !string.IsNullOrWhiteSpace(name) ? $"{name}\nLv. {fatePtr->Level} FATE" : $"Lv. {fatePtr->Level} FATE";
|
|
var timeRemaining = fateData.Value.GetTimeRemaining();
|
|
if (fatePtr->State is FateState.Running) {
|
|
var progressLine = $"Progress: {fatePtr->Progress}%";
|
|
if (timeRemaining > TimeSpan.Zero)
|
|
title += $"\n{(fatePtr->IsBonus ? "Exp Bonus! " : "")}{SeIconChar.Clock.ToIconString()} {timeRemaining:mm\\:ss} {progressLine}";
|
|
else
|
|
title += $"\n{progressLine}";
|
|
}
|
|
else if (fatePtr->State is not FateState.Preparing) {
|
|
title += $"\n{fatePtr->State}";
|
|
}
|
|
return title;
|
|
}
|
|
return "FATE";
|
|
}
|
|
|
|
// Other event markers: try to get name from Lumina (e.g. Quest by DataId) instead of "Lv. X Event".
|
|
if (marker.DataId != 0) {
|
|
try {
|
|
var questRow = Service.DataManager.GetExcelSheet<Quest>()?.GetRow(marker.DataId + 65536u);
|
|
var name = questRow?.Name.ExtractText();
|
|
if (!string.IsNullOrWhiteSpace(name))
|
|
return name;
|
|
} catch { }
|
|
}
|
|
return marker.RecommendedLevel is 0 ? "Event" : $"Lv. {marker.RecommendedLevel} Event";
|
|
}
|
|
catch (AccessViolationException) {
|
|
return string.Empty;
|
|
}
|
|
catch (NullReferenceException) {
|
|
return string.Empty;
|
|
}
|
|
catch (Exception) {
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawMinimapGatheringMarkers(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
foreach (var marker in agent->MiniMapGatheringMarkers) {
|
|
if (marker.ShouldRender is 0) continue;
|
|
var iconId = marker.MapMarker.IconId;
|
|
if (iconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(iconId, out var setting) && setting.Hide) continue;
|
|
|
|
var tx = 1024.0f + marker.MapMarker.X / 16.0f;
|
|
var ty = 1024.0f + marker.MapMarker.Y / 16.0f;
|
|
var contentPos = texToContent(tx, ty);
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
|
|
var tooltipText = marker.MapMarker.Subtext.AsDalamudSeString();
|
|
var tooltip = (string.IsNullOrEmpty(tooltipText.TextValue) && System.SystemConfig.ShowMiscTooltips)
|
|
? System.TooltipCache.GetValue(marker.MapMarker.IconId)
|
|
: tooltipText.ToString();
|
|
DrawMinimapIcon(iconId, contentPos + contentTopLeft, sizeScale: 1.5f, tooltip);
|
|
}
|
|
}
|
|
|
|
/// <summary>Draw party and alliance members on the minimap. When a member is off the minimap circle, draw a faded marker at the rim.</summary>
|
|
private unsafe void DrawMinimapGroupMembers(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
var currentTerritoryId = agent->CurrentTerritoryId;
|
|
var centerOffset = size * 0.5f;
|
|
var radius = Math.Min(size.X, size.Y) * 0.5f;
|
|
const float rimMargin = 2f; // place icon at the rim (minimal inset)
|
|
const float offMapFadeAlpha = 0.5f;
|
|
|
|
foreach (var partyMember in GroupManager.Instance()->MainGroup.PartyMembers[..(int)GroupManager.Instance()->MainGroup.MemberCount])
|
|
{
|
|
if (partyMember.EntityId is 0xE0000000) continue;
|
|
if (partyMember.TerritoryType != currentTerritoryId) continue;
|
|
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(60421, out var setting) && setting.Hide) continue;
|
|
|
|
var pos = new Vector2(partyMember.Position.X, partyMember.Position.Z);
|
|
var tx = 1024.0f + (pos.X - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (pos.Y - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var distFromCenter = (contentPos - centerOffset).Length();
|
|
var tooltip = $"Lv. {partyMember.Level} {partyMember.NameString}";
|
|
|
|
if (distFromCenter <= radius && IsInMinimapBounds(contentPos, size, MinimapBoundsMargin))
|
|
{
|
|
DrawMinimapIcon(60421, contentPos + contentTopLeft, sizeScale: 1.5f, tooltip);
|
|
}
|
|
else if (distFromCenter > radius)
|
|
{
|
|
var direction = (contentPos - centerOffset) / distFromCenter;
|
|
var rimPos = centerOffset + direction * (radius - rimMargin);
|
|
DrawMinimapIcon(60421, rimPos + contentTopLeft, sizeScale: 1.5f, tooltip, fadeAlpha: offMapFadeAlpha);
|
|
}
|
|
}
|
|
|
|
foreach (var allianceMember in GroupManager.Instance()->MainGroup.AllianceMembers)
|
|
{
|
|
if (allianceMember.EntityId is 0xE0000000) continue;
|
|
if (allianceMember.TerritoryType != currentTerritoryId) continue;
|
|
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(60403, out var allianceSetting) && allianceSetting.Hide) continue;
|
|
|
|
var pos = new Vector2(allianceMember.Position.X, allianceMember.Position.Z);
|
|
var tx = 1024.0f + (pos.X - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (pos.Y - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var distFromCenter = (contentPos - centerOffset).Length();
|
|
var allianceTooltip = $"Lv. {allianceMember.Level} {allianceMember.NameString}";
|
|
|
|
if (distFromCenter <= radius && IsInMinimapBounds(contentPos, size, MinimapBoundsMargin))
|
|
{
|
|
DrawMinimapIcon(60403, contentPos + contentTopLeft, sizeScale: 1.5f, allianceTooltip);
|
|
}
|
|
else if (distFromCenter > radius)
|
|
{
|
|
var direction = (contentPos - centerOffset) / distFromCenter;
|
|
var rimPos = centerOffset + direction * (radius - rimMargin);
|
|
DrawMinimapIcon(60403, rimPos + contentTopLeft, sizeScale: 1.5f, allianceTooltip, fadeAlpha: offMapFadeAlpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawMinimapFlag(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, float scaleFactor, float offsetX, float offsetY)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
if (agent->FlagMarkerCount is 0) return;
|
|
ref var flag = ref agent->FlagMapMarkers[0];
|
|
// Match territory (zone) and map (sub-area); flag stores TerritoryId and MapId from SetFlagMapMarker
|
|
if (flag.TerritoryId != agent->CurrentTerritoryId || flag.MapId != agent->CurrentMapId) return;
|
|
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(flag.MapMarker.IconId, out var setting) && setting.Hide) return;
|
|
|
|
var tx = 1024.0f + (flag.XFloat - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (flag.YFloat - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var flagTooltip = System.TooltipCache.GetValue(flag.MapMarker.IconId);
|
|
if (string.IsNullOrEmpty(flagTooltip)) flagTooltip = "Flag";
|
|
DrawMinimapIcon(flag.MapMarker.IconId, contentPos + contentTopLeft, sizeScale: 1.5f, flagTooltip);
|
|
}
|
|
|
|
private unsafe void DrawMinimapTempMarkers(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY, float scale)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
var mapId = agent->CurrentMapId;
|
|
var showRadius = System.SystemConfig.MinimapShowQuestAreaRadius;
|
|
// Use minimap scale so the circle scales with minimap zoom and is not tied to area map zoom.
|
|
// radiusPixels = markerRadius * scale * scaleFactor (same as position transform on minimap).
|
|
|
|
if (agent->TempMapMarkerCount > 0) {
|
|
var span = new Span<TempMapMarker>(Unsafe.AsPointer(ref agent->TempMapMarkers[0]), agent->TempMapMarkerCount);
|
|
var groups = span.ToArray().GroupBy(m => new Vector2(m.MapMarker.X, m.MapMarker.Y));
|
|
var cacheList = new List<CachedTempMarker>();
|
|
|
|
foreach (var group in groups) {
|
|
var first = group.First();
|
|
var markerRadius = group.Max(m => m.MapMarker.Scale);
|
|
var iconId = group.FirstOrNull(m => m.MapMarker.IconId is not (60493 or 0))?.MapMarker.IconId ?? first.MapMarker.IconId;
|
|
if (iconId is 0 && group.Count() == 2 && first.Type == 4 && group.Last() is { Type: 6, MapMarker.IconId: 0 })
|
|
iconId = DrawHelpers.QuestionMarkIcon;
|
|
if (iconId is 0) continue;
|
|
|
|
var tooltip = first.TooltipText.ToString();
|
|
cacheList.Add(new CachedTempMarker(iconId, first.MapMarker.X, first.MapMarker.Y, markerRadius, tooltip));
|
|
|
|
var tx = 1024.0f + (first.MapMarker.X / 16.0f - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (first.MapMarker.Y / 16.0f - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var centerScreen = contentPos + contentTopLeft;
|
|
|
|
if (showRadius)
|
|
DrawHelpers.DrawRadiusCircle(centerScreen, markerRadius, scale, scaleFactor, MinimapQuestCircleFill, MinimapQuestCircleOutline);
|
|
ShowQuestRadiusTooltipIfHovered(centerScreen, markerRadius, scale, scaleFactor, tooltip);
|
|
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(iconId, out var setting) && setting.Hide) continue;
|
|
|
|
DrawMinimapIcon(iconId, centerScreen, sizeScale: 1.5f, tooltip);
|
|
}
|
|
if (cacheList.Count > 0)
|
|
TempMarkerCache[mapId] = cacheList;
|
|
return;
|
|
}
|
|
|
|
if (!TempMarkerCache.TryGetValue(mapId, out var cached))
|
|
return;
|
|
foreach (var m in cached) {
|
|
if (m.IconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(m.IconId, out var setting) && setting.Hide) continue;
|
|
var tx = 1024.0f + (m.X / 16.0f - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (m.Y / 16.0f - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
var centerScreen = contentPos + contentTopLeft;
|
|
|
|
if (showRadius)
|
|
DrawHelpers.DrawRadiusCircle(centerScreen, m.Radius, scale, scaleFactor, MinimapQuestCircleFill, MinimapQuestCircleOutline);
|
|
ShowQuestRadiusTooltipIfHovered(centerScreen, m.Radius, scale, scaleFactor, m.Tooltip);
|
|
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
DrawMinimapIcon(m.IconId, centerScreen, sizeScale: 1.5f, m.Tooltip);
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawMinimapFieldMarkers(Vector2 contentTopLeft, Func<float, float, Vector2> texToContent, Vector2 size, float scaleFactor, float offsetX, float offsetY)
|
|
{
|
|
var agent = AgentMap.Instance();
|
|
if (agent->CurrentMapId != agent->SelectedMapId) return;
|
|
|
|
var fieldMarkersSheet = Service.DataManager.GetExcelSheet<FieldMarkerSheet>().Where(m => m.MapIcon is not 0).ToList();
|
|
var markerSpan = MarkingController.Instance()->FieldMarkers;
|
|
|
|
for (var i = 0; i < 8; i++) {
|
|
if (markerSpan[i] is not { Active: true } marker) continue;
|
|
if (i >= fieldMarkersSheet.Count) continue;
|
|
var iconId = fieldMarkersSheet[i].MapIcon;
|
|
if (iconId is 0) continue;
|
|
if (System.IconConfig.IconSettingMap.TryGetValue(iconId, out var setting) && setting.Hide) continue;
|
|
|
|
// Field marker position: world coords / 1000 * scaleFactor, then texture = 1024 + (world - offset) * scaleFactor
|
|
var wx = marker.X / 1000.0f;
|
|
var wz = marker.Z / 1000.0f;
|
|
var tx = 1024.0f + (wx - offsetX) * scaleFactor;
|
|
var ty = 1024.0f + (wz - offsetY) * scaleFactor;
|
|
var contentPos = texToContent(tx, ty);
|
|
if (!IsInMinimapBounds(contentPos, size, MinimapBoundsMargin)) continue;
|
|
|
|
DrawMinimapIcon(iconId, contentPos + contentTopLeft, sizeScale: 1.5f, $"Waymark {i + 1}");
|
|
}
|
|
}
|
|
|
|
private void DrawMinimapIcon(uint iconId, Vector2 screenPos, float sizeScale = 1f, string? tooltip = null, float? fadeAlpha = null)
|
|
{
|
|
try
|
|
{
|
|
var texture = Service.TextureProvider.GetFromGameIcon(iconId).GetWrapOrEmpty();
|
|
var texSize = texture.Size;
|
|
if (texSize.X <= 0 || texSize.Y <= 0) return;
|
|
var iconScale = MinimapIconSize / Math.Max(texSize.X, texSize.Y) * MinimapIconScaleFromConfig * sizeScale;
|
|
System.IconConfig.IconSettingMap.TryGetValue(iconId, out var setting);
|
|
if (setting != null)
|
|
iconScale *= setting.Scale;
|
|
var size = texSize * iconScale;
|
|
var half = size / 2f;
|
|
var col = setting?.Color ?? Vector4.One;
|
|
if (fadeAlpha is { } alpha)
|
|
col.W *= alpha;
|
|
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
drawList.AddImage(texture.Handle, screenPos - half, screenPos + half, Vector2.Zero, Vector2.One, ImGui.GetColorU32(col));
|
|
|
|
if (!string.IsNullOrEmpty(tooltip) && ImGui.IsMouseHoveringRect(screenPos - half, screenPos + half))
|
|
ImGui.SetTooltip(tooltip);
|
|
}
|
|
catch (Dalamud.Interface.Textures.Internal.IconNotFoundException)
|
|
{
|
|
// Icon not in game data (e.g. 60494 HiRes), skip drawing
|
|
}
|
|
}
|
|
}
|