851b450a17
Made-with: Cursor
600 lines
24 KiB
C#
600 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Hooking;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using KamiLib.Classes;
|
|
using KamiLib.Extensions;
|
|
using Lumina.Excel.Sheets;
|
|
using Mappy.Classes;
|
|
using Mappy.Extensions;
|
|
using MapType = FFXIVClientStructs.FFXIV.Client.UI.Agent.MapType;
|
|
|
|
namespace Mappy.Controllers;
|
|
|
|
public unsafe class IntegrationsController : IDisposable
|
|
{
|
|
private readonly Hook<AgentMap.Delegates.ShowMap>? showMapHook;
|
|
private readonly Hook<AgentMap.Delegates.OpenMap>? openMapHook;
|
|
|
|
private bool _wasBetweenAreas;
|
|
private int _lastQuestCount = -1;
|
|
private int _lastTempMarkerCount = -1;
|
|
/// <summary>Snapshot of (QuestId, Sequence) for each active quest; when this changes we refresh so markers update (e.g. multi-step objective).</summary>
|
|
private string _lastQuestSequenceSnapshot = string.Empty;
|
|
private bool _refreshedDuringLoad;
|
|
/// <summary>When true, request a silent refresh on the next framework update (e.g. after plugin load).</summary>
|
|
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; }
|
|
|
|
public IntegrationsController()
|
|
{
|
|
showMapHook ??=
|
|
Service.Hooker.HookFromAddress<AgentMap.Delegates.ShowMap>(AgentMap.MemberFunctionPointers.ShowMap,
|
|
OnShowHook);
|
|
openMapHook ??=
|
|
Service.Hooker.HookFromAddress<AgentMap.Delegates.OpenMap>(AgentMap.MemberFunctionPointers.OpenMap,
|
|
OnOpenMapHook);
|
|
|
|
if (Service.ClientState is { IsPvP: false })
|
|
{
|
|
EnableIntegrations();
|
|
}
|
|
|
|
Service.ClientState.EnterPvP += DisableIntegrations;
|
|
Service.ClientState.LeavePvP += EnableIntegrations;
|
|
Service.Framework.Update += OnFrameworkUpdate;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Service.Framework.Update -= OnFrameworkUpdate;
|
|
DisableIntegrations();
|
|
|
|
showMapHook?.Dispose();
|
|
openMapHook?.Dispose();
|
|
|
|
Service.ClientState.EnterPvP -= DisableIntegrations;
|
|
Service.ClientState.LeavePvP -= EnableIntegrations;
|
|
}
|
|
|
|
private void EnableIntegrations()
|
|
{
|
|
Service.Log.Debug("Enabling Integrations");
|
|
|
|
showMapHook?.Enable();
|
|
openMapHook?.Enable();
|
|
|
|
// System.AreaMapController.EnableIntegrations();
|
|
System.FlagController.EnableIntegrations();
|
|
}
|
|
|
|
private void DisableIntegrations()
|
|
{
|
|
Service.Log.Debug("Disabling Integrations");
|
|
|
|
showMapHook?.Disable();
|
|
openMapHook?.Disable();
|
|
|
|
// System.AreaMapController.DisableIntegrations();
|
|
System.FlagController.DisableIntegrations();
|
|
}
|
|
|
|
/// <summary>
|
|
/// On load/quest accept/turn-in/objective update we open the map briefly so the game populates markers, then Hide().
|
|
/// The map renderer caches static, temp, and event marker positions during that time so the minimap can draw them after the map is closed.
|
|
/// </summary>
|
|
private void OnFrameworkUpdate(IFramework framework)
|
|
{
|
|
if (Service.ClientState is not { IsLoggedIn: true } or { IsPvP: true }) return;
|
|
|
|
// If we're in the middle of a silent refresh, count down then Hide() so the map closes and we don't affect other plugins
|
|
if (_silentRefreshHideFramesRemaining > 0) {
|
|
_silentRefreshHideFramesRemaining--;
|
|
if (_silentRefreshHideFramesRemaining == 0) {
|
|
try { AgentMap.Instance()->Hide(); } catch { }
|
|
SilentRefreshInProgress = false;
|
|
}
|
|
_wasBetweenAreas = Service.Condition.IsBetweenAreas();
|
|
_lastQuestCount = GetActiveQuestCount();
|
|
try { _lastTempMarkerCount = (int)AgentMap.Instance()->TempMapMarkerCount; } catch { }
|
|
return;
|
|
}
|
|
|
|
var betweenAreas = Service.Condition.IsBetweenAreas();
|
|
|
|
// First frame after leaving a load screen: refresh so minimap has static POI and markers without user opening map
|
|
if (_wasBetweenAreas && !betweenAreas)
|
|
RequestSilentRefresh();
|
|
|
|
// Once per load screen: refresh while the screen is black so the game populates markers
|
|
if (betweenAreas) {
|
|
if (!_refreshedDuringLoad)
|
|
RequestSilentRefresh();
|
|
_refreshedDuringLoad = true;
|
|
} else {
|
|
_refreshedDuringLoad = false;
|
|
}
|
|
|
|
_wasBetweenAreas = betweenAreas;
|
|
|
|
// On plugin load (first frame we're in a zone), refresh so markers are populated immediately
|
|
if (_requestRefreshOnLoad) {
|
|
_requestRefreshOnLoad = false;
|
|
RequestSilentRefresh();
|
|
}
|
|
|
|
// 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 { }
|
|
var sequenceSnapshot = GetQuestSequenceSnapshot();
|
|
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;
|
|
}
|
|
|
|
// Movement trail: record player position when enabled (Carbonite-style)
|
|
if (System.SystemConfig.ShowMovementTrail && Service.ObjectTable.LocalPlayer is { } localPlayer) {
|
|
try {
|
|
var agent = AgentMap.Instance();
|
|
System.MovementTrailConfig.TryAddPoint(
|
|
agent->CurrentTerritoryId,
|
|
agent->CurrentMapId,
|
|
localPlayer.Position.X,
|
|
localPlayer.Position.Z);
|
|
} catch { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
/// <summary>Build a string of (QuestId, Sequence) for each active quest so we can detect step advances.</summary>
|
|
private static unsafe string GetQuestSequenceSnapshot()
|
|
{
|
|
try
|
|
{
|
|
var parts = new List<string>();
|
|
foreach (var q in QuestManager.Instance()->NormalQuests)
|
|
{
|
|
if (q.QuestId is 0) continue;
|
|
parts.Add($"{q.QuestId}:{q.Sequence}");
|
|
}
|
|
return string.Join("|", parts);
|
|
}
|
|
catch
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <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()
|
|
{
|
|
RequestSilentRefreshCore(framesBeforeHide: 5);
|
|
}
|
|
|
|
private void RequestSilentRefreshCore(int framesBeforeHide)
|
|
{
|
|
if (_silentRefreshHideFramesRemaining > 0) return;
|
|
try {
|
|
var agent = AgentMap.Instance();
|
|
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();
|
|
_silentRefreshHideFramesRemaining = framesBeforeHide;
|
|
} catch {
|
|
SilentRefreshInProgress = false;
|
|
}
|
|
}
|
|
|
|
private static int GetActiveQuestCount()
|
|
{
|
|
try {
|
|
var count = 0;
|
|
foreach (var q in QuestManager.Instance()->NormalQuests) {
|
|
if (q.QuestId is not 0) count++;
|
|
}
|
|
return count;
|
|
} catch {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the current map and hide it after a few frames (see RequestSilentRefresh) so the game
|
|
/// populates MapMarkers, EventMarkers, and TempMapMarkers. Used when a refresh is requested
|
|
/// from code paths that don't use the frame-delayed flow (e.g. if needed elsewhere).
|
|
/// </summary>
|
|
private static void SilentRefreshMapMarkers()
|
|
{
|
|
try {
|
|
var agent = AgentMap.Instance();
|
|
var currentMapId = agent->CurrentMapId;
|
|
if (currentMapId == 0) return;
|
|
|
|
agent->OpenMapByMapId(currentMapId, 0, true);
|
|
agent->ResetMapMarkers();
|
|
agent->Hide();
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
private void OnShowHook(AgentMap* agent, bool a1, bool a2) =>
|
|
HookSafety.ExecuteSafe(() =>
|
|
{
|
|
Service.Log.Verbose("[OnShow] Beginning Show");
|
|
|
|
// 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();
|
|
}
|
|
|
|
if (!ShouldShowMap())
|
|
{
|
|
Service.Log.Debug("[OnShow] Condition to open map is rejected, aborting.");
|
|
return;
|
|
}
|
|
|
|
// CurrentMapId != SelectedMapId = viewing quest map in different zone; pass through, don't Hide()
|
|
if (addonId is not 0 && currentMapId != selectedMapId)
|
|
{
|
|
showMapHook!.Original(agent, a1, a2);
|
|
return;
|
|
}
|
|
|
|
if (System.SystemConfig.KeepOpen)
|
|
{
|
|
Service.Log.Verbose("[OnShow] Keeping Open");
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
case MapType.QuestLog:
|
|
ProcessQuestLink(agent, mapInfo);
|
|
break;
|
|
|
|
case MapType.GatheringLog:
|
|
ProcessGatheringLink(agent);
|
|
break;
|
|
|
|
case MapType.FlagMarker:
|
|
ProcessFlagLink(agent);
|
|
break;
|
|
|
|
case MapType.Bozja:
|
|
ProcessForayLink(agent, mapInfo);
|
|
break;
|
|
|
|
case MapType.MobHunt:
|
|
case MapType.SharedFate:
|
|
case MapType.Teleport:
|
|
case MapType.Treasure:
|
|
ProcessTeleportLink(agent, mapInfo);
|
|
break;
|
|
|
|
// This appears to get triggered after a Teleport/Shared Fate teleport event.
|
|
case MapType.Centered:
|
|
|
|
case MapType.AetherCurrent:
|
|
default:
|
|
Service.Log.Debug($"[OpenMap] Ignoring MapType: {mapInfo->Type}");
|
|
break;
|
|
}
|
|
|
|
if (System.SystemConfig.AutoZoom)
|
|
{
|
|
MapRenderer.MapRenderer.Scale =
|
|
DrawHelpers.GetMapScaleFactor() * System.SystemConfig.AutoZoomScaleFactor;
|
|
}
|
|
}, Service.Log, "Exception during OpenMap");
|
|
|
|
private void ProcessQuestLink(AgentMap* agent, OpenMapInfo* mapInfo)
|
|
{
|
|
Service.Log.Debug("[OpenMap] Processing QuestLog Event");
|
|
|
|
var targetMapId = mapInfo->MapId;
|
|
|
|
if (GetMapIdForQuest(mapInfo) is { } foundMapId)
|
|
{
|
|
Service.Log.Debug($"[OpenMap] GetMapIdForQuest identified Quest Target Map as MapId: {foundMapId}");
|
|
|
|
if (targetMapId is 0)
|
|
{
|
|
Service.Log.Debug($"[OpenMap] targetMapId was {targetMapId} using foundMapId: {foundMapId}");
|
|
targetMapId = foundMapId;
|
|
}
|
|
}
|
|
|
|
if (agent->SelectedMapId != targetMapId)
|
|
{
|
|
Service.Log.Debug($"[OpenMap] Opening MapId: {targetMapId}");
|
|
OpenMap(targetMapId);
|
|
}
|
|
else
|
|
{
|
|
Service.Log.Debug($"[OpenMap] Already in MapId: {targetMapId}, aborting.");
|
|
}
|
|
|
|
if (System.SystemConfig.CenterOnQuest)
|
|
{
|
|
ref var targetMarker = ref agent->TempMapMarkers[0].MapMarker;
|
|
CenterOnMarker(targetMarker);
|
|
Service.Log.Debug($"[OpenMap] Centering Map on X = {targetMarker.X}, Y = {targetMarker.Y}");
|
|
}
|
|
|
|
SuppressSilentRefreshForUserMapOpen();
|
|
System.MapWindow.ProcessingCommand = true;
|
|
}
|
|
|
|
private void ProcessGatheringLink(AgentMap* agent)
|
|
{
|
|
Service.Log.Debug("[OpenMap] Processing GatheringLog Event");
|
|
|
|
if (System.SystemConfig.CenterOnGathering)
|
|
{
|
|
ref var targetMarker = ref agent->TempMapMarkers[0].MapMarker;
|
|
|
|
CenterOnMarker(targetMarker);
|
|
Service.Log.Debug($"[OpenMap] Centering Map on X = {targetMarker.X}, Y = {targetMarker.Y}");
|
|
}
|
|
|
|
SuppressSilentRefreshForUserMapOpen();
|
|
System.MapWindow.ProcessingCommand = true;
|
|
}
|
|
|
|
private void ProcessFlagLink(AgentMap* agent)
|
|
{
|
|
Service.Log.Debug("[OpenMap] Processing FlagMarker Event");
|
|
|
|
if (System.SystemConfig.CenterOnFlag)
|
|
{
|
|
ref var targetMarker = ref agent->FlagMapMarkers[0].MapMarker;
|
|
|
|
CenterOnMarker(targetMarker);
|
|
Service.Log.Debug($"[OpenMap] Centering Map on X = {targetMarker.X}, Y = {targetMarker.Y}");
|
|
}
|
|
|
|
SuppressSilentRefreshForUserMapOpen();
|
|
System.MapWindow.ProcessingCommand = true;
|
|
}
|
|
|
|
private void ProcessForayLink(AgentMap* agent, OpenMapInfo* mapInfo)
|
|
{
|
|
Service.Log.Debug("[OpenMap] Processing Bozja Event");
|
|
|
|
var eventMarker =
|
|
agent->EventMarkers.FirstOrNull(marker => marker.DataId == mapInfo->FateId && marker.Flags == 0x40);
|
|
if (eventMarker is not null)
|
|
{
|
|
CenterOnMarker(eventMarker.Value);
|
|
}
|
|
|
|
SuppressSilentRefreshForUserMapOpen();
|
|
System.MapWindow.ProcessingCommand = true;
|
|
}
|
|
|
|
private void ProcessTeleportLink(AgentMap* agent, OpenMapInfo* mapInfo)
|
|
{
|
|
Service.Log.Debug("[OpenMap] Processing Teleport Event");
|
|
|
|
var targetMapId = mapInfo->MapId;
|
|
|
|
if (agent->CurrentMapId != targetMapId)
|
|
{
|
|
Service.Log.Debug($"[OpenMap] Opening MapId: {targetMapId}");
|
|
|
|
OpenMap(mapInfo->MapId);
|
|
|
|
SuppressSilentRefreshForUserMapOpen();
|
|
System.MapWindow.ProcessingCommand = true;
|
|
|
|
return;
|
|
}
|
|
|
|
Service.Log.Debug($"[OpenMap] Already in MapId: {targetMapId}, aborting.");
|
|
SuppressSilentRefreshForUserMapOpen();
|
|
System.MapWindow.ProcessingCommand = true;
|
|
}
|
|
|
|
public void OpenMap(uint mapId)
|
|
{
|
|
AgentMap.Instance()->OpenMapByMapId(mapId, 0, true);
|
|
|
|
// Since this is effecting state, we need to keep an eye on it for potential issues.
|
|
AgentMap.Instance()->ResetMapMarkers();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask the game to refresh its map markers (quests, FATEs, gathering, etc.) for the current map.
|
|
/// Uses OpenMapByMapId with show=false so the game repopulates markers without opening the Area Map UI.
|
|
/// Call periodically when the minimap is visible so it stays in sync.
|
|
/// </summary>
|
|
public static void RefreshMapMarkers()
|
|
{
|
|
try {
|
|
var agent = AgentMap.Instance();
|
|
var currentMapId = agent->CurrentMapId;
|
|
if (currentMapId == 0) return;
|
|
// OpenMapByMapId with show=false: refresh map/marker data for current map without showing the map addon.
|
|
agent->OpenMapByMapId(currentMapId, 0, false);
|
|
} catch {
|
|
// Ignore if agent not ready
|
|
}
|
|
}
|
|
|
|
public void OpenOccupiedMap() => OpenMap(AgentMap.Instance()->CurrentMapId);
|
|
|
|
private static void CenterOnMarker(MapMarkerBase marker)
|
|
{
|
|
var coordinates = new Vector2(marker.X, marker.Y) / 16.0f * DrawHelpers.GetMapScaleFactor() -
|
|
DrawHelpers.GetMapOffsetVector();
|
|
|
|
System.SystemConfig.FollowPlayer = false;
|
|
System.MapRenderer.DrawOffset = -coordinates;
|
|
}
|
|
|
|
private static void CenterOnMarker(MapMarkerData marker)
|
|
{
|
|
var coordinates = marker.Position.AsMapVector() * DrawHelpers.GetMapScaleFactor() -
|
|
DrawHelpers.GetMapOffsetVector();
|
|
|
|
System.SystemConfig.FollowPlayer = false;
|
|
System.MapRenderer.DrawOffset = -coordinates;
|
|
}
|
|
|
|
/// <summary>When true, minimap uses same visibility as main map (hides during dialogue/interaction if Hide With Game GUI is on). When false, minimap stays visible during dialogue and object interaction.</summary>
|
|
public static bool ShouldShowMinimap()
|
|
{
|
|
if (Service.ClientState is { IsLoggedIn: false } or { IsPvP: true }) return false;
|
|
if (System.SystemConfig.HideInCombat && Service.Condition.IsInCombat()) return false;
|
|
if (System.SystemConfig.HideBetweenAreas && Service.Condition.IsBetweenAreas()) return false;
|
|
if (!System.SystemConfig.MinimapHideWithGameGui) return true;
|
|
// Don't hide during dialogue (Occupied = NPC dialogue, OccupiedInQuestEvent = quest dialogue)
|
|
if (Service.Condition[ConditionFlag.Occupied] || Service.Condition[ConditionFlag.OccupiedInQuestEvent])
|
|
return true;
|
|
// Same as main map for non-dialogue cases
|
|
if (System.SystemConfig.HideWithGameGui && !IsNamePlateAddonVisible()) return false;
|
|
if (System.SystemConfig.HideWithGameGui && Control.Instance()->TargetSystem.TargetModeIndex is 1) return false;
|
|
return true;
|
|
}
|
|
|
|
public static bool ShouldShowMap()
|
|
{
|
|
if (Service.ClientState is { IsLoggedIn: false } or { IsPvP: true }) return false;
|
|
if (System.SystemConfig.HideInCombat && Service.Condition.IsInCombat()) return false;
|
|
if (System.SystemConfig.HideBetweenAreas && Service.Condition.IsBetweenAreas()) return false;
|
|
if (System.SystemConfig.HideWithGameGui && !IsNamePlateAddonVisible()) return false;
|
|
if (System.SystemConfig.HideWithGameGui && Control.Instance()->TargetSystem.TargetModeIndex is 1) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool IsNamePlateAddonVisible() =>
|
|
!RaptureAtkUnitManager.Instance()->UiFlags.HasFlag(UIModule.UiFlags.Nameplates);
|
|
|
|
private uint? GetMapIdForQuest(OpenMapInfo* mapInfo)
|
|
{
|
|
foreach (var leveQuest in QuestManager.Instance()->LeveQuests)
|
|
{
|
|
if (leveQuest.LeveId is 0) continue;
|
|
|
|
var leveData = Service.DataManager.GetExcelSheet<Leve>().GetRow(leveQuest.LeveId);
|
|
if (!IsNameMatch(leveData.Name.ExtractText(), mapInfo)) continue;
|
|
|
|
return leveData.LevelStart.Value.Map.RowId;
|
|
}
|
|
|
|
foreach (var quest in QuestManager.Instance()->NormalQuests)
|
|
{
|
|
if (quest.QuestId is 0) continue;
|
|
|
|
// Is this the quest we are looking for?
|
|
var questData = Service.DataManager.GetExcelSheet<Quest>().GetRow(quest.QuestId + 65536u);
|
|
if (!IsNameMatch(questData.Name.ExtractText(), mapInfo)) continue;
|
|
|
|
return questData
|
|
.TodoParams.FirstOrDefault(param => param.ToDoCompleteSeq == quest.Sequence)
|
|
.ToDoLocation.FirstOrDefault(location => location is not { RowId: 0, ValueNullable: null })
|
|
.Value.Map.RowId;
|
|
}
|
|
|
|
var possibleQuests = Service.DataManager.GetExcelSheet<Quest>()
|
|
.Where(quest => quest is { IssuerLocation: { IsValid: true, RowId: not 0 } })
|
|
.FirstOrNull(quest => IsNameMatch(quest.Name.ExtractText(), mapInfo));
|
|
|
|
return possibleQuests?.IssuerLocation.Value.Map.RowId ?? null;
|
|
}
|
|
|
|
private static bool IsNameMatch(string name, OpenMapInfo* mapInfo) => string.Equals(name,
|
|
mapInfo->TitleString.ToString(), StringComparison.OrdinalIgnoreCase);
|
|
} |