Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3afa7af645 | |||
| 1cccf8967a | |||
| f1864f4cac | |||
| d8457e8d87 | |||
| daaac71c83 | |||
| d10a550136 | |||
| c9b50f8f72 | |||
| 015d7ee191 | |||
| 2c54907cd5 | |||
| 31ab36d645 | |||
| 4b2c337695 |
@@ -102,7 +102,6 @@ public unsafe class AddonAreaMapController : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
// If the window actually considered closed by the agent.
|
||||
if (AgentMap.Instance()->AddonId is 0)
|
||||
{
|
||||
System.WindowManager.GetWindow<MapWindow>()?.Close();
|
||||
|
||||
@@ -33,6 +33,10 @@ public unsafe class IntegrationsController : IDisposable
|
||||
private bool _requestRefreshOnLoad = true;
|
||||
/// <summary>Frames to wait before moving the map off-screen after a silent refresh so the game has time to populate markers.</summary>
|
||||
private int _silentRefreshHideFramesRemaining;
|
||||
/// <summary>Frames to skip quest/temp-marker-triggered silent refresh after user opened map via Duty List (quest/gathering/flag/teleport).</summary>
|
||||
private int _suppressSilentRefreshFramesRemaining;
|
||||
/// <summary>Frames after user opened map via Duty List; OnShowHook should not Hide() during this window (ProcessingCommand is cleared when MapWindow opens).</summary>
|
||||
private int _userOpenedMapFramesRemaining;
|
||||
|
||||
/// <summary>True while we're doing a silent refresh; OnAreaMapPreShow should not open the MapWindow.</summary>
|
||||
public static bool SilentRefreshInProgress { get; private set; }
|
||||
@@ -135,23 +139,40 @@ public unsafe class IntegrationsController : IDisposable
|
||||
}
|
||||
|
||||
// Quest turned in, quest accepted, or objectives updated: refresh so markers stay in sync
|
||||
// Skip these triggers when user just opened map via Duty List (quest/gathering/flag/teleport)
|
||||
// so we don't close the map they intentionally opened.
|
||||
if (_suppressSilentRefreshFramesRemaining > 0) {
|
||||
_suppressSilentRefreshFramesRemaining--;
|
||||
}
|
||||
if (_userOpenedMapFramesRemaining > 0) {
|
||||
_userOpenedMapFramesRemaining--;
|
||||
}
|
||||
var skipQuestTempRefresh = _suppressSilentRefreshFramesRemaining > 0;
|
||||
|
||||
var questCount = GetActiveQuestCount();
|
||||
var tempCount = -1;
|
||||
try { tempCount = (int)AgentMap.Instance()->TempMapMarkerCount; } catch { }
|
||||
if (_lastQuestCount >= 0 && questCount < _lastQuestCount)
|
||||
RequestSilentRefresh(); // quest turned in
|
||||
if (_lastQuestCount >= 0 && questCount > _lastQuestCount)
|
||||
RequestSilentRefresh(); // quest accepted
|
||||
if (_lastTempMarkerCount >= 0 && tempCount >= 0 && tempCount < _lastTempMarkerCount)
|
||||
RequestSilentRefresh(); // objectives decreased
|
||||
if (_lastTempMarkerCount >= 0 && tempCount >= 0 && tempCount > _lastTempMarkerCount)
|
||||
RequestSilentRefresh(); // objectives added (e.g. new quest)
|
||||
var sequenceSnapshot = GetQuestSequenceSnapshot();
|
||||
if (_lastQuestSequenceSnapshot.Length > 0 && sequenceSnapshot != _lastQuestSequenceSnapshot)
|
||||
RequestSilentRefresh(); // quest step advanced (multi-step objective)
|
||||
_lastQuestCount = questCount;
|
||||
_lastTempMarkerCount = tempCount;
|
||||
_lastQuestSequenceSnapshot = sequenceSnapshot;
|
||||
if (!skipQuestTempRefresh) {
|
||||
if (_lastQuestCount >= 0 && questCount < _lastQuestCount)
|
||||
RequestSilentRefresh(); // quest turned in
|
||||
if (_lastQuestCount >= 0 && questCount > _lastQuestCount)
|
||||
RequestSilentRefresh(); // quest accepted
|
||||
if (_lastTempMarkerCount >= 0 && tempCount >= 0 && tempCount < _lastTempMarkerCount)
|
||||
RequestSilentRefresh(); // objectives decreased
|
||||
if (_lastTempMarkerCount >= 0 && tempCount >= 0 && tempCount > _lastTempMarkerCount)
|
||||
RequestSilentRefresh(); // objectives added (e.g. new quest)
|
||||
if (_lastQuestSequenceSnapshot.Length > 0 && sequenceSnapshot != _lastQuestSequenceSnapshot)
|
||||
RequestSilentRefresh(); // quest step advanced (multi-step objective)
|
||||
_lastQuestCount = questCount;
|
||||
_lastTempMarkerCount = tempCount;
|
||||
_lastQuestSequenceSnapshot = sequenceSnapshot;
|
||||
} else {
|
||||
// During suppression: only update temp baseline so we don't false-trigger when suppression
|
||||
// ends (e.g. Duty List click repopulates markers). Keep _lastQuestCount and _lastQuestSequenceSnapshot
|
||||
// so quest turn-in and objective progression during suppression still trigger refresh when suppression ends.
|
||||
_lastTempMarkerCount = tempCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Build a string of (QuestId, Sequence) for each active quest so we can detect step advances.</summary>
|
||||
@@ -173,6 +194,14 @@ public unsafe class IntegrationsController : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Call when user opens map via Duty List (quest/gathering/flag/teleport). Cancels any in-progress silent refresh so we never Hide() the map. Suppresses new quest/temp-marker-triggered refresh for ~1s. Must be called BEFORE openMapHook.Original so OnFrameworkUpdate cannot call Hide() first.</summary>
|
||||
private void SuppressSilentRefreshForUserMapOpen()
|
||||
{
|
||||
_silentRefreshHideFramesRemaining = 0; // Cancel in-progress silent refresh so we never Hide() the map the user just opened
|
||||
SilentRefreshInProgress = false;
|
||||
_suppressSilentRefreshFramesRemaining = 30; // ~1 second at typical framerate
|
||||
}
|
||||
|
||||
/// <summary>Request a silent map refresh; opens the map, waits a few frames for the game to populate markers (and caches), then Hide().</summary>
|
||||
private void RequestSilentRefresh()
|
||||
{
|
||||
@@ -187,6 +216,9 @@ public unsafe class IntegrationsController : IDisposable
|
||||
var currentMapId = agent->CurrentMapId;
|
||||
if (currentMapId == 0) return;
|
||||
|
||||
// Clear temp marker cache so old markers (e.g. from turned-in quest) don't persist
|
||||
MapRenderer.MapRenderer.InvalidateTempMarkerCache(currentMapId);
|
||||
|
||||
SilentRefreshInProgress = true;
|
||||
agent->OpenMapByMapId(currentMapId, 0, true);
|
||||
agent->ResetMapMarkers();
|
||||
@@ -234,8 +266,19 @@ public unsafe class IntegrationsController : IDisposable
|
||||
{
|
||||
Service.Log.Verbose("[OnShow] Beginning Show");
|
||||
|
||||
// If you managed to open the window while the agent says it should be closed
|
||||
if (System.MapWindow.IsOpen && AgentMap.Instance()->AddonId is 0)
|
||||
// When user just opened via Duty List / gathering / flag / teleport, pass through immediately.
|
||||
var userRequestedMap = System.MapWindow.ProcessingCommand || _userOpenedMapFramesRemaining > 0;
|
||||
if (userRequestedMap)
|
||||
{
|
||||
showMapHook!.Original(agent, a1, a2);
|
||||
return;
|
||||
}
|
||||
|
||||
var addonId = AgentMap.Instance()->AddonId;
|
||||
var currentMapId = AgentMap.Instance()->CurrentMapId;
|
||||
var selectedMapId = AgentMap.Instance()->SelectedMapId;
|
||||
|
||||
if (System.MapWindow.IsOpen && addonId is 0)
|
||||
{
|
||||
Service.Log.Debug("[OnShow] MapWindow can not be open now.");
|
||||
System.MapWindow.Close();
|
||||
@@ -247,15 +290,10 @@ public unsafe class IntegrationsController : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (AgentMap.Instance()->AddonId is not 0 &&
|
||||
AgentMap.Instance()->CurrentMapId != AgentMap.Instance()->SelectedMapId)
|
||||
// CurrentMapId != SelectedMapId = viewing quest map in different zone; pass through, don't Hide()
|
||||
if (addonId is not 0 && currentMapId != selectedMapId)
|
||||
{
|
||||
if (!System.SystemConfig.KeepOpen)
|
||||
{
|
||||
AgentMap.Instance()->Hide();
|
||||
}
|
||||
|
||||
Service.Log.Verbose("[OnShow] Vanilla tried to return to current map, aborted.");
|
||||
showMapHook!.Original(agent, a1, a2);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -268,9 +306,22 @@ public unsafe class IntegrationsController : IDisposable
|
||||
showMapHook!.Original(agent, a1, a2);
|
||||
}, Service.Log, "Exception during OnShowHook");
|
||||
|
||||
private static bool IsUserInitiatedMapOpen(MapType type) =>
|
||||
type is MapType.QuestLog or MapType.GatheringLog or MapType.FlagMarker or MapType.Bozja
|
||||
or MapType.MobHunt or MapType.SharedFate or MapType.Teleport or MapType.Treasure;
|
||||
|
||||
private void OnOpenMapHook(AgentMap* agent, OpenMapInfo* mapInfo) =>
|
||||
HookSafety.ExecuteSafe(() =>
|
||||
{
|
||||
// MUST run before Original: cancel any in-progress silent refresh so OnFrameworkUpdate won't call Hide()
|
||||
// after the game opens the map. Also set flags for OnShowHook pass-through.
|
||||
if (IsUserInitiatedMapOpen(mapInfo->Type))
|
||||
{
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
_userOpenedMapFramesRemaining = 30; // Persists after ProcessingCommand cleared by MapWindow.OnOpen
|
||||
}
|
||||
|
||||
openMapHook!.Original(agent, mapInfo);
|
||||
|
||||
switch (mapInfo->Type)
|
||||
@@ -348,10 +399,11 @@ public unsafe class IntegrationsController : IDisposable
|
||||
Service.Log.Debug($"[OpenMap] Centering Map on X = {targetMarker.X}, Y = {targetMarker.Y}");
|
||||
}
|
||||
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
}
|
||||
|
||||
private static void ProcessGatheringLink(AgentMap* agent)
|
||||
private void ProcessGatheringLink(AgentMap* agent)
|
||||
{
|
||||
Service.Log.Debug("[OpenMap] Processing GatheringLog Event");
|
||||
|
||||
@@ -363,10 +415,11 @@ public unsafe class IntegrationsController : IDisposable
|
||||
Service.Log.Debug($"[OpenMap] Centering Map on X = {targetMarker.X}, Y = {targetMarker.Y}");
|
||||
}
|
||||
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
}
|
||||
|
||||
private static void ProcessFlagLink(AgentMap* agent)
|
||||
private void ProcessFlagLink(AgentMap* agent)
|
||||
{
|
||||
Service.Log.Debug("[OpenMap] Processing FlagMarker Event");
|
||||
|
||||
@@ -378,10 +431,11 @@ public unsafe class IntegrationsController : IDisposable
|
||||
Service.Log.Debug($"[OpenMap] Centering Map on X = {targetMarker.X}, Y = {targetMarker.Y}");
|
||||
}
|
||||
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
}
|
||||
|
||||
private static void ProcessForayLink(AgentMap* agent, OpenMapInfo* mapInfo)
|
||||
private void ProcessForayLink(AgentMap* agent, OpenMapInfo* mapInfo)
|
||||
{
|
||||
Service.Log.Debug("[OpenMap] Processing Bozja Event");
|
||||
|
||||
@@ -392,6 +446,7 @@ public unsafe class IntegrationsController : IDisposable
|
||||
CenterOnMarker(eventMarker.Value);
|
||||
}
|
||||
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
}
|
||||
|
||||
@@ -407,12 +462,14 @@ public unsafe class IntegrationsController : IDisposable
|
||||
|
||||
OpenMap(mapInfo->MapId);
|
||||
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Service.Log.Debug($"[OpenMap] Already in MapId: {targetMapId}, aborting.");
|
||||
SuppressSilentRefreshForUserMapOpen();
|
||||
System.MapWindow.ProcessingCommand = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,10 +112,14 @@ public class SystemConfig : CharacterConfiguration
|
||||
public bool MinimapShowQuestDirectionArrow = true;
|
||||
/// <summary>Show direction arrows for nearby FATEs on the minimap (purple, matching FATE marker).</summary>
|
||||
public bool MinimapShowFateDirectionArrows = true;
|
||||
/// <summary>Show a red direction arrow pointing toward the player flag when it's off the minimap.</summary>
|
||||
public bool MinimapShowFlagDirectionArrow = true;
|
||||
/// <summary>Show quest objective area radius circles on the minimap (same as Area Map).</summary>
|
||||
public bool MinimapShowQuestAreaRadius = true;
|
||||
/// <summary>When true, minimap hides with the game GUI (dialogue, interaction, nameplates off). When false, minimap stays visible during dialogue and object interaction.</summary>
|
||||
public bool MinimapHideWithGameGui = false;
|
||||
/// <summary>Show Player/NPC tracking (other players, enemies, bosses, etc.) on the minimap, matching the main map display.</summary>
|
||||
public bool MinimapShowPlayersAndNpcs = true;
|
||||
|
||||
// Do not persist this setting
|
||||
[JsonIgnore]
|
||||
|
||||
@@ -214,7 +214,7 @@ public unsafe partial class MapRenderer : IDisposable
|
||||
// Markers are drawn from whatever the game has already populated (e.g. after opening the map once).
|
||||
|
||||
// When the game has the current map loaded (area map open or just closed), update our cache for this map.
|
||||
if (agent->SelectedMapId == currentMapId && agent->SelectedMapPath.Length > 0) {
|
||||
if (agent->SelectedMapId == currentMapId && agent->SelectedMapPath.Length > 0 && agent->SelectedMapBgPath.Length > 0) {
|
||||
var bgPath = $"{agent->SelectedMapBgPath}.tex";
|
||||
var fgPath = $"{agent->SelectedMapPath}.tex";
|
||||
var pathKey = bgPath + "|" + fgPath;
|
||||
@@ -282,6 +282,8 @@ public unsafe partial class MapRenderer : IDisposable
|
||||
DrawMinimapQuestDirectionArrow(contentTopLeft, drawPosition, scale, size, centerOffset, cached.OffsetX, cached.OffsetY, cached.ScaleFactor, currentMapId);
|
||||
if (System.SystemConfig.MinimapShowFateDirectionArrows)
|
||||
DrawMinimapFateDirectionArrows(contentTopLeft, drawPosition, scale, size, centerOffset, cached.OffsetX, cached.OffsetY, cached.ScaleFactor);
|
||||
if (System.SystemConfig.MinimapShowFlagDirectionArrow)
|
||||
DrawMinimapFlagDirectionArrow(contentTopLeft, drawPosition, scale, size, centerOffset, cached.OffsetX, cached.OffsetY, cached.ScaleFactor);
|
||||
}
|
||||
|
||||
private void TrimMinimapCacheToLimit()
|
||||
@@ -344,13 +346,23 @@ public unsafe partial class MapRenderer : IDisposable
|
||||
|
||||
private static TexFile? GetTexFile(string rawPath)
|
||||
{
|
||||
var path = Service.TextureSubstitutionProvider.GetSubstitutedPath(rawPath);
|
||||
|
||||
if (Path.IsPathRooted(path)) {
|
||||
return Service.DataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||
if (string.IsNullOrWhiteSpace(rawPath)) return null;
|
||||
string path;
|
||||
try {
|
||||
path = Service.TextureSubstitutionProvider.GetSubstitutedPath(rawPath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(path)) return null;
|
||||
|
||||
return Service.DataManager.GetFile<TexFile>(path);
|
||||
try {
|
||||
if (Path.IsPathRooted(path)) {
|
||||
return Service.DataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||
}
|
||||
return Service.DataManager.GetFile<TexFile>(path);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMapMarkers()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
@@ -18,6 +19,53 @@ namespace Mappy.MapRenderer;
|
||||
|
||||
public partial class MapRenderer
|
||||
{
|
||||
private unsafe void DrawMinimapGameObjects(
|
||||
Vector2 contentTopLeft,
|
||||
Func<float, float, Vector2> texToContent,
|
||||
Vector2 size,
|
||||
float scaleFactor,
|
||||
float offsetX,
|
||||
float offsetY)
|
||||
{
|
||||
if (!System.SystemConfig.MinimapShowPlayersAndNpcs) return;
|
||||
if (Service.ObjectTable is not { LocalPlayer: { } player }) return;
|
||||
|
||||
foreach (var obj in Service.ObjectTable.Reverse())
|
||||
{
|
||||
if (!obj.IsTargetable) continue;
|
||||
if (GroupManager.Instance()->MainGroup.IsEntityIdInParty(obj.EntityId)) continue;
|
||||
if (GroupManager.Instance()->MainGroup.IsEntityIdInAlliance(obj.EntityId)) continue;
|
||||
if (Vector3.Distance(obj.Position, player.Position) >= 150.0f) continue;
|
||||
|
||||
var iconId = obj.ObjectKind switch
|
||||
{
|
||||
ObjectKind.Player when GroupManager.Instance()->MainGroup.MemberCount is 0 && System.SystemConfig.ShowPlayers => 60421u,
|
||||
ObjectKind.Player when System.SystemConfig.ShowPlayers => 60444u,
|
||||
ObjectKind.BattleNpc when IsBoss(obj) && obj.TargetObject is null => 60402u,
|
||||
ObjectKind.BattleNpc when IsBoss(obj) && obj.TargetObject is not null => 60401u,
|
||||
ObjectKind.BattleNpc when obj is { SubKind: (int)BattleNpcSubKind.Enemy, TargetObject: not null } => 60422u,
|
||||
ObjectKind.BattleNpc when obj is { SubKind: (int)BattleNpcSubKind.Enemy, TargetObject: null } => 60424u,
|
||||
ObjectKind.BattleNpc when obj.SubKind == (int)BattleNpcSubKind.Pet => 60961u,
|
||||
ObjectKind.Treasure => 60003u,
|
||||
ObjectKind.GatheringPoint => System.GatheringPointIconCache.GetValue(obj.BaseId),
|
||||
ObjectKind.EventObj when IsAetherCurrent(obj) => 60653u,
|
||||
_ => 0u
|
||||
};
|
||||
|
||||
if (iconId is 0) continue;
|
||||
if (System.IconConfig.IconSettingMap.TryGetValue(iconId, out var setting) && setting.Hide) continue;
|
||||
|
||||
var pos = new Vector2(obj.Position.X, obj.Position.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 = GetTooltipForGameObject(obj);
|
||||
DrawMinimapIcon(iconId, contentPos + contentTopLeft, sizeScale: 1.5f, tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void DrawGameObjects()
|
||||
{
|
||||
if (AgentMap.Instance()->SelectedMapId != AgentMap.Instance()->CurrentMapId) return;
|
||||
|
||||
@@ -55,6 +55,8 @@ public partial class MapRenderer
|
||||
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.)
|
||||
@@ -150,14 +152,7 @@ public partial class MapRenderer
|
||||
list.Add((tx, ty, tooltip, iconId));
|
||||
}
|
||||
|
||||
if (agent->FlagMarkerCount > 0) {
|
||||
ref var flag = ref agent->FlagMapMarkers[0];
|
||||
if (flag.TerritoryId == agent->CurrentMapId || flag.TerritoryId == agent->CurrentTerritoryId) {
|
||||
var tooltip = System.TooltipCache.GetValue(flag.MapMarker.IconId);
|
||||
if (string.IsNullOrEmpty(tooltip)) tooltip = "Flag";
|
||||
AddIfNew(1024.0f + (flag.XFloat - offsetX) * scaleFactor, 1024.0f + (flag.YFloat - offsetY) * scaleFactor, tooltip, flag.MapMarker.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);
|
||||
@@ -290,6 +285,65 @@ public partial class MapRenderer
|
||||
}
|
||||
}
|
||||
|
||||
/// <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).
|
||||
@@ -317,6 +371,9 @@ public partial class MapRenderer
|
||||
/// <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();
|
||||
|
||||
@@ -674,7 +731,8 @@ public partial class MapRenderer
|
||||
var agent = AgentMap.Instance();
|
||||
if (agent->FlagMarkerCount is 0) return;
|
||||
ref var flag = ref agent->FlagMapMarkers[0];
|
||||
if (flag.TerritoryId != agent->CurrentMapId) return;
|
||||
// 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;
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<Name>HSMappy</Name>
|
||||
<InternalName>HSMappy</InternalName>
|
||||
<Author>Knack117</Author>
|
||||
<Version>1.0.0.0</Version>
|
||||
<Version>1.0.0.11</Version>
|
||||
<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>
|
||||
<RepoUrl>http://brassnet.ddns.net:33983/KnackAtNite/HSMappy</RepoUrl>
|
||||
|
||||
@@ -280,9 +280,15 @@ public class MinimapOptionsTab : ITabItem
|
||||
configChanged |= ImGui.Checkbox("Show FATE Direction Arrows", ref System.SystemConfig.MinimapShowFateDirectionArrows);
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Show purple arrows at the edge of the minimap pointing toward nearby FATEs.");
|
||||
configChanged |= ImGui.Checkbox("Show Flag Direction Arrow", ref System.SystemConfig.MinimapShowFlagDirectionArrow);
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Show a red arrow at the edge of the minimap pointing toward your placed flag.");
|
||||
configChanged |= ImGui.Checkbox("Show Quest Area Radius", ref System.SystemConfig.MinimapShowQuestAreaRadius);
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Show quest objective area circles on the minimap (same as on the Area Map).");
|
||||
configChanged |= ImGui.Checkbox("Show Players and NPCs", ref System.SystemConfig.MinimapShowPlayersAndNpcs);
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Show Player/NPC tracking on the minimap (other players, enemies, bosses, etc.), matching the main map display.");
|
||||
configChanged |= ImGui.Checkbox("Hide Minimap With Game GUI", ref System.SystemConfig.MinimapHideWithGameGui);
|
||||
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.");
|
||||
|
||||
@@ -48,7 +48,6 @@ public class MapWindow : Window
|
||||
|
||||
public override unsafe void PreOpenCheck()
|
||||
{
|
||||
// If you managed to open the window while the agent says it should be closed
|
||||
if (System.MapWindow.IsOpen && AgentMap.Instance()->AddonId is 0)
|
||||
{
|
||||
Service.Log.Debug("[OnShow] MapWindow can not be open now.");
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Mappy.Windows;
|
||||
|
||||
public class MinimapWindow : Window
|
||||
{
|
||||
private bool _wasLoggedIn;
|
||||
|
||||
public MinimapWindow() : base("HSMappy Minimap###HSMappyMinimap", new Vector2(200.0f, 200.0f))
|
||||
{
|
||||
DisableWindowSounds = true;
|
||||
@@ -22,8 +24,18 @@ public class MinimapWindow : Window
|
||||
|
||||
public override void PreOpenCheck()
|
||||
{
|
||||
if (Service.ClientState is { IsLoggedIn: false } or { IsPvP: true })
|
||||
var isLoggedIn = Service.ClientState is { IsLoggedIn: true, IsPvP: false };
|
||||
if (!isLoggedIn)
|
||||
{
|
||||
IsOpen = false;
|
||||
_wasLoggedIn = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore minimap when transitioning from login screen to in-game (ShowMinimap persists in config)
|
||||
if (!_wasLoggedIn && System.SystemConfig.ShowMinimap)
|
||||
UnCollapseOrShow();
|
||||
_wasLoggedIn = true;
|
||||
}
|
||||
|
||||
protected override unsafe void DrawContents()
|
||||
|
||||
@@ -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.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.0","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.0/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.0/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.0/latest.zip","LastUpdate":"1761700000"}]
|
||||
[{"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.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.11","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.11/latest.zip","IsHide":false,"IsTestingExclusive":false,"DownloadLinkTesting":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.11/latest.zip","DownloadLinkUpdate":"http://brassnet.ddns.net:33983/KnackAtNite/HSMappy/releases/download/v1.0.0.11/latest.zip","LastUpdate":"1772313048"}]
|
||||
|
||||
Reference in New Issue
Block a user