using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; using Dalamud.Utility; using LLib.ImGui; using Lumina.Excel; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Data; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; using Questionable.Windows.QuestComponents; namespace Questionable.Windows.JournalComponents; internal sealed class QuestMapComponent { private sealed record ZoneEntry(ushort TerritoryId, string Name, string DisplayName, string MapId); private sealed class QuestMarker { public ElementId QuestId { get; } public string QuestName { get; } public Vector2 MapPixelPos { get; } public Vector3 WorldPos { get; } public Vector4 Color { get; set; } public FontAwesomeIcon Icon { get; set; } public string Status { get; set; } public QuestMarker(ElementId questId, string questName, Vector2 mapPixelPos, Vector3 worldPos) { QuestId = questId; QuestName = questName; MapPixelPos = mapPixelPos; WorldPos = worldPos; Status = string.Empty; } } private const float MapTextureSize = 2048f; private const float MarkerRadius = 6f; private const float MarkerHitRadius = 8f; private const float MinZoom = 0.15f; private const float MaxZoom = 3f; private const float ZoomSpeed = 0.1f; private readonly MapTextureService _mapService; private readonly IDataManager _dataManager; private readonly QuestRegistry _questRegistry; private readonly QuestData _questData; private readonly UiUtils _uiUtils; private readonly TerritoryData _territoryData; private readonly QuestFunctions _questFunctions; private readonly QuestTooltipComponent _questTooltipComponent; private readonly IGameGui _gameGui; private readonly IClientState _clientState; private Vector2 _offset = Vector2.Zero; private float _zoom = 0.5f; private bool _isDragging; private Vector2 _lastMousePos; private Vector2 _dragStartPos; private int _selectedZoneIndex; private int _selectedStatusFilter; private bool _showUnobtainable; private readonly string[] _statusFilterNames = new string[5] { "All", "Available", "Active", "Complete", "Locked" }; private List _zones = new List(); private string[] _zoneItems = Array.Empty(); private List _currentMarkers = new List(); private Dictionary> _questsByTerritory = new Dictionary>(); private bool _needsRebuild = true; private long _lastStyleRefresh; private const long StyleRefreshIntervalMs = 3000L; private QuestMarker? _selectedMarker; private bool _centerOnMarker; private string _zoneSearchText = string.Empty; public Action? SelectTabAction { get; set; } public QuestChainComponent? QuestChainComponent { get; set; } public QuestJournalUtils? QuestJournalUtils { get; set; } public QuestMapComponent(ITextureProvider textureProvider, IDataManager dataManager, QuestRegistry questRegistry, QuestData questData, UiUtils uiUtils, TerritoryData territoryData, QuestFunctions questFunctions, QuestTooltipComponent questTooltipComponent, IGameGui gameGui, IClientState clientState, ILoggerFactory loggerFactory) { _mapService = new MapTextureService(textureProvider, dataManager, loggerFactory.CreateLogger()); _dataManager = dataManager; _questRegistry = questRegistry; _questData = questData; _uiUtils = uiUtils; _territoryData = territoryData; _questFunctions = questFunctions; _questTooltipComponent = questTooltipComponent; _gameGui = gameGui; _clientState = clientState; } public void DrawQuestMap() { if (_needsRebuild) { RebuildZoneList(); _needsRebuild = false; } DrawFilters(); ImGui.Separator(); if (_zones.Count == 0) { UiThemeUtils.EmptyState(FontAwesomeIcon.Map, "No quest zones available."); return; } ZoneEntry zone = _zones[_selectedZoneIndex]; DrawMapCanvas(zone); } private void DrawFilters() { ImGuiStylePtr style = ImGui.GetStyle(); float num = 100f * ImGui.GetIO().FontGlobalScale; float num2 = ImGui.CalcTextSize("My Zone").X + style.FramePadding.X * 2f; float num3 = ImGui.GetFrameHeight() + style.ItemInnerSpacing.X + ImGui.CalcTextSize("Unobtainable").X; float num4 = num + num2 + num3 + style.ItemSpacing.X * 4f; string previewOverride = ((_zones.Count > 0 && _selectedZoneIndex < _zones.Count) ? _zones[_selectedZoneIndex].DisplayName : "No zones"); if (UiThemeUtils.SearchableCombo("##Zone", ref _selectedZoneIndex, _zoneItems, ref _zoneSearchText, 0f - num4, previewOverride)) { OnZoneChanged(); } ImGui.SameLine(); ImGui.SetNextItemWidth(100f * ImGui.GetIO().FontGlobalScale); if (UiThemeUtils.ThemedCombo("##Status", ref _selectedStatusFilter, _statusFilterNames, _statusFilterNames.Length)) { RebuildMarkers(); } ImGui.SameLine(); if (UiThemeUtils.PillButton("My Zone", selected: false)) { uint currentTerritory = _clientState.TerritoryType; int num5 = _zones.FindIndex((ZoneEntry z) => z.TerritoryId == currentTerritory); if (num5 >= 0) { _selectedZoneIndex = num5; OnZoneChanged(); } } if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Jump to your current zone"); } ImGui.SameLine(); if (UiThemeUtils.WrappedCheckbox("Unobtainable", ref _showUnobtainable)) { RebuildMarkers(); } } private static void DrawMapLegend(ImDrawListPtr drawList, Vector2 canvasPos, float canvasWidth) { (Vector4, string)[] array = new(Vector4, string)[4] { (UiThemeUtils.StatusAvailable, "Available"), (UiThemeUtils.StatusComplete, "Complete"), (UiThemeUtils.StatusLocked, "Locked"), (UiThemeUtils.StatusUnobtainable, "Unobtainable") }; float num = 0f; (Vector4, string)[] array2 = array; for (int i = 0; i < array2.Length; i++) { string item = array2[i].Item2; num += 14f + ImGui.CalcTextSize(item).X + 14f; } num -= 14f; float num2 = canvasPos.X + canvasWidth - num - 8f; float num3 = canvasPos.Y + 8f; float textLineHeight = ImGui.GetTextLineHeight(); Vector2 pMin = new Vector2(num2 - 8f, num3 - 2f); Vector2 pMax = new Vector2(canvasPos.X + canvasWidth, num3 + textLineHeight + 2f); Vector4 playButtonText = UiThemeUtils.PlayButtonText; playButtonText.W = 0.75f; drawList.AddRectFilled(pMin, pMax, ImGui.ColorConvertFloat4ToU32(playButtonText), 3f); float num4 = num2; array2 = array; for (int i = 0; i < array2.Length; i++) { (Vector4, string) tuple = array2[i]; Vector4 item2 = tuple.Item1; string item3 = tuple.Item2; float y = num3 + textLineHeight * 0.5f; drawList.AddCircleFilled(new Vector2(num4 + 4f, y), 4f, ImGui.ColorConvertFloat4ToU32(item2), 12); num4 += 14f; drawList.AddText(new Vector2(num4, num3), ImGui.ColorConvertFloat4ToU32(UiThemeUtils.DimTextColor), item3); num4 += ImGui.CalcTextSize(item3).X + 14f; } } private void DrawMapCanvas(ZoneEntry zone) { long tickCount = Environment.TickCount64; if (tickCount - _lastStyleRefresh > 3000) { _lastStyleRefresh = tickCount; RefreshMarkerStyles(); } Vector2 contentRegionAvail = ImGui.GetContentRegionAvail(); Vector2 vector = new Vector2(contentRegionAvail.X, MathF.Max(contentRegionAvail.Y, 100f)); Vector2 cursorScreenPos = ImGui.GetCursorScreenPos(); ImGui.InvisibleButton("##mapCanvas", vector); bool flag = ImGui.IsItemHovered(); ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList(); Vector2 vector2 = cursorScreenPos + vector; windowDrawList.AddRectFilled(cursorScreenPos, vector2, ImGui.ColorConvertFloat4ToU32(UiThemeUtils.PlayButtonText)); windowDrawList.PushClipRect(cursorScreenPos, vector2, intersectWithCurrentClipRect: true); HandlePanZoom(cursorScreenPos, vector, flag); if (_centerOnMarker && _selectedMarker != null) { _centerOnMarker = false; _zoom = 1f; Vector2 vector3 = (_selectedMarker.MapPixelPos - new Vector2(1024f)) * _zoom; _offset = -vector3; } _mapService.LoadForTerritory(zone.TerritoryId); IDalamudTextureWrap textureWrap = _mapService.GetTextureWrap(); if (textureWrap != null) { Vector2 vector4 = new Vector2(2048f * _zoom, 2048f * _zoom); Vector2 vector5 = cursorScreenPos + _offset + vector * 0.5f - vector4 * 0.5f; windowDrawList.AddImage(textureWrap.Handle, vector5, vector5 + vector4); } else { string text = (_mapService.IsSearching ? "Loading map..." : "Map not available for this zone."); Vector2 pos = cursorScreenPos + vector * 0.5f - ImGui.CalcTextSize(text) * 0.5f; windowDrawList.AddText(pos, ImGui.ColorConvertFloat4ToU32(UiThemeUtils.DimTextColor), text); } Vector2 vector6 = ScreenToMapPixel(cursorScreenPos, cursorScreenPos, vector); Vector2 vector7 = ScreenToMapPixel(vector2, cursorScreenPos, vector); float num = 12f; QuestMarker questMarker = null; foreach (QuestMarker currentMarker in _currentMarkers) { if (!(currentMarker.MapPixelPos.X < vector6.X - num) && !(currentMarker.MapPixelPos.X > vector7.X + num) && !(currentMarker.MapPixelPos.Y < vector6.Y - num) && !(currentMarker.MapPixelPos.Y > vector7.Y + num)) { Vector2 vector8 = MapPixelToScreen(currentMarker.MapPixelPos, cursorScreenPos, vector); uint col = ImGui.ColorConvertFloat4ToU32(currentMarker.Color); uint col2 = ImGui.ColorConvertFloat4ToU32(new Vector4(0f, 0f, 0f, 0.8f)); float num2 = 6f * Math.Max(_zoom, 0.5f); windowDrawList.AddCircleFilled(vector8, num2 + 1f, col2, 16); windowDrawList.AddCircleFilled(vector8, num2, col, 16); if (flag && !_isDragging && Vector2.Distance(ImGui.GetMousePos(), vector8) <= 8f * Math.Max(_zoom, 0.5f)) { questMarker = currentMarker; } } } DrawMapLegend(windowDrawList, cursorScreenPos, vector.X); if (_selectedMarker != null) { Vector2 center = MapPixelToScreen(_selectedMarker.MapPixelPos, cursorScreenPos, vector); if (center.X >= cursorScreenPos.X && center.X <= vector2.X && center.Y >= cursorScreenPos.Y && center.Y <= vector2.Y) { float num3 = 6f * Math.Max(_zoom, 0.5f); windowDrawList.AddCircle(center, num3 + 3f, ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f)), 16, 2f); } } bool flag2 = false; bool flag3 = false; Vector2 mousePos; bool flag4; uint col3; float num5; string text5; float num7; Vector2 pMin3; Vector2 pMax3; int num9; Vector4 input; float num6; if (_selectedMarker != null) { Vector4 color = _selectedMarker.Color; FontAwesomeIcon icon = _selectedMarker.Icon; string status = _selectedMarker.Status; string text2 = UiThemeUtils.CleanSeString(_selectedMarker.QuestName); mousePos = ImGui.GetMousePos(); flag4 = ImGui.IsMouseReleased(ImGuiMouseButton.Left); Vector2 vector9 = new Vector2(cursorScreenPos.X, vector2.Y - 34f); Vector2 vector10 = vector2; Vector2 pMin = vector9; Vector2 pMax = vector10; Vector4 playButtonText = UiThemeUtils.PlayButtonText; playButtonText.W = 0.9f; windowDrawList.AddRectFilled(pMin, pMax, ImGui.ColorConvertFloat4ToU32(playButtonText)); Vector2 pMin2 = vector9; Vector2 pMax2 = new Vector2(vector10.X, vector9.Y + 1f); playButtonText = UiThemeUtils.AccentColor; playButtonText.W = 0.4f; windowDrawList.AddRectFilled(pMin2, pMax2, ImGui.ColorConvertFloat4ToU32(playButtonText)); float y = vector9.Y + (34f - ImGui.GetTextLineHeight()) * 0.5f; float num4 = vector9.X + 10f; string text3 = icon.ToIconString(); Vector2 vector11; using (ImRaii.PushFont(UiBuilder.IconFont)) { vector11 = ImGui.CalcTextSize(text3); } windowDrawList.AddText(UiBuilder.IconFont, ImGui.GetFontSize(), new Vector2(num4, y), ImGui.ColorConvertFloat4ToU32(color), text3); num4 += vector11.X + 6f; col3 = ImGui.ColorConvertFloat4ToU32(UiThemeUtils.PrimaryTextColor); windowDrawList.AddText(new Vector2(num4, y), col3, text2); num4 += ImGui.CalcTextSize(text2).X + 10f; Vector2 pos2 = new Vector2(num4, y); uint col4 = ImGui.ColorConvertFloat4ToU32(color); ImU8String text4 = new ImU8String(2, 1); text4.AppendLiteral("["); text4.AppendFormatted(status); text4.AppendLiteral("]"); windowDrawList.AddText(pos2, col4, text4); num5 = vector9.Y + (34f - ImGui.GetTextLineHeight() - 8f) * 0.5f; num6 = vector10.X - 10f; text5 = "Open on Map"; Vector2 vector12 = ImGui.CalcTextSize(text5); num7 = vector12.X + 16f; float num8 = vector12.Y + 8f; pMin3 = new Vector2(num6 - num7, num5); pMax3 = new Vector2(num6, num5 + num8); if (mousePos.X >= pMin3.X && mousePos.X <= pMax3.X && mousePos.Y >= pMin3.Y) { num9 = ((mousePos.Y <= pMax3.Y) ? 1 : 0); if (num9 != 0) { playButtonText = UiThemeUtils.AccentColor; playButtonText.W = 0.25f; input = playButtonText; goto IL_06fa; } } else { num9 = 0; } input = UiThemeUtils.AccentDimColor; goto IL_06fa; } goto IL_085c; IL_06fa: windowDrawList.AddRectFilled(col: ImGui.ColorConvertFloat4ToU32(input), pMin: pMin3, pMax: pMax3, rounding: 3f); windowDrawList.AddText(new Vector2(pMin3.X + 8f, pMin3.Y + 4f), col3, text5); if (((uint)num9 & (flag4 ? 1u : 0u)) != 0) { flag3 = true; } num6 -= num7 + 6f; string text6 = "Quest Chain"; Vector2 vector13 = ImGui.CalcTextSize(text6); float num10 = vector13.X + 16f; float num11 = vector13.Y + 8f; Vector2 pMin4 = new Vector2(num6 - num10, num5); Vector2 pMax4 = new Vector2(num6, num5 + num11); int num12; Vector4 input2; if (mousePos.X >= pMin4.X && mousePos.X <= pMax4.X && mousePos.Y >= pMin4.Y) { num12 = ((mousePos.Y <= pMax4.Y) ? 1 : 0); if (num12 != 0) { Vector4 playButtonText = UiThemeUtils.AccentColor; playButtonText.W = 0.25f; input2 = playButtonText; goto IL_080c; } } else { num12 = 0; } input2 = UiThemeUtils.AccentDimColor; goto IL_080c; IL_080c: windowDrawList.AddRectFilled(col: ImGui.ColorConvertFloat4ToU32(input2), pMin: pMin4, pMax: pMax4, rounding: 3f); windowDrawList.AddText(new Vector2(pMin4.X + 8f, pMin4.Y + 4f), col3, text6); if (((uint)num12 & (flag4 ? 1u : 0u)) != 0) { flag2 = true; } goto IL_085c; IL_085c: windowDrawList.PopClipRect(); if (flag3 && _selectedMarker != null && _selectedMarker.WorldPos != Vector3.Zero) { try { ZoneEntry zoneEntry = _zones[_selectedZoneIndex]; TerritoryType? rowOrDefault = _dataManager.GetExcelSheet().GetRowOrDefault(zoneEntry.TerritoryId); if (rowOrDefault.HasValue) { Map? valueNullable = rowOrDefault.Value.Map.ValueNullable; if (valueNullable.HasValue) { Vector2 vector14 = MapUtil.WorldToMap(new Vector2(_selectedMarker.WorldPos.X, _selectedMarker.WorldPos.Z), valueNullable.Value); MapLinkPayload mapLink = new MapLinkPayload(zoneEntry.TerritoryId, valueNullable.Value.RowId, vector14.X, vector14.Y); _gameGui.OpenMapWithMapLink(mapLink); } } } catch { } } if (flag2 && _selectedMarker != null) { QuestChainComponent?.SelectQuest(_selectedMarker.QuestId); SelectTabAction?.Invoke("Quest Chain"); } if (questMarker != null && _questData.TryGetQuestInfo(questMarker.QuestId, out IQuestInfo questInfo)) { _questTooltipComponent.Draw(questInfo); } bool flag5 = _isDragging && Vector2.Distance(ImGui.GetMousePos(), _dragStartPos) > 4f; if (ImGui.IsMouseReleased(ImGuiMouseButton.Left) && !flag5) { if (questMarker != null) { _selectedMarker = questMarker; } else if (flag) { _selectedMarker = null; } } if (questMarker != null && ImGui.IsMouseReleased(ImGuiMouseButton.Right) && !flag5) { _selectedMarker = questMarker; ImU8String strId = new ImU8String(12, 1); strId.AppendLiteral("##QuestPopup"); strId.AppendFormatted(questMarker.QuestId); ImGui.OpenPopup(strId); } if (_selectedMarker != null && _questData.TryGetQuestInfo(_selectedMarker.QuestId, out IQuestInfo questInfo2)) { Questionable.Model.Quest quest = null; _questRegistry.TryGetQuest(_selectedMarker.QuestId, out quest); QuestJournalUtils?.ShowContextMenu(questInfo2, quest, "QuestMapComponent"); } } private void HandlePanZoom(Vector2 canvasPos, Vector2 canvasSize, bool isHovered) { ImGuiIOPtr iO = ImGui.GetIO(); if (isHovered && MathF.Abs(iO.MouseWheel) > 0.01f) { float zoom = _zoom; _zoom = Math.Clamp(_zoom + iO.MouseWheel * 0.1f, 0.15f, 3f); Vector2 vector = ImGui.GetMousePos() - canvasPos - canvasSize * 0.5f - _offset; _offset -= vector * (_zoom / zoom - 1f); } bool flag = ImGui.IsMouseDown(ImGuiMouseButton.Left) || ImGui.IsMouseDown(ImGuiMouseButton.Right) || ImGui.IsMouseDown(ImGuiMouseButton.Middle); if (isHovered && flag) { if (!_isDragging) { _isDragging = true; _lastMousePos = ImGui.GetMousePos(); _dragStartPos = _lastMousePos; } else { Vector2 mousePos = ImGui.GetMousePos(); Vector2 vector2 = mousePos - _lastMousePos; _offset += vector2; _lastMousePos = mousePos; } } else { _isDragging = false; } } private Vector2 MapPixelToScreen(Vector2 mapPixel, Vector2 canvasPos, Vector2 canvasSize) { Vector2 vector = (mapPixel - new Vector2(1024f)) * _zoom; return canvasPos + canvasSize * 0.5f + _offset + vector; } private Vector2 ScreenToMapPixel(Vector2 screenPos, Vector2 canvasPos, Vector2 canvasSize) { return (screenPos - canvasPos - canvasSize * 0.5f - _offset) / _zoom + new Vector2(1024f); } private static float WorldToMapPixel(float worldCoord, float offset, float sizeFactor) { return 2048f * ((worldCoord + offset) * sizeFactor / 100f + 1024f) / 2048f; } private void RebuildZoneList() { HashSet hashSet = new HashSet(); Dictionary territoryQuests = new Dictionary(); _questsByTerritory.Clear(); foreach (Questionable.Model.Quest allQuest in _questRegistry.AllQuests) { if (allQuest.Root.Disabled) { continue; } QuestSequence questSequence = allQuest.FindSequence(0); if (questSequence == null || questSequence.Steps.Count == 0) { continue; } QuestStep questStep = questSequence.Steps[0]; if (questStep.TerritoryId != 0 && questStep.Position.HasValue) { hashSet.Add(questStep.TerritoryId); territoryQuests[questStep.TerritoryId] = territoryQuests.GetValueOrDefault(questStep.TerritoryId) + 1; if (!_questsByTerritory.TryGetValue(questStep.TerritoryId, out List<(ElementId, string, Vector3, bool)> value)) { value = new List<(ElementId, string, Vector3, bool)>(); _questsByTerritory[questStep.TerritoryId] = value; } value.Add((allQuest.Id, allQuest.Info.Name, questStep.Position.Value, true)); } } HashSet hashSet2 = new HashSet(_questRegistry.AllQuests.Select((Questionable.Model.Quest q) => q.Id)); try { ExcelSheet excelSheet = _dataManager.GetExcelSheet(); foreach (var (elementId2, questInfo2) in _questData.AllQuests) { if (!(elementId2 is QuestId questId) || hashSet2.Contains(elementId2)) { continue; } try { Lumina.Excel.Sheets.Quest? rowOrDefault = excelSheet.GetRowOrDefault((uint)(questId.Value + 65536)); if (!rowOrDefault.HasValue) { continue; } Level? valueNullable = rowOrDefault.Value.IssuerLocation.ValueNullable; if (!valueNullable.HasValue) { continue; } ushort num = (ushort)valueNullable.Value.Territory.RowId; if (num != 0) { hashSet.Add(num); territoryQuests[num] = territoryQuests.GetValueOrDefault(num) + 1; if (!_questsByTerritory.TryGetValue(num, out List<(ElementId, string, Vector3, bool)> value2)) { value2 = new List<(ElementId, string, Vector3, bool)>(); _questsByTerritory[num] = value2; } Vector3 item = new Vector3(valueNullable.Value.X, valueNullable.Value.Y, valueNullable.Value.Z); value2.Add((questId, questInfo2.Name, item, false)); } } catch { } } } catch { } _zones = (from z in hashSet.Select(delegate(ushort id) { string name = _territoryData.GetName(id); string mapIdForTerritory = GetMapIdForTerritory(id); int num3 = territoryQuests[id]; string text = $"({num3} quest{((num3 != 1) ? "s" : "")})"; string displayName = ((name != null) ? (name + " " + text) : $"Zone {id} {text}"); return new ZoneEntry(id, name ?? $"Zone {id}", displayName, mapIdForTerritory); }) orderby z.Name select z).ToList(); _zoneItems = _zones.Select((ZoneEntry z) => z.DisplayName).ToArray(); if (_zones.Count > 0) { uint currentTerritory = _clientState.TerritoryType; int num2 = _zones.FindIndex((ZoneEntry z) => z.TerritoryId == currentTerritory); _selectedZoneIndex = ((num2 >= 0) ? num2 : Math.Clamp(_selectedZoneIndex, 0, _zones.Count - 1)); RebuildMarkers(); } } private string GetMapIdForTerritory(ushort territoryId) { try { RowRef map = _dataManager.GetExcelSheet().GetRow(territoryId).Map; if (map.RowId != 0) { return map.Value.Id.ExtractText(); } } catch { } return string.Empty; } private Map? LoadMapRow(ushort territoryId) { try { TerritoryType row = _dataManager.GetExcelSheet().GetRow(territoryId); return (row.Map.RowId != 0) ? new Map?(row.Map.Value) : ((Map?)null); } catch { return null; } } private void OnZoneChanged() { _offset = Vector2.Zero; _zoom = 0.5f; _selectedMarker = null; RebuildMarkers(); } private void RebuildMarkers() { _currentMarkers.Clear(); if (_zones.Count == 0 || _selectedZoneIndex >= _zones.Count) { return; } ZoneEntry zoneEntry = _zones[_selectedZoneIndex]; if (!_questsByTerritory.TryGetValue(zoneEntry.TerritoryId, out List<(ElementId, string, Vector3, bool)> value)) { return; } Map? map = LoadMapRow(zoneEntry.TerritoryId); foreach (var (elementId, questName, worldPos, _) in value) { if ((_showUnobtainable || !_questFunctions.IsQuestUnobtainable(elementId)) && PassesStatusFilter(elementId)) { Vector2 mapPixelPos = (map.HasValue ? new Vector2(WorldToMapPixel(worldPos.X, map.Value.OffsetX, (int)map.Value.SizeFactor), WorldToMapPixel(worldPos.Z, map.Value.OffsetY, (int)map.Value.SizeFactor)) : new Vector2(1024f + worldPos.X, 1024f + worldPos.Z)); _currentMarkers.Add(new QuestMarker(elementId, questName, mapPixelPos, worldPos)); } } RefreshMarkerStyles(); } private void RefreshMarkerStyles() { foreach (QuestMarker currentMarker in _currentMarkers) { (Vector4 Color, FontAwesomeIcon Icon, string Status) questStyle = _uiUtils.GetQuestStyle(currentMarker.QuestId); Vector4 item = questStyle.Color; FontAwesomeIcon item2 = questStyle.Icon; string item3 = questStyle.Status; currentMarker.Color = item; currentMarker.Icon = item2; currentMarker.Status = item3; } if (_selectedMarker != null && !_currentMarkers.Contains(_selectedMarker)) { var (color, icon, status) = _uiUtils.GetQuestStyle(_selectedMarker.QuestId); _selectedMarker.Color = color; _selectedMarker.Icon = icon; _selectedMarker.Status = status; } _lastStyleRefresh = Environment.TickCount64; } private bool PassesStatusFilter(ElementId questId) { return _selectedStatusFilter switch { 1 => !_questFunctions.IsQuestLocked(questId) && _questFunctions.IsReadyToAcceptQuest(questId) && !_questFunctions.IsQuestAcceptedOrComplete(questId), 2 => _questFunctions.IsQuestAccepted(questId), 3 => _questFunctions.IsQuestComplete(questId), 4 => _questFunctions.IsQuestLocked(questId) || !_questFunctions.IsReadyToAcceptQuest(questId), _ => true, }; } public void SelectZone(ushort territoryId) { if (_needsRebuild) { RebuildZoneList(); _needsRebuild = false; } for (int i = 0; i < _zones.Count; i++) { if (_zones[i].TerritoryId == territoryId) { _selectedZoneIndex = i; OnZoneChanged(); break; } } } public void SelectQuest(ElementId questId, ushort territoryId) { SelectZone(territoryId); foreach (QuestMarker currentMarker in _currentMarkers) { if (currentMarker.QuestId == questId) { _selectedMarker = currentMarker; _centerOnMarker = true; return; } } if (!_questData.TryGetQuestInfo(questId, out IQuestInfo questInfo)) { return; } Vector2 mapPixelPos = new Vector2(1024f); Vector3 worldPos = Vector3.Zero; if (questId is QuestId questId2) { try { Lumina.Excel.Sheets.Quest? rowOrDefault = _dataManager.GetExcelSheet().GetRowOrDefault((uint)(questId2.Value + 65536)); if (rowOrDefault.HasValue) { Level? valueNullable = rowOrDefault.Value.IssuerLocation.ValueNullable; if (valueNullable.HasValue) { worldPos = new Vector3(valueNullable.Value.X, valueNullable.Value.Y, valueNullable.Value.Z); Map? map = LoadMapRow(territoryId); if (map.HasValue) { mapPixelPos = new Vector2(WorldToMapPixel(worldPos.X, map.Value.OffsetX, (int)map.Value.SizeFactor), WorldToMapPixel(worldPos.Z, map.Value.OffsetY, (int)map.Value.SizeFactor)); } } } } catch { } } QuestMarker questMarker = new QuestMarker(questId, questInfo.Name, mapPixelPos, worldPos); (Vector4 Color, FontAwesomeIcon Icon, string Status) questStyle = _uiUtils.GetQuestStyle(questId); Vector4 item = questStyle.Color; FontAwesomeIcon item2 = questStyle.Icon; string item3 = questStyle.Status; questMarker.Color = item; questMarker.Icon = item2; questMarker.Status = item3; _selectedMarker = questMarker; _centerOnMarker = true; } public void MarkDirty() { _needsRebuild = true; _selectedZoneIndex = -1; _selectedMarker = null; _offset = Vector2.Zero; _zoom = 0.5f; } }