Initial HSMappy release (fork of Mappy)
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using KamiLib.Classes;
|
||||
using KamiLib.Extensions;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.Caches;
|
||||
|
||||
public class AetheryteAethernetCache : Cache<uint, Aetheryte?>
|
||||
{
|
||||
protected override Aetheryte? LoadValue(uint key)
|
||||
{
|
||||
if (Service.DataManager.GetExcelSheet<Aetheryte>().FirstOrNull(aetheryte => aetheryte.AethernetName.RowId == key) is not { AethernetGroup: var aethernetGroup })
|
||||
return null;
|
||||
if (Service.DataManager.GetExcelSheet<Aetheryte>().FirstOrNull(aetheryte => aetheryte.IsAetheryte && aetheryte.AethernetGroup == aethernetGroup) is not { } targetAetheryte)
|
||||
return null;
|
||||
|
||||
return targetAetheryte;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Linq;
|
||||
using KamiLib.Classes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.Caches;
|
||||
|
||||
public class CardRewardCache : Cache<uint, string>
|
||||
{
|
||||
protected override string LoadValue(uint key)
|
||||
{
|
||||
if (Service.DataManager.GetExcelSheet<TripleTriad>().GetRow(key) is { RowId: not 0 } triadInfo) {
|
||||
var cardRewards = triadInfo.ItemPossibleReward
|
||||
.Where(reward => reward.RowId is not 0)
|
||||
.Select(reward => reward.Value)
|
||||
.Where(item => item.RowId is not 0)
|
||||
.Select(item => item.Name.ExtractText());
|
||||
|
||||
return string.Join("\n", cardRewards);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using KamiLib.Classes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.Caches;
|
||||
|
||||
public class GatheringPointIconCache : Cache<uint, uint>
|
||||
{
|
||||
protected override uint LoadValue(uint key)
|
||||
{
|
||||
var gatheringPoint = Service.DataManager.GetExcelSheet<GatheringPoint>().GetRow(key);
|
||||
var gatheringPointBase = Service.DataManager.GetExcelSheet<GatheringPointBase>().GetRow(gatheringPoint.GatheringPointBase.RowId);
|
||||
|
||||
return gatheringPointBase.GatheringType.RowId switch
|
||||
{
|
||||
0 => 60438,
|
||||
1 => 60437,
|
||||
2 => 60433,
|
||||
3 => 60432,
|
||||
5 => 60445,
|
||||
_ => throw new Exception($"Unknown Gathering Type: {gatheringPointBase.GatheringType.RowId}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using KamiLib.Classes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.Caches;
|
||||
|
||||
public class GatheringPointNameCache : Cache<(uint dataId, string name), string>
|
||||
{
|
||||
protected override string LoadValue((uint dataId, string name) key)
|
||||
{
|
||||
var gatheringPoint = Service.DataManager.GetExcelSheet<GatheringPoint>().GetRow(key.dataId);
|
||||
var gatheringPointBase = Service.DataManager.GetExcelSheet<GatheringPointBase>().GetRow(gatheringPoint.GatheringPointBase.RowId);
|
||||
|
||||
return $"Lv. {gatheringPointBase.GatheringLevel.ToString()} {key.name}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using KamiLib.Classes;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Extensions;
|
||||
|
||||
namespace Mappy.Classes.Caches;
|
||||
|
||||
public class TooltipCache : Cache<uint, string>
|
||||
{
|
||||
protected override string LoadValue(uint key)
|
||||
{
|
||||
var mapMarker = Service.DataManager.GetExcelSheet<MapSymbol>().FirstOrNull(marker => marker.Icon == key);
|
||||
|
||||
if (mapMarker is null) return string.Empty;
|
||||
if (!mapMarker.Value.PlaceName.IsValid) return string.Empty;
|
||||
|
||||
return mapMarker.Value.PlaceName.Value.Name.ExtractText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using KamiLib.Classes;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.Caches;
|
||||
|
||||
public class TripleTriadCache : Cache<uint, bool>
|
||||
{
|
||||
protected override bool LoadValue(uint key) => Service.DataManager.GetExcelSheet<TripleTriad>().HasRow(key);
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
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>
|
||||
/// 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 texture = Service.TextureProvider.GetFromGameIcon(markerInfo.IconId).GetWrapOrEmpty();
|
||||
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;
|
||||
}
|
||||
|
||||
ImGui.SetCursorPos(markerInfo.Position + markerInfo.Offset - texture.Size * iconScale / 2.0f * scale * System.IconConfig.IconSettingMap[markerInfo.IconId].Scale);
|
||||
var cursorScreenPos = ImGui.GetCursorScreenPos();
|
||||
var iconSize = texture.Size * scale * iconScale * System.IconConfig.IconSettingMap[markerInfo.IconId].Scale;
|
||||
|
||||
ImGui.Image(texture.Handle, iconSize, Vector2.Zero, Vector2.One, System.IconConfig.IconSettingMap[markerInfo.IconId].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();
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Mappy.Classes;
|
||||
|
||||
[Flags]
|
||||
public enum HoverFlags
|
||||
{
|
||||
Nothing = 0,
|
||||
MapTexture = 1 << 0,
|
||||
Toolbar = 1 << 1,
|
||||
CoordinateBar = 1 << 2,
|
||||
Window = 1 << 3,
|
||||
WindowInnerFrame = 1 << 4,
|
||||
}
|
||||
|
||||
public static class HoverFlagsExtensions
|
||||
{
|
||||
public static bool Any(this HoverFlags flags) => flags != HoverFlags.Nothing;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using KamiLib.Window;
|
||||
using Mappy.Data;
|
||||
using Mappy.Windows;
|
||||
|
||||
namespace Mappy.Classes.MapWindowComponents;
|
||||
|
||||
public unsafe class MapContextMenu
|
||||
{
|
||||
public void Draw(Vector2 mapDrawOffset)
|
||||
{
|
||||
using var contextMenu = ImRaii.ContextPopup("Mappy_Context_Menu");
|
||||
|
||||
if (!contextMenu) return;
|
||||
|
||||
if (ImGui.MenuItem("Place Flag")) {
|
||||
var cursorPosition = ImGui.GetMousePosOnOpeningCurrentPopup(); // Get initial cursor position (screen relative)
|
||||
var mapChildOffset = mapDrawOffset; // Get the screen position we started drawing the map at
|
||||
var mapDrawPositionOffset = System.MapRenderer.DrawPosition; // Get the map texture top left offset vector
|
||||
var textureClickLocation = (cursorPosition - mapChildOffset - mapDrawPositionOffset) / MapRenderer.MapRenderer.Scale; // Math
|
||||
var result = textureClickLocation - new Vector2(1024.0f, 1024.0f); // One of our vectors made the map centered, undo it.
|
||||
var scaledResult = result / DrawHelpers.GetMapScaleFactor() + DrawHelpers.GetRawMapOffsetVector(); // Apply offset x/y and scalefactor
|
||||
|
||||
AgentMap.Instance()->FlagMarkerCount = 0;
|
||||
AgentMap.Instance()->SetFlagMapMarker(AgentMap.Instance()->SelectedTerritoryId, AgentMap.Instance()->SelectedMapId, scaledResult.X, scaledResult.Y);
|
||||
AgentChatLog.Instance()->InsertTextCommandParam(1048, false);
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Remove Flag", false, AgentMap.Instance()->FlagMarkerCount is not 0)) {
|
||||
AgentMap.Instance()->FlagMarkerCount = 0;
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5.0f);
|
||||
|
||||
if (ImGui.MenuItem("Center on Player", false, Service.ObjectTable.LocalPlayer is not null) && Service.ObjectTable.LocalPlayer is not null) {
|
||||
System.IntegrationsController.OpenOccupiedMap();
|
||||
System.MapRenderer.CenterOnGameObject(Service.ObjectTable.LocalPlayer);
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Center on Map")) {
|
||||
System.SystemConfig.FollowPlayer = false;
|
||||
System.MapRenderer.DrawOffset = Vector2.Zero;
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5.0f);
|
||||
|
||||
if (ImGui.MenuItem("Lock Zoom", "", ref System.SystemConfig.ZoomLocked)) {
|
||||
SystemConfig.Save();
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5.0f);
|
||||
|
||||
if (ImGui.MenuItem("Open Quest List", false, System.WindowManager.GetWindow<QuestListWindow>() is null)) {
|
||||
System.WindowManager.AddWindow(new QuestListWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn);
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open Fate List", false, System.WindowManager.GetWindow<FateListWindow>() is null)) {
|
||||
System.WindowManager.AddWindow(new FateListWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn);
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open Flag List", false, System.WindowManager.GetWindow<FlagHistoryWindow>() is null)) {
|
||||
System.WindowManager.AddWindow(new FlagHistoryWindow(), WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
namespace Mappy.Classes.MapWindowComponents;
|
||||
|
||||
public unsafe class MapCoordinateBar
|
||||
{
|
||||
public void Draw(bool isMapHovered, Vector2 mapDrawOffset)
|
||||
{
|
||||
var coordinateBarSize = new Vector2(ImGui.GetContentRegionMax().X, 20.0f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.SetCursorPos(ImGui.GetContentRegionMax() - coordinateBarSize);
|
||||
|
||||
using var childBackgroundStyle = ImRaii.PushColor(ImGuiCol.ChildBg, Vector4.Zero with { W = System.SystemConfig.CoordinateBarFade });
|
||||
using var coordinateChild = ImRaii.Child("coordinate_child", coordinateBarSize);
|
||||
if (!coordinateChild) return;
|
||||
|
||||
var offsetX = -AgentMap.Instance()->SelectedOffsetX;
|
||||
var offsetY = -AgentMap.Instance()->SelectedOffsetY;
|
||||
var scale = AgentMap.Instance()->SelectedMapSizeFactor;
|
||||
|
||||
var characterMapPosition = MapUtil.WorldToMap(Service.ObjectTable.LocalPlayer?.Position ?? Vector3.Zero, offsetX, offsetY, 0, (uint)scale);
|
||||
var characterPosition = $"Character {characterMapPosition.X:F1} {characterMapPosition.Y:F1}";
|
||||
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2.0f * ImGuiHelpers.GlobalScale);
|
||||
|
||||
var characterStringSize = ImGui.CalcTextSize(characterPosition);
|
||||
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X / 3.0f - characterStringSize.X / 2.0f);
|
||||
|
||||
if (AgentMap.Instance()->SelectedMapId == AgentMap.Instance()->CurrentMapId) {
|
||||
ImGui.TextColored(System.SystemConfig.CoordinateTextColor, characterPosition);
|
||||
}
|
||||
|
||||
if (isMapHovered) {
|
||||
var cursorPosition = ImGui.GetMousePos() - mapDrawOffset;
|
||||
cursorPosition -= System.MapRenderer.DrawPosition;
|
||||
cursorPosition /= MapRenderer.MapRenderer.Scale;
|
||||
cursorPosition -= new Vector2(1024.0f, 1024.0f);
|
||||
cursorPosition -= new Vector2(offsetX, offsetY);
|
||||
cursorPosition /= AgentMap.Instance()->SelectedMapSizeFactorFloat;
|
||||
|
||||
var cursorMapPosition = MapUtil.WorldToMap(new Vector3(cursorPosition.X, 0.0f, cursorPosition.Y), offsetX, offsetY, 0, (uint)scale);
|
||||
var cursorPositionString = $"Cursor {cursorMapPosition.X:F1} {cursorMapPosition.Y:F1}";
|
||||
var cursorStringSize = ImGui.CalcTextSize(characterPosition);
|
||||
ImGui.SameLine(ImGui.GetContentRegionMax().X * 2.0f / 3.0f - cursorStringSize.X / 2.0f);
|
||||
ImGui.TextColored(System.SystemConfig.CoordinateTextColor, cursorPositionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiLib.Extensions;
|
||||
using KamiLib.Window;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Mappy.Windows;
|
||||
using MapType = Lumina.Excel.Sheets.MapType;
|
||||
|
||||
namespace Mappy.Classes.MapWindowComponents;
|
||||
|
||||
public unsafe class MapToolbar
|
||||
{
|
||||
public void Draw()
|
||||
{
|
||||
var toolbarSize = new Vector2(ImGui.GetContentRegionMax().X, 33.0f * ImGuiHelpers.GlobalScale);
|
||||
|
||||
using var childBackgroundStyle = ImRaii.PushColor(ImGuiCol.ChildBg, Vector4.Zero with { W = System.SystemConfig.ToolbarFade });
|
||||
using var toolbarChild = ImRaii.Child("toolbar_child", toolbarSize);
|
||||
if (!toolbarChild) return;
|
||||
|
||||
ImGui.SetCursorPos(new Vector2(5.0f, 5.0f));
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.ArrowUp, "up", "Open Parent Map")) {
|
||||
var valueArgs = new AtkValue
|
||||
{
|
||||
Type = ValueType.Int, Int = 5,
|
||||
};
|
||||
|
||||
var returnValue = new AtkValue();
|
||||
AgentMap.Instance()->ReceiveEvent(&returnValue, &valueArgs, 1, 0);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.LayerGroup, "layers", "Show Map Layers")) {
|
||||
ImGui.OpenPopup("Mappy_Show_Layers");
|
||||
}
|
||||
|
||||
DrawLayersContextMenu();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var _ = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetStyle().GetColor(ImGuiCol.ButtonActive), System.SystemConfig.FollowPlayer)) {
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.LocationArrow, "follow", "Toggle Follow Player")) {
|
||||
System.SystemConfig.FollowPlayer = !System.SystemConfig.FollowPlayer;
|
||||
|
||||
if (System.SystemConfig.FollowPlayer) {
|
||||
System.IntegrationsController.OpenOccupiedMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.ArrowsToCircle, "centerPlayer", "Center on Player") && Service.ObjectTable.LocalPlayer is not null) {
|
||||
// Don't center on player if we are already following the player.
|
||||
if (!System.SystemConfig.FollowPlayer) {
|
||||
System.IntegrationsController.OpenOccupiedMap();
|
||||
System.MapRenderer.CenterOnGameObject(Service.ObjectTable.LocalPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.MapMarked, "centerMap", "Center on Map")) {
|
||||
System.SystemConfig.FollowPlayer = false;
|
||||
System.MapRenderer.DrawOffset = Vector2.Zero;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.Search, "search", "Search for Map")) {
|
||||
System.WindowManager.AddWindow(new MapSelectionWindow
|
||||
{
|
||||
SingleSelectionCallback = selection =>
|
||||
{
|
||||
if (selection?.Map != null) {
|
||||
if (AgentMap.Instance()->SelectedMapId != selection.Map.RowId) {
|
||||
System.IntegrationsController.OpenMap(selection.Map.RowId);
|
||||
}
|
||||
|
||||
if (selection.MarkerLocation is { } location) {
|
||||
System.SystemConfig.FollowPlayer = false;
|
||||
System.MapRenderer.DrawOffset = -location + DrawHelpers.GetMapCenterOffsetVector();
|
||||
}
|
||||
}
|
||||
},
|
||||
}, WindowFlags.OpenImmediately | WindowFlags.RequireLoggedIn);
|
||||
}
|
||||
|
||||
var offset = System.SystemConfig.HideWindowFrame ? 50.0f : 25.0f;
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - offset * ImGuiHelpers.GlobalScale - ImGui.GetStyle().ItemSpacing.X);
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.Cog, "settings", "Open Settings")) {
|
||||
System.ConfigWindow.UnCollapseOrShow();
|
||||
ImGui.SetWindowFocus(System.ConfigWindow.WindowName);
|
||||
}
|
||||
|
||||
if (!System.SystemConfig.HideWindowFrame) return;
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (MappyGuiTweaks.IconButton(FontAwesomeIcon.Times, "closeMap", "Close Map"))
|
||||
{
|
||||
System.MapWindow.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLayersContextMenu()
|
||||
{
|
||||
using var contextMenu = ImRaii.Popup("Mappy_Show_Layers");
|
||||
if (!contextMenu) return;
|
||||
|
||||
var currentMap = Service.DataManager.GetExcelSheet<Map>().GetRow(AgentMap.Instance()->SelectedMapId);
|
||||
if (currentMap.RowId is 0) return;
|
||||
|
||||
// If this is a region map
|
||||
if (currentMap.MapType.RowId == 3) {
|
||||
foreach (var marker in AgentMap.Instance()->MapMarkers) {
|
||||
if (!DrawHelpers.IsRegionIcon(marker.MapMarker.IconId)) continue;
|
||||
|
||||
var label = marker.MapMarker.Subtext.AsDalamudSeString();
|
||||
|
||||
if (ImGui.MenuItem(label.ToString())) {
|
||||
System.IntegrationsController.OpenMap(marker.DataKey);
|
||||
System.SystemConfig.FollowPlayer = false;
|
||||
System.MapRenderer.DrawOffset = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any other map
|
||||
else {
|
||||
var layers = Service.DataManager.GetExcelSheet<Map>()
|
||||
.Where(eachMap => eachMap.PlaceName.RowId == currentMap.PlaceName.RowId)
|
||||
.Where(eachMap => eachMap.MapIndex != 0)
|
||||
.OrderBy(eachMap => eachMap.MapIndex)
|
||||
.ToList();
|
||||
|
||||
if (layers.Count is 0) {
|
||||
ImGui.Text("No layers for this map");
|
||||
}
|
||||
|
||||
foreach (var layer in layers) {
|
||||
if (ImGui.MenuItem(layer.PlaceNameSub.Value.Name.ExtractText(), "", AgentMap.Instance()->SelectedMapId == layer.RowId)) {
|
||||
System.IntegrationsController.OpenMap(layer.RowId);
|
||||
System.SystemConfig.FollowPlayer = false;
|
||||
System.MapRenderer.DrawOffset = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace Mappy.Classes;
|
||||
|
||||
public static class MappyGuiTweaks
|
||||
{
|
||||
public static bool IconButton(FontAwesomeIcon icon, string id, string? tooltip)
|
||||
{
|
||||
using var imRaiiId = ImRaii.PushId(id);
|
||||
|
||||
bool result;
|
||||
|
||||
using (Service.PluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) {
|
||||
result = ImGui.Button($"{icon.ToIconString()}");
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && tooltip is not null) {
|
||||
ImGui.SetTooltip(tooltip);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Mappy.Classes;
|
||||
|
||||
public enum MarkerType
|
||||
{
|
||||
Unknown = 0,
|
||||
Fate = 1,
|
||||
Stellar = 6,
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using KamiLib.Extensions;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.SelectionWindowComponents;
|
||||
|
||||
public class AetheryteDrawableOption : DrawableOption
|
||||
{
|
||||
public required Aetheryte Aetheryte { get; set; }
|
||||
|
||||
public override string ExtraLineLong => GetName();
|
||||
|
||||
public override Map Map => GetAetheryteMap()!.Value; // Probably a bad idea
|
||||
|
||||
protected override string[] GetAdditionalFilterStrings() =>
|
||||
[
|
||||
Aetheryte.PlaceName.Value.Name.ExtractText(),
|
||||
Aetheryte.AethernetName.Value.Name.ExtractText(),
|
||||
];
|
||||
|
||||
protected override void DrawIcon()
|
||||
{
|
||||
using var imageFrame = ImRaii.Child($"image_frame{Aetheryte.RowId}#{MarkerLocation}#{ExtraLineLong}", new Vector2(Width, Height), false, ImGuiWindowFlags.NoInputs);
|
||||
if (!imageFrame) return;
|
||||
|
||||
var xOffset = (Width - Height) / 2.0f;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xOffset);
|
||||
ImGui.Image(Service.TextureProvider.GetFromGameIcon(Aetheryte.IsAetheryte ? 60453 : 60430).GetWrapOrEmpty().Handle, new Vector2(Height, Height));
|
||||
}
|
||||
|
||||
private Map? GetAetheryteMap()
|
||||
{
|
||||
if (Aetheryte.Map.RowId is not 0) return Aetheryte.Map.Value;
|
||||
|
||||
if (Service.DataManager.GetExcelSheet<Aetheryte>().FirstOrNull(aetheryte => aetheryte.IsAetheryte && aetheryte.AethernetGroup == Aetheryte.AethernetGroup) is not
|
||||
{ } targetAetheryte)
|
||||
return null;
|
||||
|
||||
return targetAetheryte.Map.Value;
|
||||
}
|
||||
|
||||
private string GetName()
|
||||
{
|
||||
if (Aetheryte.AethernetName.RowId is not 0) return Aetheryte.AethernetName.Value.Name.ExtractText();
|
||||
if (Aetheryte.PlaceName.RowId is not 0) return Aetheryte.PlaceName.Value.Name.ExtractText();
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override string GetElementKey() => base.GetElementKey() + $"{Aetheryte.RowId}";
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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 Dalamud.Utility;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.SelectionWindowComponents;
|
||||
|
||||
public abstract class DrawableOption
|
||||
{
|
||||
protected virtual string[] GetAdditionalFilterStrings() => [];
|
||||
|
||||
public virtual Map Map { get; set; }
|
||||
|
||||
protected static float Width => 133.5f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
protected static float Height => 75.0f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
protected abstract void DrawIcon();
|
||||
|
||||
public virtual string ExtraLineLong => string.Empty;
|
||||
|
||||
public virtual string ExtraLineShort => string.Empty;
|
||||
|
||||
public virtual Vector2? MarkerLocation => null;
|
||||
|
||||
public virtual string GetElementKey() => $"{Map.RowId}{MarkerLocation}{ExtraLineShort}{ExtraLineLong}";
|
||||
|
||||
public string[] GetFilterStrings()
|
||||
{
|
||||
if (Map.RowId is 0) return [];
|
||||
|
||||
var baseStrings = new[]
|
||||
{
|
||||
Map.PlaceNameRegion.ValueNullable?.Name.ExtractText() ?? string.Empty, Map.PlaceName.ValueNullable?.Name.ExtractText() ?? string.Empty,
|
||||
Map.PlaceNameSub.ValueNullable?.Name.ExtractText() ?? string.Empty, Map.TerritoryType.ValueNullable?.Name.ExtractText() ?? string.Empty, Map.Id.ExtractText(),
|
||||
};
|
||||
|
||||
return baseStrings.Concat(GetAdditionalFilterStrings()).ToArray();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var id = ImRaii.PushId(Map.RowId.ToString());
|
||||
|
||||
DrawIcon();
|
||||
ImGui.SameLine();
|
||||
|
||||
using var contentsFrame = ImRaii.Child($"contents_frame#{GetElementKey()}", new Vector2(ImGui.GetContentRegionAvail().X, Height), false, ImGuiWindowFlags.NoInputs);
|
||||
if (!contentsFrame) return;
|
||||
|
||||
ImGuiHelpers.ScaledDummy(1.0f);
|
||||
|
||||
using var table = ImRaii.Table("data_table", 2, ImGuiTableFlags.SizingStretchProp);
|
||||
if (!table) return;
|
||||
|
||||
ImGui.TableSetupColumn("##column1", ImGuiTableColumnFlags.None, 2.0f);
|
||||
ImGui.TableSetupColumn("##column2", ImGuiTableColumnFlags.None, 1.0f);
|
||||
|
||||
var placeName = Map.PlaceName.ValueNullable?.Name.ExtractText() ?? string.Empty;
|
||||
var zoneName = Map.PlaceNameSub.ValueNullable?.Name.ExtractText() ?? string.Empty;
|
||||
var regionName = Map.PlaceNameRegion.ValueNullable?.Name.ExtractText() ?? string.Empty;
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(placeName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(Map.RowId.ToString());
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
using var grayColor = ImRaii.PushColor(ImGuiCol.Text, KnownColor.DarkGray.Vector());
|
||||
if (!zoneName.IsNullOrEmpty() && !regionName.IsNullOrEmpty()) {
|
||||
ImGui.TextUnformatted($"{regionName}, {zoneName}");
|
||||
}
|
||||
else if (!zoneName.IsNullOrEmpty()) {
|
||||
ImGui.TextUnformatted($"{zoneName}");
|
||||
}
|
||||
else if (!regionName.IsNullOrEmpty()) {
|
||||
ImGui.TextUnformatted($"{regionName}");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{Map.Id}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(ExtraLineLong);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(ExtraLineShort);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.SelectionWindowComponents;
|
||||
|
||||
public class MapDrawableOption : DrawableOption
|
||||
{
|
||||
protected override void DrawIcon()
|
||||
{
|
||||
var option = Map.TerritoryType.Value;
|
||||
|
||||
using var imageFrame = ImRaii.Child($"image_frame{option}", new Vector2(Width, Height), false, ImGuiWindowFlags.NoInputs);
|
||||
if (!imageFrame) return;
|
||||
|
||||
var texture = GetMapTexture(Map.RowId);
|
||||
if (texture is not null) {
|
||||
ImGui.Image(texture.Handle, new Vector2(Width, Height), new Vector2(0.15f, 0.15f), new Vector2(0.85f, 0.85f));
|
||||
}
|
||||
else {
|
||||
ImGuiHelpers.ScaledDummy(Width, Height);
|
||||
}
|
||||
}
|
||||
|
||||
public static IDalamudTextureWrap? GetMapTexture(uint mapId)
|
||||
{
|
||||
if (mapId is 0) return null;
|
||||
|
||||
var map = Service.DataManager.GetExcelSheet<Map>().GetRow(mapId);
|
||||
var territory = map.TerritoryType;
|
||||
if (!territory.IsValid) return null;
|
||||
|
||||
var loadingImage = territory.Value.LoadingImage;
|
||||
if (!loadingImage.IsValid) return null;
|
||||
|
||||
var texturePath = $"ui/loadingimage/{loadingImage.Value.FileName}_hr1.tex";
|
||||
return Service.TextureProvider.GetFromGame(texturePath).GetWrapOrDefault();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Mappy.Classes.SelectionWindowComponents;
|
||||
|
||||
public class PoiDrawableOption : DrawableOption
|
||||
{
|
||||
public required MapMarker MapMarker { get; set; }
|
||||
|
||||
public override Vector2? MarkerLocation => new Vector2(MapMarker.X, MapMarker.Y);
|
||||
|
||||
public override Map Map => Service.DataManager.GetExcelSheet<Map>().FirstOrDefault(map => map.MapMarkerRange == MapMarker.RowId);
|
||||
|
||||
public override string ExtraLineLong => MapMarker.PlaceNameSubtext.Value.Name.ExtractText();
|
||||
|
||||
protected override void DrawIcon()
|
||||
{
|
||||
using var imageFrame = ImRaii.Child($"image_frame{MapMarker.RowId}#{MarkerLocation}", new Vector2(Width, Height), false, ImGuiWindowFlags.NoInputs);
|
||||
if (!imageFrame) return;
|
||||
|
||||
var xOffset = (Width - Height) / 2.0f;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xOffset);
|
||||
ImGui.Image(Service.TextureProvider.GetFromGameIcon((uint)MapMarker.Icon).GetWrapOrEmpty().Handle, new Vector2(Height, Height));
|
||||
}
|
||||
|
||||
protected override string[] GetAdditionalFilterStrings() =>
|
||||
[
|
||||
MapMarker.PlaceNameSubtext.Value.Name.ExtractText(),
|
||||
];
|
||||
|
||||
public override string GetElementKey() => base.GetElementKey() + $"{MapMarker.RowId}";
|
||||
}
|
||||
Reference in New Issue
Block a user