542da3a71b
Made-with: Cursor
327 lines
13 KiB
C#
327 lines
13 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using KamiLib.Classes;
|
|
using Mappy.Data;
|
|
using SeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
|
|
|
namespace Mappy.Classes;
|
|
|
|
public class MarkerInfo
|
|
{
|
|
public required Vector2 Position { get; set; }
|
|
public required Vector2 Offset { get; set; }
|
|
public required float Scale { get; set; }
|
|
public uint? ObjectiveId { get; init; }
|
|
public uint? DataId { get; set; }
|
|
public MarkerType MarkerType { get; set; }
|
|
public uint IconId { get; set; }
|
|
public Func<string?>? PrimaryText { get; set; }
|
|
public Func<string?>? SecondaryText { get; set; }
|
|
public float? Radius { get; set; }
|
|
public Vector4 RadiusColor { get; set; } = KnownColor.CornflowerBlue.Vector();
|
|
public Vector4 RadiusOutlineColor { get; set; } = KnownColor.CornflowerBlue.Vector();
|
|
public Action? OnRightClicked { get; set; }
|
|
public Action? OnLeftClicked { get; set; }
|
|
public bool IsDynamicMarker { get; init; }
|
|
}
|
|
|
|
public static class DrawHelpers
|
|
{
|
|
private static bool DebugMode => System.SystemConfig.DebugMode;
|
|
|
|
public const uint QuestionMarkIcon = 60071;
|
|
|
|
/// <summary>Sentinel IconId for user-placed map notes (custom-drawn white page with writing).</summary>
|
|
public const uint MapNoteIconId = 65000;
|
|
|
|
/// <summary>
|
|
/// Offset Vector of SelectedX, SelectedY, scaled with SelectedSizeFactor
|
|
/// </summary>
|
|
public static Vector2 GetMapOffsetVector() => GetRawMapOffsetVector() * GetMapScaleFactor();
|
|
|
|
/// <summary>
|
|
/// Unscaled Vector of SelectedX, SelectedY
|
|
/// </summary>
|
|
public static unsafe Vector2 GetRawMapOffsetVector() => new(AgentMap.Instance()->SelectedOffsetX, AgentMap.Instance()->SelectedOffsetY);
|
|
|
|
/// <summary>
|
|
/// Selected Scale Factor
|
|
/// </summary>
|
|
public static unsafe float GetMapScaleFactor() => AgentMap.Instance()->SelectedMapSizeFactorFloat;
|
|
|
|
/// <summary>
|
|
/// 1024 vector, center offset vector
|
|
/// </summary>
|
|
public static Vector2 GetMapCenterOffsetVector() => new(1024.0f, 1024.0f);
|
|
|
|
/// <summary>
|
|
/// Offset for the top left corner of the drawn map
|
|
/// </summary>
|
|
public static Vector2 GetCombinedOffsetVector() => -GetMapOffsetVector() + GetMapCenterOffsetVector();
|
|
|
|
public static void DrawMapMarker(MarkerInfo markerInfo)
|
|
{
|
|
if (markerInfo.IconId is 0) return;
|
|
|
|
// Don't draw markers that are positioned off the map texture
|
|
if (markerInfo.Position.X < 0.0f || markerInfo.Position.X > 2048.0f * markerInfo.Scale || markerInfo.Position.Y < 0.0f ||
|
|
markerInfo.Position.Y > 2048.0f * markerInfo.Scale)
|
|
return;
|
|
|
|
markerInfo.IconId = markerInfo.IconId switch
|
|
{
|
|
// Translate circle markers that don't have icons, into [?] icon
|
|
>= 60483 and <= 60494 => QuestionMarkIcon,
|
|
|
|
// Translate Gemstone Trader Icon into smaller version... why square, why.
|
|
60091 => 61731,
|
|
|
|
// Leave all other icons as they were
|
|
_ => markerInfo.IconId,
|
|
};
|
|
|
|
if (DebugMode) {
|
|
markerInfo.SecondaryText = markerInfo.PrimaryText;
|
|
markerInfo.PrimaryText = () => $"[Debug] IconId: {markerInfo.IconId}";
|
|
}
|
|
|
|
// If this is the first time we have seen this iconId, save it
|
|
if (System.IconConfig.IconSettingMap.TryAdd(markerInfo.IconId, new IconSetting { IconId = markerInfo.IconId, })) {
|
|
System.IconConfig.Save();
|
|
}
|
|
|
|
// If this icon is disabled, don't even process it
|
|
if (System.IconConfig.IconSettingMap[markerInfo.IconId] is { Hide: true }) {
|
|
return;
|
|
}
|
|
|
|
// Only process modules for Dynamic Markers
|
|
if (markerInfo.IsDynamicMarker) {
|
|
foreach (var module in System.Modules) {
|
|
if (module.ProcessMarker(markerInfo)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawRadiusUnderlay(markerInfo);
|
|
DrawIcon(markerInfo);
|
|
ProcessInteractions(markerInfo);
|
|
DrawTooltip(markerInfo);
|
|
}
|
|
|
|
private static unsafe void DrawRadiusUnderlay(MarkerInfo markerInfo)
|
|
{
|
|
if (markerInfo is not { Radius: { } markerRadius and > 1.0f }) return;
|
|
|
|
var center = markerInfo.Position + markerInfo.Offset + ImGui.GetWindowPos();
|
|
DrawRadiusCircle(center, markerRadius, markerInfo.Scale, AgentMap.Instance()->SelectedMapSizeFactorFloat,
|
|
markerInfo.RadiusColor with { W = System.SystemConfig.AreaColor.W },
|
|
markerInfo.RadiusOutlineColor with { W = System.SystemConfig.AreaOutlineColor.W });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the quest/area radius circle using the same formula as the area map.
|
|
/// Used by both the area map (DrawRadiusUnderlay) and the minimap so behavior is identical.
|
|
/// </summary>
|
|
public static unsafe void DrawRadiusCircle(Vector2 centerScreen, float markerRadius, float mapScale, float sizeFactor, Vector4? fillColor = null, Vector4? outlineColor = null)
|
|
{
|
|
if (markerRadius <= 1.0f) return;
|
|
var radiusPixels = markerRadius * mapScale * sizeFactor;
|
|
if (radiusPixels < 0.5f) return;
|
|
|
|
var fill = ImGui.GetColorU32(fillColor ?? System.SystemConfig.AreaColor);
|
|
var outline = ImGui.GetColorU32(outlineColor ?? System.SystemConfig.AreaOutlineColor);
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
drawList.AddCircleFilled(centerScreen, radiusPixels, fill);
|
|
drawList.AddCircle(centerScreen, radiusPixels, outline, 0, 3.0f);
|
|
}
|
|
|
|
private static void DrawIcon(MarkerInfo markerInfo)
|
|
{
|
|
var scale = System.SystemConfig.ScaleWithZoom ? markerInfo.Scale : 1.0f;
|
|
var iconScale = System.SystemConfig.IconScale;
|
|
|
|
if (markerInfo.IconId is 60401 or 60402) {
|
|
scale *= 2.0f;
|
|
}
|
|
|
|
// Fixed scale not supported for map region markers
|
|
if (IsRegionIcon(markerInfo.IconId)) {
|
|
scale = markerInfo.Scale;
|
|
iconScale = 0.42f;
|
|
}
|
|
|
|
var setting = System.IconConfig.IconSettingMap[markerInfo.IconId];
|
|
var sizeMultiplier = scale * iconScale * setting.Scale;
|
|
|
|
Vector2 iconSize;
|
|
Vector2 cursorScreenPos;
|
|
|
|
if (markerInfo.IconId == MapNoteIconId) {
|
|
// Custom-drawn white page with writing icon (roughly 24x30 aspect)
|
|
iconSize = new Vector2(20f, 26f) * sizeMultiplier;
|
|
ImGui.SetCursorPos(markerInfo.Position + markerInfo.Offset - iconSize / 2f);
|
|
cursorScreenPos = ImGui.GetCursorScreenPos();
|
|
ImGui.InvisibleButton($"mapnote_{markerInfo.Position.X}_{markerInfo.Position.Y}", iconSize);
|
|
DrawMapNoteIcon(cursorScreenPos, iconSize, setting.Color);
|
|
}
|
|
else {
|
|
var texture = Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty();
|
|
iconSize = texture.Size * sizeMultiplier;
|
|
ImGui.SetCursorPos(markerInfo.Position + markerInfo.Offset - iconSize / 2f);
|
|
cursorScreenPos = ImGui.GetCursorScreenPos();
|
|
ImGui.Image(texture.Handle, iconSize, Vector2.Zero, Vector2.One, setting.Color);
|
|
}
|
|
|
|
if (DebugMode) {
|
|
foreach (var x in Enumerable.Range(-1, 3)) {
|
|
foreach (var y in Enumerable.Range(-1, 3)) {
|
|
ImGui.GetWindowDrawList().AddRect(cursorScreenPos + new Vector2(x, y), cursorScreenPos + iconSize, ImGui.GetColorU32(KnownColor.White.Vector()), 3.0f);
|
|
}
|
|
}
|
|
|
|
ImGui.GetWindowDrawList().AddRect(cursorScreenPos, cursorScreenPos + iconSize, ImGui.GetColorU32(KnownColor.Red.Vector()), 3.0f);
|
|
}
|
|
}
|
|
|
|
public static void DrawText(MarkerInfo markerInfo, SeString text) => DrawText(markerInfo, text.ToString());
|
|
|
|
public static void DrawText(MarkerInfo markerInfo, string text)
|
|
{
|
|
using var largeFont = System.LargeAxisFontHandle.Push();
|
|
ImGui.SetWindowFontScale(markerInfo.Scale);
|
|
|
|
var textSize = ImGui.CalcTextSize(text);
|
|
var drawPosition = markerInfo.Position + markerInfo.Offset + ImGui.GetWindowPos() - textSize / 2.0f;
|
|
|
|
drawPosition = new Vector2(MathF.Round(drawPosition.X), MathF.Round(drawPosition.Y));
|
|
|
|
if (System.SystemConfig.DebugMode) {
|
|
ImGui.GetWindowDrawList().AddCircleFilled(markerInfo.Position + markerInfo.Offset + ImGui.GetWindowPos(), 5.0f, ImGui.GetColorU32(KnownColor.Red.Vector()));
|
|
ImGui.GetWindowDrawList().AddRect(drawPosition, drawPosition + textSize, ImGui.GetColorU32(KnownColor.Green.Vector()), 3.0f);
|
|
}
|
|
|
|
foreach (var x in Enumerable.Range(-1, 3)) {
|
|
foreach (var y in Enumerable.Range(-1, 3)) {
|
|
if (x is 0 && y is 0) continue;
|
|
|
|
ImGui.SetCursorScreenPos(drawPosition + new Vector2(x, y));
|
|
ImGui.TextColored(KnownColor.Black.Vector(), text);
|
|
}
|
|
}
|
|
|
|
ImGui.SetCursorScreenPos(drawPosition);
|
|
ImGui.TextColored(KnownColor.White.Vector(), text);
|
|
|
|
ImGui.SetWindowFontScale(1.0f);
|
|
}
|
|
|
|
private static void ProcessInteractions(MarkerInfo markerInfo)
|
|
{
|
|
if (System.IconConfig.IconSettingMap[markerInfo.IconId] is not { AllowClick: true }) return;
|
|
|
|
if (markerInfo is { OnRightClicked: { } rightClickAction } && ImGui.IsItemClicked(ImGuiMouseButton.Right)) {
|
|
rightClickAction.Invoke();
|
|
}
|
|
|
|
if (markerInfo is { OnLeftClicked: { } leftClickAction } && ImGui.IsItemClicked(ImGuiMouseButton.Left)) {
|
|
leftClickAction.Invoke();
|
|
}
|
|
}
|
|
|
|
private static unsafe void DrawTooltip(MarkerInfo markerInfo)
|
|
{
|
|
if (System.IconConfig.IconSettingMap[markerInfo.IconId] is { AllowTooltip: false } && !DebugMode) {
|
|
return;
|
|
}
|
|
|
|
var isActivatedViaRadius = false;
|
|
|
|
if (markerInfo is { Radius: { } sameRadius and > 1.0f }) {
|
|
var center = markerInfo.Position + markerInfo.Offset + ImGui.GetWindowPos();
|
|
var radius = sameRadius * markerInfo.Scale * AgentMap.Instance()->SelectedMapSizeFactorFloat;
|
|
|
|
if (Vector2.Distance(ImGui.GetMousePos() - System.MapWindow.MapDrawOffset + ImGui.GetWindowPos(), center) <= radius && System.MapWindow.HoveredFlags.Any()) {
|
|
isActivatedViaRadius = true;
|
|
}
|
|
}
|
|
|
|
if (isActivatedViaRadius || ImGui.IsItemHovered()) {
|
|
if (markerInfo.PrimaryText?.Invoke() is { Length: > 0 } primaryText) {
|
|
using var tooltip = ImRaii.Tooltip();
|
|
|
|
if (markerInfo.IconId == MapNoteIconId) {
|
|
var iconSize = ImGuiHelpers.ScaledVector2(24f, 31f);
|
|
DrawMapNoteIcon(ImGui.GetCursorScreenPos(), iconSize, Vector4.One);
|
|
ImGui.Dummy(iconSize);
|
|
}
|
|
else {
|
|
ImGui.Image(Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty().Handle, ImGuiHelpers.ScaledVector2(32.0f, 32.0f));
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 7.5f * ImGuiHelpers.GlobalScale);
|
|
var cursorPosition = ImGui.GetCursorPos();
|
|
ImGui.Text(primaryText);
|
|
|
|
if (markerInfo.SecondaryText?.Invoke() is { Length: > 0 } secondaryText) {
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPos(cursorPosition);
|
|
ImGuiTweaks.TextColoredUnformatted(KnownColor.Gray.Vector(), $"\n{secondaryText}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Draw a custom "white page with writing" icon for map notes, centered at the given position.</summary>
|
|
public static void DrawMapNoteIconCentered(Vector2 center, Vector2 size, Vector4 tint) =>
|
|
DrawMapNoteIcon(center - size * 0.5f, size, tint);
|
|
|
|
/// <summary>Draw a custom "white page with writing" icon for map notes.</summary>
|
|
private static void DrawMapNoteIcon(Vector2 topLeft, Vector2 size, Vector4 tint)
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var p1 = topLeft;
|
|
var p2 = topLeft + size;
|
|
var rounding = MathF.Min(size.X * 0.15f, size.Y * 0.12f);
|
|
var white = ImGui.GetColorU32(new Vector4(0.98f, 0.98f, 0.96f, tint.W));
|
|
var border = ImGui.GetColorU32(new Vector4(0.7f, 0.7f, 0.65f, tint.W));
|
|
var writing = ImGui.GetColorU32(new Vector4(0.25f, 0.22f, 0.2f, tint.W));
|
|
var padding = size * 0.12f;
|
|
var lineHeight = (size.Y - padding.Y * 2f) / 4f;
|
|
var lineLeft = p1.X + padding.X;
|
|
var lineRight = p2.X - padding.X;
|
|
|
|
drawList.AddRectFilled(p1, p2, white, rounding);
|
|
drawList.AddRect(p1, p2, border, rounding, 0, 1.2f);
|
|
for (var i = 0; i < 3; i++) {
|
|
var y = p1.Y + padding.Y + lineHeight * (i + 0.5f);
|
|
var w = (i == 1 ? 0.9f : 0.7f) * (lineRight - lineLeft);
|
|
drawList.AddLine(new Vector2(lineLeft, y), new Vector2(lineLeft + w, y), writing, 1.5f);
|
|
}
|
|
}
|
|
|
|
public static bool IsDisallowedIcon(uint iconId) =>
|
|
iconId switch
|
|
{
|
|
60091 => true,
|
|
_ when IsRegionIcon(iconId) => true,
|
|
_ => false,
|
|
};
|
|
|
|
public static bool IsRegionIcon(uint iconId) =>
|
|
iconId switch
|
|
{
|
|
>= 63200 and < 63900 => true,
|
|
>= 62620 and < 62800 => true,
|
|
_ => false,
|
|
};
|
|
} |