qstbak/Questionable/Questionable.Windows.JournalComponents/AttunementJournalComponent.cs
2026-08-19 13:19:57 +10:00

1043 lines
34 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Movement;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Navigation;
using Questionable.Windows.QuestComponents;
using SmartNav;
using SmartNav.Data;
using SmartNav.Model;
using SmartNav.Model.Converter;
namespace Questionable.Windows.JournalComponents;
internal sealed class AttunementJournalComponent
{
private sealed record AetheryteEntry(EAetheryteLocation Location, string Name, ushort TerritoryId);
private sealed record CityGroup(string CityName, ImmutableList<AetheryteEntry> Shards);
private sealed record ZoneCurrentGroup(ushort TerritoryId, string ZoneName, ImmutableList<CurrentEntry> Currents);
private sealed record CurrentEntry(uint AetherCurrentId, bool IsQuestGated, uint QuestId, string QuestName);
private sealed record ExpansionGroup<T>(string ExpansionName, ImmutableList<T> Items, byte SortOrder = 0);
private enum AttuneRowKind : byte
{
Header,
AetheryteLeaf,
CurrentLeaf
}
private enum BatchKind : byte
{
None,
AethernetCity,
CurrentZone
}
private sealed class FlatRow
{
public AttuneRowKind Kind { get; init; }
public int Depth { get; init; }
public string Key { get; init; } = string.Empty;
public string Label { get; init; } = string.Empty;
public string CountText { get; init; } = string.Empty;
public bool CountComplete { get; init; }
public bool CountEmpty { get; init; }
public BatchKind Batch { get; init; }
public CityGroup? City { get; init; }
public ZoneCurrentGroup? Zone { get; init; }
public AetheryteEntry? Aetheryte { get; init; }
public bool IsAethernet { get; init; }
public CurrentEntry? Current { get; init; }
}
private readonly AetheryteData _aetheryteData;
private readonly AetherCurrentData _aetherCurrentData;
private readonly AetheryteFunctions _aetheryteFunctions;
private readonly GameFunctions _gameFunctions;
private readonly TerritoryData _territoryData;
private readonly NavRouter _navRouter;
private readonly PlayerNavStateBuilder _playerNavStateBuilder;
private readonly RouteInstructionBuilder _instructionBuilder;
private readonly SmartNavTaskMapper _taskMapper;
private readonly MovementController _movementController;
private readonly AttunementController _attunementController;
private readonly QuestController _questController;
private readonly FateController _fateController;
private readonly SeasonalDutyController _seasonalDutyController;
private readonly CustomDeliveryController _customDeliveryController;
private readonly IClientState _clientState;
private readonly IObjectTable _objectTable;
private readonly UiUtils _uiUtils;
private readonly QuestTooltipComponent _questTooltipComponent;
private readonly QuestData _questData;
private readonly QuestRegistry _questRegistry;
private readonly QuestJournalUtils _questJournalUtils;
private readonly IDataManager _dataManager;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ILogger<AttunementJournalComponent> _logger;
private static readonly Dictionary<EAetheryteLocation, uint> FirmamentPlaceNameIds = new Dictionary<EAetheryteLocation, uint>
{
{
EAetheryteLocation.FirmamentMendicantsCourt,
3436u
},
{
EAetheryteLocation.FirmamentMattock,
3473u
},
{
EAetheryteLocation.FirmamentNewNest,
3475u
},
{
EAetheryteLocation.FirmanentSaintRoellesDais,
3474u
},
{
EAetheryteLocation.FirmamentFeatherfall,
3525u
},
{
EAetheryteLocation.FirmamentHoarfrostHall,
3528u
},
{
EAetheryteLocation.FirmamentWesternRisensongQuarter,
3646u
},
{
EAetheryteLocation.FirmamentEasternRisensongQuarter,
3645u
}
};
private ImmutableList<ExpansionGroup<AetheryteEntry>> _aetherytesByExpansion = ImmutableList.Create(default(ReadOnlySpan<ExpansionGroup<AetheryteEntry>>));
private ImmutableList<CityGroup> _aethernetByCity = ImmutableList.Create(default(ReadOnlySpan<CityGroup>));
private ImmutableList<ExpansionGroup<ZoneCurrentGroup>> _currentsByExpansion = ImmutableList.Create(default(ReadOnlySpan<ExpansionGroup<ZoneCurrentGroup>>));
private int _aetheryteUnlocked;
private int _aetheryteTotal;
private int _aethernetUnlocked;
private int _aethernetTotal;
private int _currentUnlocked;
private int _currentTotal;
private Dictionary<EAetheryteLocation, bool> _aetheryteUnlockCache = new Dictionary<EAetheryteLocation, bool>();
private Dictionary<uint, bool> _currentUnlockCache = new Dictionary<uint, bool>();
private Dictionary<byte, int> _aetheryteExpansionUnlockedCounts = new Dictionary<byte, int>();
private Dictionary<string, int> _aethernetCityUnlockedCounts = new Dictionary<string, int>();
private Dictionary<ushort, int> _currentZoneUnlockedCounts = new Dictionary<ushort, int>();
private long _lastStatusRefresh;
private const long StatusRefreshIntervalMs = 3000L;
private string _searchText = string.Empty;
private bool _showUnattunedOnly;
private List<FlatRow> _flatRows = new List<FlatRow>();
private readonly HashSet<string> _expandedGroups = new HashSet<string>();
private readonly List<(string Name, ushort TerritoryId, Vector3 Position, ITask AttuneTask)> _pendingBatchTargets = new List<(string, ushort, Vector3, ITask)>();
private string? _batchName;
private const string SectionAetherytes = "Aetherytes";
private const string SectionAethernet = "Aethernet Shards";
private const string SectionCurrents = "Aether Currents";
private static readonly int CountPadWidth = 9999.ToString(CultureInfo.CurrentCulture).Length;
public AttunementJournalComponent(AetheryteData aetheryteData, AetherCurrentData aetherCurrentData, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, TerritoryData territoryData, NavRouter navRouter, PlayerNavStateBuilder playerNavStateBuilder, RouteInstructionBuilder instructionBuilder, SmartNavTaskMapper taskMapper, MovementController movementController, AttunementController attunementController, QuestController questController, FateController fateController, SeasonalDutyController seasonalDutyController, CustomDeliveryController customDeliveryController, UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, QuestData questData, QuestRegistry questRegistry, QuestJournalUtils questJournalUtils, IClientState clientState, IObjectTable objectTable, IDataManager dataManager, IDalamudPluginInterface pluginInterface, ILogger<AttunementJournalComponent> logger)
{
_aetheryteData = aetheryteData;
_aetherCurrentData = aetherCurrentData;
_aetheryteFunctions = aetheryteFunctions;
_gameFunctions = gameFunctions;
_territoryData = territoryData;
_navRouter = navRouter;
_playerNavStateBuilder = playerNavStateBuilder;
_instructionBuilder = instructionBuilder;
_taskMapper = taskMapper;
_movementController = movementController;
_attunementController = attunementController;
_questController = questController;
_fateController = fateController;
_seasonalDutyController = seasonalDutyController;
_customDeliveryController = customDeliveryController;
_uiUtils = uiUtils;
_questTooltipComponent = questTooltipComponent;
_questData = questData;
_questRegistry = questRegistry;
_questJournalUtils = questJournalUtils;
_clientState = clientState;
_objectTable = objectTable;
_dataManager = dataManager;
_pluginInterface = pluginInterface;
_logger = logger;
InitializeData();
}
private void InitializeData()
{
ExcelSheet<Lumina.Excel.Sheets.Aetheryte> aetheryteSheet = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Aetheryte>();
ExcelSheet<TerritoryType> territorySheet = _dataManager.GetExcelSheet<TerritoryType>();
ExcelSheet<Lumina.Excel.Sheets.Quest> questSheet = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>();
List<AetheryteEntry> list = new List<AetheryteEntry>();
List<AetheryteEntry> list2 = new List<AetheryteEntry>();
ExcelSheet<PlaceName> excelSheet = _dataManager.GetExcelSheet<PlaceName>();
foreach (EAetheryteLocation key2 in _aetheryteData.Locations.Keys)
{
ushort valueOrDefault = _aetheryteData.TerritoryIds.GetValueOrDefault(key2);
Lumina.Excel.Sheets.Aetheryte? rowOrDefault = aetheryteSheet.GetRowOrDefault((uint)key2);
if (!rowOrDefault.HasValue)
{
uint value;
string name = ((!FirmamentPlaceNameIds.TryGetValue(key2, out value)) ? key2.ToString() : (excelSheet.GetRowOrDefault(value)?.Name.ToString() ?? key2.ToString()));
bool num = _aetheryteData.AethernetGroups.ContainsKey(key2) && !AetheryteConverter.IsLargeAetheryte(key2);
AetheryteEntry item = new AetheryteEntry(key2, name, valueOrDefault);
if (num)
{
list2.Add(item);
}
else
{
list.Add(item);
}
}
else if (AetheryteConverter.IsLargeAetheryte(key2))
{
string name = rowOrDefault.Value.PlaceName.ValueNullable?.Name.ToString() ?? key2.ToString();
list.Add(new AetheryteEntry(key2, name, valueOrDefault));
}
else
{
string name = rowOrDefault.Value.AethernetName.ValueNullable?.Name.ToString() ?? key2.ToString();
list2.Add(new AetheryteEntry(key2, name, valueOrDefault));
}
}
_aetherytesByExpansion = (from g in list.GroupBy(delegate(AetheryteEntry a)
{
ExVersion? exVersion = territorySheet.GetRowOrDefault(a.TerritoryId)?.ExVersion.ValueNullable;
return (Name: exVersion?.Name.ToString() ?? "A Realm Reborn", SortOrder: (byte)(exVersion?.RowId ?? 0));
})
orderby g.Key.SortOrder
select new ExpansionGroup<AetheryteEntry>(g.Key.Name, g.OrderBy((AetheryteEntry a) => a.Name).ToImmutableList(), g.Key.SortOrder)).ToImmutableList();
_aetheryteTotal = _aetherytesByExpansion.Sum((ExpansionGroup<AetheryteEntry> g) => g.Items.Count);
_aethernetByCity = (from g in (from a in list2
group a by _aetheryteData.AethernetGroups.GetValueOrDefault(a.Location) into g
where g.Key != 0
select g).Select(delegate(IGrouping<ushort, AetheryteEntry> g)
{
ushort groupId = g.Key;
string text = (from kv in _aetheryteData.AethernetGroups
where kv.Value == groupId && AetheryteConverter.IsLargeAetheryte(kv.Key)
select aetheryteSheet.GetRowOrDefault((uint)kv.Key)?.PlaceName.ValueNullable?.Name.ToString()).FirstOrDefault();
if (string.IsNullOrEmpty(text))
{
ushort territoryId = g.First().TerritoryId;
text = territorySheet.GetRowOrDefault(territoryId)?.PlaceName.ValueNullable?.Name.ToString() ?? $"Group {groupId}";
}
return new CityGroup(text, g.OrderBy((AetheryteEntry a) => a.Name).ToImmutableList());
})
orderby g.CityName
select g).ToImmutableList();
_aethernetTotal = _aethernetByCity.Sum((CityGroup g) => g.Shards.Count);
_currentsByExpansion = (from x in _aetherCurrentData.AllCurrentsByTerritory.Select<KeyValuePair<ushort, ImmutableList<AetherCurrentInfo>>, (ushort, string, string, byte, ImmutableList<CurrentEntry>)>(delegate(KeyValuePair<ushort, ImmutableList<AetherCurrentInfo>> kv)
{
ushort key = kv.Key;
TerritoryType? rowOrDefault2 = territorySheet.GetRowOrDefault(key);
string item2 = rowOrDefault2?.PlaceName.ValueNullable?.Name.ToString() ?? key.ToString(CultureInfo.InvariantCulture);
ExVersion? exVersion = rowOrDefault2?.ExVersion.ValueNullable;
byte item3 = (byte)(exVersion?.RowId ?? 0);
string item4 = exVersion?.Name.ToString() ?? "A Realm Reborn";
ImmutableList<CurrentEntry> item5 = kv.Value.Select(delegate(AetherCurrentInfo info)
{
string questName = string.Empty;
if (info.IsQuestGated && info.QuestId != 0)
{
questName = questSheet.GetRowOrDefault(info.QuestId)?.Name.ToString() ?? string.Empty;
}
return new CurrentEntry(info.AetherCurrentId, info.IsQuestGated, info.QuestId, questName);
}).ToImmutableList();
return (territoryId: key, zoneName: item2, expansionName: item4, sortOrder: item3, currents: item5);
})
where x.sortOrder > 0
group x by (expansionName: x.expansionName, sortOrder: x.sortOrder) into g
orderby g.Key.sortOrder
select new ExpansionGroup<ZoneCurrentGroup>(g.Key.expansionName, (from x in g
orderby x.zoneName
select new ZoneCurrentGroup(x.territoryId, x.zoneName, x.currents)).ToImmutableList(), g.Key.sortOrder)).ToImmutableList();
_currentTotal = _currentsByExpansion.Sum((ExpansionGroup<ZoneCurrentGroup> g) => g.Items.Sum((ZoneCurrentGroup z) => z.Currents.Count));
}
public void RefreshCounts()
{
Dictionary<EAetheryteLocation, bool> dictionary = new Dictionary<EAetheryteLocation, bool>();
Dictionary<byte, int> dictionary2 = new Dictionary<byte, int>();
int num = 0;
foreach (ExpansionGroup<AetheryteEntry> item in _aetherytesByExpansion)
{
int num2 = 0;
foreach (AetheryteEntry item2 in item.Items)
{
bool flag = _aetheryteFunctions.IsAetheryteUnlocked(item2.Location);
dictionary[item2.Location] = flag;
if (flag)
{
num2++;
}
}
dictionary2[item.SortOrder] = num2;
num += num2;
}
Dictionary<string, int> dictionary3 = new Dictionary<string, int>();
int num3 = 0;
foreach (CityGroup item3 in _aethernetByCity)
{
int num4 = 0;
foreach (AetheryteEntry shard in item3.Shards)
{
bool flag2 = _aetheryteFunctions.IsAetheryteUnlocked(shard.Location);
dictionary[shard.Location] = flag2;
if (flag2)
{
num4++;
}
}
dictionary3[item3.CityName] = num4;
num3 += num4;
}
Dictionary<uint, bool> dictionary4 = new Dictionary<uint, bool>();
Dictionary<ushort, int> dictionary5 = new Dictionary<ushort, int>();
int num5 = 0;
foreach (ExpansionGroup<ZoneCurrentGroup> item4 in _currentsByExpansion)
{
foreach (ZoneCurrentGroup item5 in item4.Items)
{
int num6 = 0;
foreach (CurrentEntry current in item5.Currents)
{
bool flag3 = GameFunctions.IsAetherCurrentUnlocked(current.AetherCurrentId);
dictionary4[current.AetherCurrentId] = flag3;
if (flag3)
{
num6++;
}
}
dictionary5[item5.TerritoryId] = num6;
num5 += num6;
}
}
_aetheryteUnlockCache = dictionary;
_currentUnlockCache = dictionary4;
_aetheryteExpansionUnlockedCounts = dictionary2;
_aethernetCityUnlockedCounts = dictionary3;
_currentZoneUnlockedCounts = dictionary5;
_aetheryteUnlocked = num;
_aethernetUnlocked = num3;
_currentUnlocked = num5;
_lastStatusRefresh = Environment.TickCount64;
RebuildFlatRows();
}
public void ClearCounts(int type, int code)
{
_aetheryteUnlocked = (_aethernetUnlocked = (_currentUnlocked = 0));
_aetheryteUnlockCache.Clear();
_currentUnlockCache.Clear();
_aetheryteExpansionUnlockedCounts.Clear();
_aethernetCityUnlockedCounts.Clear();
_currentZoneUnlockedCounts.Clear();
RebuildFlatRows();
}
public void UpdateFilter()
{
}
public void DrawAttunement()
{
if (Environment.TickCount64 - _lastStatusRefresh > 3000)
{
RefreshCounts();
}
if (!_attunementController.IsRunning && _pendingBatchTargets.Count > 0)
{
if (_attunementController.CompletedSuccessfully)
{
(string, ushort, Vector3, ITask) tuple = PopNearestTarget();
string text = _batchName;
if (text == null)
{
(text, _, _, _) = tuple;
}
StartAttunement(text, tuple.Item2, tuple.Item3, tuple.Item4);
}
else
{
_pendingBatchTargets.Clear();
_batchName = null;
}
}
if (_attunementController.IsRunning || _pendingBatchTargets.Count > 0)
{
string text2 = ((_pendingBatchTargets.Count > 0) ? $"Navigating to {_attunementController.CurrentTargetName}... ({_pendingBatchTargets.Count} remaining)" : ("Navigating to " + _attunementController.CurrentTargetName + "..."));
float x;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
x = ImGui.CalcTextSize(FontAwesomeIcon.Stop.ToIconString()).X;
}
float num = x + 6f + ImGui.CalcTextSize("Stop").X + ImGui.GetStyle().FramePadding.X * 2f + ImGui.GetStyle().ItemSpacing.X;
ImGui.TextColored(in UiThemeUtils.StatusActive, UiThemeUtils.TruncateToWidth(text2, ImGui.GetContentRegionAvail().X - num));
ImGui.SameLine();
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Stop, "Stop"))
{
StopAll("User cancelled (attunement UI)");
}
}
float x2 = ImGui.GetStyle().ItemSpacing.X;
float num2 = ImGui.GetFrameHeight() + ImGui.CalcTextSize("Unattuned only").X + 3f * x2;
if (UiThemeUtils.SearchInput("##AttunementSearch", ref _searchText, 0f - num2))
{
RebuildFlatRows();
}
ImGui.SameLine();
if (UiThemeUtils.WrappedCheckbox("Unattuned only", ref _showUnattunedOnly))
{
RebuildFlatRows();
}
ImGui.Separator();
DrawTrackerTable();
}
private unsafe void DrawTrackerTable()
{
if (_flatRows.Count == 0)
{
UiThemeUtils.EmptyState(FontAwesomeIcon.Wifi, "No attunements match your search.");
return;
}
float x;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
x = ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
}
float fontGlobalScale = ImGui.GetIO().FontGlobalScale;
using ImRaii.TableDisposable tableDisposable = UiThemeUtils.BeginThemedTable("Attunement", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.ScrollY);
if (!tableDisposable)
{
return;
}
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
ImGui.TableSetupColumn("Unlocked", ImGuiTableColumnFlags.WidthFixed, 120f * fontGlobalScale);
ImGui.TableHeadersRow();
float textLineHeightWithSpacing = ImGui.GetTextLineHeightWithSpacing();
bool flag = false;
ImGuiListClipper* intPtr = ImGuiNative.ImGuiListClipper();
ImGuiNative.Begin(intPtr, _flatRows.Count, textLineHeightWithSpacing);
ImGuiListClipperPtr imGuiListClipperPtr = intPtr;
try
{
while (imGuiListClipperPtr.Step())
{
for (int i = imGuiListClipperPtr.DisplayStart; i < imGuiListClipperPtr.DisplayEnd; i++)
{
if (DrawFlatRow(_flatRows[i], x))
{
flag = true;
}
}
}
}
finally
{
imGuiListClipperPtr.Destroy();
}
if (flag)
{
RebuildFlatRows();
}
}
private bool DrawFlatRow(FlatRow row, float statusIconSpacing)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (row.Depth > 0)
{
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().IndentSpacing * (float)row.Depth);
}
if (row.Kind == AttuneRowKind.AetheryteLeaf)
{
DrawAetheryteLeaf(row, statusIconSpacing);
return false;
}
if (row.Kind == AttuneRowKind.CurrentLeaf)
{
DrawCurrentLeaf(row, statusIconSpacing);
return false;
}
bool flag = _expandedGroups.Contains(row.Key);
UiThemeUtils.DrawTrackerGroupCaret(_pluginInterface.UiBuilder.IconFontFixedWidthHandle, flag);
ImGui.SameLine();
bool result = false;
ImU8String label = new ImU8String(2, 2);
label.AppendFormatted(row.Label);
label.AppendLiteral("##");
label.AppendFormatted(row.Key);
if (ImGui.Selectable(label))
{
if (flag)
{
_expandedGroups.Remove(row.Key);
}
else
{
_expandedGroups.Add(row.Key);
}
result = true;
}
DrawHeaderBatchPopup(row);
ImGui.TableNextColumn();
UiThemeUtils.DrawTrackerCount(row.CountText, row.CountComplete, row.CountEmpty);
return result;
}
private void DrawHeaderBatchPopup(FlatRow row)
{
if (row.Batch == BatchKind.AethernetCity)
{
CityGroup city = row.City;
if ((object)city != null)
{
int count = city.Shards.Count;
if (_aethernetCityUnlockedCounts.GetValueOrDefault(city.CityName) >= count || IsAnyControllerRunning())
{
return;
}
string text = "##AttuneAllAethernet_" + city.CityName;
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
ImGui.OpenPopup(text);
}
ImRaii.PopupDisposable popupDisposable = ImRaii.Popup(text);
try
{
if (!popupDisposable.Success)
{
return;
}
ImU8String label = new ImU8String(14, 1);
label.AppendLiteral("Attune all in ");
label.AppendFormatted(city.CityName);
if (ImGui.MenuItem(label))
{
IEnumerable<(string, ushort, Vector3, ITask)> targets = from s in city.Shards
where !_aetheryteUnlockCache.GetValueOrDefault(s.Location)
select ((string Name, ushort TerritoryId, Vector3, ITask))(Name: s.Name, TerritoryId: s.TerritoryId, _aetheryteData.Locations[s.Location], new AethernetShard.Attune(s.Location));
StartBatchAttunement("All shards in " + city.CityName, targets);
}
return;
}
finally
{
popupDisposable.Dispose();
}
}
}
if (row.Batch != BatchKind.CurrentZone)
{
return;
}
ZoneCurrentGroup zone = row.Zone;
if ((object)zone == null)
{
return;
}
List<CurrentEntry> list = zone.Currents.Where((CurrentEntry c) => !c.IsQuestGated && !_currentUnlockCache.GetValueOrDefault(c.AetherCurrentId)).ToList();
if (list.Count <= 0 || IsAnyControllerRunning())
{
return;
}
string text2 = $"##AttuneAllCurrents_{zone.TerritoryId}";
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
ImGui.OpenPopup(text2);
}
ImRaii.PopupDisposable popupDisposable2 = ImRaii.Popup(text2);
try
{
if (!popupDisposable2.Success)
{
return;
}
ImU8String label2 = new ImU8String(14, 1);
label2.AppendLiteral("Attune all in ");
label2.AppendFormatted(zone.ZoneName);
if (ImGui.MenuItem(label2))
{
IEnumerable<(string, ushort, Vector3, ITask)> targets2 = from c in list
select (current: c, pos: _aetherCurrentData.GetOverworldPosition(c.AetherCurrentId)) into x
where x.pos != null
select ((string, ushort TerritoryId, Vector3 Position, ITask))("Aether Current", TerritoryId: x.pos.TerritoryId, Position: x.pos.Position, new Questionable.Controller.Steps.Interactions.AetherCurrent.Attune(x.pos.DataId, x.current.AetherCurrentId));
StartBatchAttunement("All currents in " + zone.ZoneName, targets2);
}
}
finally
{
popupDisposable2.Dispose();
}
}
private void DrawAetheryteLeaf(FlatRow row, float statusIconSpacing)
{
AetheryteEntry aetheryte = row.Aetheryte;
bool valueOrDefault = _aetheryteUnlockCache.GetValueOrDefault(aetheryte.Location);
ImU8String label = new ImU8String(2, 2);
label.AppendFormatted(aetheryte.Name);
label.AppendLiteral("##");
label.AppendFormatted(row.Key);
ImGui.Selectable(label);
if (!valueOrDefault && !IsAnyControllerRunning())
{
string text = (row.IsAethernet ? $"##AttuneAethernet_{aetheryte.Location}" : $"##AttuneAetheryte_{aetheryte.Location}");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
ImGui.OpenPopup(text);
}
ImRaii.PopupDisposable popupDisposable = ImRaii.Popup(text);
try
{
if (popupDisposable.Success)
{
ImU8String label2 = new ImU8String(10, 1);
label2.AppendLiteral("Attune to ");
label2.AppendFormatted(aetheryte.Name);
if (ImGui.MenuItem(label2))
{
Vector3 position = _aetheryteData.Locations[aetheryte.Location];
ITask task2;
if (!row.IsAethernet)
{
ITask task = new Questionable.Controller.Steps.Interactions.Aetheryte.Attune(aetheryte.Location);
task2 = task;
}
else
{
ITask task = new AethernetShard.Attune(aetheryte.Location);
task2 = task;
}
ITask attuneTask = task2;
StartAttunement(aetheryte.Name, aetheryte.TerritoryId, position, attuneTask);
}
}
}
finally
{
popupDisposable.Dispose();
}
}
ImGui.TableNextColumn();
DrawStatusGlyph(valueOrDefault, statusIconSpacing);
}
private void DrawCurrentLeaf(FlatRow row, float statusIconSpacing)
{
CurrentEntry current = row.Current;
bool valueOrDefault = _currentUnlockCache.GetValueOrDefault(current.AetherCurrentId);
if (current.IsQuestGated)
{
string value = ((!string.IsNullOrEmpty(current.QuestName)) ? ("Quest: " + current.QuestName) : "Quest Current");
ImU8String label = new ImU8String(2, 2);
label.AppendFormatted(value);
label.AppendLiteral("##");
label.AppendFormatted(row.Key);
ImGui.Selectable(label);
if (current.QuestId != 0 && _questData.TryGetQuestInfo(QuestId.FromRowId(current.QuestId), out IQuestInfo questInfo))
{
if (ImGui.IsItemHovered())
{
_questTooltipComponent.Draw(questInfo);
}
_questRegistry.TryGetQuest(questInfo.QuestId, out Questionable.Model.Quest quest);
_questJournalUtils.ShowContextMenu(questInfo, quest, "AttunementJournalComponent");
}
}
else
{
ImU8String label2 = new ImU8String(16, 1);
label2.AppendLiteral("Aether Current##");
label2.AppendFormatted(row.Key);
ImGui.Selectable(label2);
if (!valueOrDefault)
{
AetherCurrentPosition overworldPosition = _aetherCurrentData.GetOverworldPosition(current.AetherCurrentId);
string text = $"##AttuneCurrent_{current.AetherCurrentId}";
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
ImGui.OpenPopup(text);
}
using ImRaii.PopupDisposable popupDisposable = ImRaii.Popup(text);
if ((bool)popupDisposable)
{
bool flag = overworldPosition != null && !IsAnyControllerRunning();
using (ImRaii.Disabled(!flag))
{
if (ImGui.MenuItem("Attune") && overworldPosition != null)
{
StartAttunement("Aether Current", overworldPosition.TerritoryId, overworldPosition.Position, new Questionable.Controller.Steps.Interactions.AetherCurrent.Attune(overworldPosition.DataId, current.AetherCurrentId));
}
}
if (!flag && overworldPosition == null && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Position unknown");
}
}
}
}
ImGui.TableNextColumn();
DrawStatusGlyph(valueOrDefault, statusIconSpacing);
}
private void DrawStatusGlyph(bool unlocked, float statusIconSpacing)
{
float num = ImGui.GetColumnWidth() / 2f - statusIconSpacing;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
_uiUtils.ChecklistItem(string.Empty, unlocked ? UiThemeUtils.StatusComplete : UiThemeUtils.StatusLocked, unlocked ? FontAwesomeIcon.Check : FontAwesomeIcon.Times);
}
private void RebuildFlatRows()
{
List<FlatRow> list = new List<FlatRow>();
if (_aetherytesByExpansion.Any((ExpansionGroup<AetheryteEntry> exp) => exp.Items.Any(AetherytePasses)))
{
list.Add(HeaderRow(0, "Aetherytes", "Aetherytes", _aetheryteUnlocked, _aetheryteTotal));
if (_expandedGroups.Contains("Aetherytes"))
{
foreach (ExpansionGroup<AetheryteEntry> item in _aetherytesByExpansion)
{
List<AetheryteEntry> list2 = item.Items.Where(AetherytePasses).ToList();
if (list2.Count == 0)
{
continue;
}
string text = $"AetheryteExp_{item.SortOrder}";
int valueOrDefault = _aetheryteExpansionUnlockedCounts.GetValueOrDefault(item.SortOrder);
list.Add(HeaderRow(1, text, item.ExpansionName, valueOrDefault, item.Items.Count));
if (!_expandedGroups.Contains(text))
{
continue;
}
foreach (AetheryteEntry item2 in list2)
{
list.Add(AetheryteLeafRow(2, item2, isAethernet: false));
}
}
}
}
if (_aethernetByCity.Any((CityGroup city) => city.Shards.Any(AetherytePasses)))
{
list.Add(HeaderRow(0, "Aethernet Shards", "Aethernet Shards", _aethernetUnlocked, _aethernetTotal));
if (_expandedGroups.Contains("Aethernet Shards"))
{
foreach (CityGroup item3 in _aethernetByCity)
{
List<AetheryteEntry> list3 = item3.Shards.Where(AetherytePasses).ToList();
if (list3.Count == 0)
{
continue;
}
string text2 = "AethernetCity_" + item3.CityName;
int valueOrDefault2 = _aethernetCityUnlockedCounts.GetValueOrDefault(item3.CityName);
list.Add(HeaderRow(1, text2, item3.CityName, valueOrDefault2, item3.Shards.Count, BatchKind.AethernetCity, item3));
if (!_expandedGroups.Contains(text2))
{
continue;
}
foreach (AetheryteEntry item4 in list3)
{
list.Add(AetheryteLeafRow(2, item4, isAethernet: true));
}
}
}
}
if (_currentsByExpansion.Any((ExpansionGroup<ZoneCurrentGroup> exp) => exp.Items.Any(ZoneHasContent)))
{
list.Add(HeaderRow(0, "Aether Currents", "Aether Currents", _currentUnlocked, _currentTotal));
if (_expandedGroups.Contains("Aether Currents"))
{
foreach (ExpansionGroup<ZoneCurrentGroup> item5 in _currentsByExpansion)
{
List<ZoneCurrentGroup> list4 = item5.Items.Where(ZoneHasContent).ToList();
if (list4.Count == 0)
{
continue;
}
string text3 = $"CurrentsExp_{item5.SortOrder}";
int unlocked = item5.Items.Sum((ZoneCurrentGroup z) => _currentZoneUnlockedCounts.GetValueOrDefault(z.TerritoryId));
int total = item5.Items.Sum((ZoneCurrentGroup z) => z.Currents.Count);
list.Add(HeaderRow(1, text3, item5.ExpansionName, unlocked, total));
if (!_expandedGroups.Contains(text3))
{
continue;
}
foreach (ZoneCurrentGroup item6 in list4)
{
string text4 = $"CurrentZone_{item6.TerritoryId}";
int valueOrDefault3 = _currentZoneUnlockedCounts.GetValueOrDefault(item6.TerritoryId);
list.Add(HeaderRow(2, text4, item6.ZoneName, valueOrDefault3, item6.Currents.Count, BatchKind.CurrentZone, null, item6));
if (!_expandedGroups.Contains(text4))
{
continue;
}
foreach (CurrentEntry item7 in item6.Currents.Where(CurrentPasses))
{
list.Add(CurrentLeafRow(3, item7));
}
}
}
}
}
_flatRows = list;
}
private bool AetherytePasses(AetheryteEntry entry)
{
if (!string.IsNullOrEmpty(_searchText) && !entry.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (_showUnattunedOnly && _aetheryteUnlockCache.GetValueOrDefault(entry.Location))
{
return false;
}
return true;
}
private bool CurrentPasses(CurrentEntry entry)
{
if (_showUnattunedOnly)
{
return !_currentUnlockCache.GetValueOrDefault(entry.AetherCurrentId);
}
return true;
}
private bool ZoneHasContent(ZoneCurrentGroup zone)
{
if (!string.IsNullOrEmpty(_searchText) && !zone.ZoneName.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return zone.Currents.Any(CurrentPasses);
}
private static FlatRow HeaderRow(int depth, string key, string label, int unlocked, int total, BatchKind batch = BatchKind.None, CityGroup? city = null, ZoneCurrentGroup? zone = null)
{
return new FlatRow
{
Kind = AttuneRowKind.Header,
Depth = depth,
Key = key,
Label = label,
CountText = FormatCount(unlocked, total),
CountComplete = (unlocked == total && total > 0),
CountEmpty = (total == 0),
Batch = batch,
City = city,
Zone = zone
};
}
private static FlatRow AetheryteLeafRow(int depth, AetheryteEntry entry, bool isAethernet)
{
return new FlatRow
{
Kind = AttuneRowKind.AetheryteLeaf,
Depth = depth,
Key = (isAethernet ? "Shard_" : "Aeth_") + entry.Location,
Label = entry.Name,
Aetheryte = entry,
IsAethernet = isAethernet
};
}
private static FlatRow CurrentLeafRow(int depth, CurrentEntry entry)
{
return new FlatRow
{
Kind = AttuneRowKind.CurrentLeaf,
Depth = depth,
Key = "Cur_" + entry.AetherCurrentId,
Current = entry
};
}
private static string FormatCount(int unlocked, int total)
{
if (total != 0)
{
return unlocked.ToString(CultureInfo.CurrentCulture).PadLeft(CountPadWidth) + " / " + total.ToString(CultureInfo.CurrentCulture).PadLeft(CountPadWidth);
}
return "-".PadLeft(CountPadWidth) + " / " + "-".PadLeft(CountPadWidth);
}
private void StopAll(string reason)
{
_pendingBatchTargets.Clear();
_batchName = null;
_movementController.Stop();
_questController.Stop(reason);
_fateController.Stop(reason);
_seasonalDutyController.Stop(reason);
_customDeliveryController.Stop(reason);
_attunementController.Stop(reason);
}
public void ClearBatchQueue()
{
_pendingBatchTargets.Clear();
_batchName = null;
}
private bool IsAnyControllerRunning()
{
if (!_questController.IsRunning && !_fateController.IsRunning && !_seasonalDutyController.IsRunning && !_customDeliveryController.IsRunning)
{
return _attunementController.IsRunning;
}
return true;
}
private void StartBatchAttunement(string batchName, IEnumerable<(string Name, ushort TerritoryId, Vector3 Position, ITask AttuneTask)> targets)
{
_pendingBatchTargets.Clear();
_pendingBatchTargets.AddRange(targets);
if (_pendingBatchTargets.Count != 0)
{
_batchName = batchName;
(string, ushort, Vector3, ITask) tuple = PopNearestTarget();
StartAttunement(batchName, tuple.Item2, tuple.Item3, tuple.Item4);
}
}
private (string Name, ushort TerritoryId, Vector3 Position, ITask AttuneTask) PopNearestTarget()
{
Vector3 value = _objectTable[0]?.Position ?? Vector3.Zero;
int index = 0;
float num = float.MaxValue;
for (int i = 0; i < _pendingBatchTargets.Count; i++)
{
float num2 = Vector3.DistanceSquared(value, _pendingBatchTargets[i].Position);
if (num2 < num)
{
num = num2;
index = i;
}
}
(string Name, ushort TerritoryId, Vector3 Position, ITask AttuneTask) result = _pendingBatchTargets[index];
_pendingBatchTargets.RemoveAt(index);
return result;
}
private void StartAttunement(string targetName, ushort territoryId, Vector3 position, ITask attuneTask)
{
PlayerNavState playerNavState = _playerNavStateBuilder.Build();
if (playerNavState == null)
{
_logger.LogWarning("Cannot build PlayerNavState for attunement routing");
return;
}
RouteResult routeResult = _navRouter.FindRoute(playerNavState, territoryId, position);
List<ITask> list = new List<ITask>();
if (routeResult.Segments.Count > 0)
{
list.AddRange(_taskMapper.MapInstructions(_instructionBuilder.Build(routeResult, territoryId)));
}
else if (_clientState.TerritoryType != territoryId)
{
_logger.LogWarning("No route found to territory {TerritoryId} for {Target}, aborting", territoryId, targetName);
return;
}
list.Add(new MoveTask(territoryId, position, null, MountRequired: false, DismountRequired: false, 3f));
list.Add(new Questionable.Controller.Steps.Common.Mount.UnmountTask());
list.Add(attuneTask);
_attunementController.Start(targetName, list);
}
}