qstbak/Questionable/Questionable.Windows.JournalComponents/GatheringJournalComponent.cs
2025-10-09 07:47:19 +10:00

462 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using LLib.GameData;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Questionable.Controller;
using Questionable.Model;
using Questionable.Model.Gathering;
namespace Questionable.Windows.JournalComponents;
internal sealed class GatheringJournalComponent
{
private delegate byte GetIsGatheringItemGatheredDelegate(ushort item);
private sealed record ExpansionPoints(EExpansionVersion ExpansionVersion, List<TerritoryPoints> PointsByTerritories)
{
public int TotalItems { get; set; }
public int TotalPoints { get; set; }
public int CompletedItems { get; set; }
public int CompletedPoints { get; set; }
}
private sealed record TerritoryPoints(ushort TerritoryType, string TerritoryName, List<DefaultGatheringPoint> Points)
{
public int TotalItems { get; set; }
public int TotalPoints => Points.Count;
public int CompletedItems { get; set; }
public int CompletedPoints { get; set; }
public string ToFriendlyString()
{
if (string.IsNullOrEmpty(TerritoryName))
{
return $"??? ({TerritoryType})";
}
return TerritoryName;
}
}
private sealed record DefaultGatheringPoint(GatheringPointId Id, EClassJob ClassJob, byte Level, List<ushort> GatheringItemIds, EExpansionVersion Expansion, ushort TerritoryType, string? TerritoryName, string? PlaceName)
{
public int TotalItems { get; set; }
public int CompletedItems { get; set; }
public bool IsComplete { get; set; }
}
private sealed record FilteredExpansion(ExpansionPoints Expansion, List<FilteredTerritory> Territories);
private sealed record FilteredTerritory(TerritoryPoints Territory, List<FilteredGatheringPoint> GatheringPoints);
private sealed record FilteredGatheringPoint(DefaultGatheringPoint Point, List<ushort> GatheringItemIds);
private readonly IDalamudPluginInterface _pluginInterface;
private readonly UiUtils _uiUtils;
private readonly GatheringPointRegistry _gatheringPointRegistry;
private readonly Dictionary<int, string> _gatheringItems;
private readonly List<ExpansionPoints> _gatheringPointsByExpansion;
private readonly List<ushort> _gatheredItems = new List<ushort>();
private List<FilteredExpansion> _filteredExpansions = new List<FilteredExpansion>();
private string _searchText = string.Empty;
[Signature("48 89 5C 24 ?? 57 48 83 EC 20 8B D9 8B F9")]
private GetIsGatheringItemGatheredDelegate _getIsGatheringItemGathered;
private bool IsGatheringItemGathered(uint item)
{
return _getIsGatheringItemGathered((ushort)item) != 0;
}
public GatheringJournalComponent(IDataManager dataManager, IDalamudPluginInterface pluginInterface, UiUtils uiUtils, IGameInteropProvider gameInteropProvider, GatheringPointRegistry gatheringPointRegistry)
{
GatheringJournalComponent gatheringJournalComponent = this;
_pluginInterface = pluginInterface;
_uiUtils = uiUtils;
_gatheringPointRegistry = gatheringPointRegistry;
Dictionary<uint, List<uint>> routeToGatheringPoint = (from x in (from x in dataManager.GetExcelSheet<GatheringLeveRoute>()
where x.GatheringPoint[0].RowId != 0
select x).SelectMany((GatheringLeveRoute x) => from y in x.GatheringPoint
where y.RowId != 0
select new
{
RouteId = x.RowId,
GatheringPointId = y.RowId
})
group x by x.RouteId).ToDictionary(x => x.Key, x => x.Select(y => y.GatheringPointId).ToList());
ExcelSheet<GatheringLeve> gatheringLeveSheet = dataManager.GetExcelSheet<GatheringLeve>();
ExcelSheet<TerritoryType> territoryTypeSheet = dataManager.GetExcelSheet<TerritoryType>();
HashSet<uint> leveGatheringPoints = (from y in (from x in dataManager.GetExcelSheet<Leve>()
where x.RowId != 0
select gatheringLeveSheet.GetRowOrDefault(x.DataId.RowId) into x
where x.HasValue
select x).Cast<GatheringLeve>().SelectMany((GatheringLeve x) => x.Route)
where y.RowId != 0
select y).SelectMany((RowRef<GatheringLeveRoute> y) => routeToGatheringPoint[y.RowId]).Distinct().ToHashSet();
ExcelSheet<Item> itemSheet = dataManager.GetExcelSheet<Item>();
_gatheringItems = (from x in dataManager.GetExcelSheet<GatheringItem>()
where x.RowId != 0 && x.GatheringItemLevel.RowId != 0
select new
{
GatheringItemId = (int)x.RowId,
Name = itemSheet.GetRowOrDefault(x.Item.RowId)?.Name.ToString()
} into x
where !string.IsNullOrEmpty(x.Name)
select x).ToDictionary(x => x.GatheringItemId, x => x.Name);
_gatheringPointsByExpansion = (from x in (from DefaultGatheringPoint x in from x in (from x in (from x in dataManager.GetExcelSheet<GatheringPoint>()
where x.GatheringPointBase.RowId != 0
select x).Where(delegate(GatheringPoint x)
{
uint rowId = x.GatheringPointBase.RowId;
return (rowId < 653 || rowId > 680) ? true : false;
}).DistinctBy((GatheringPoint x) => x.GatheringPointBase.RowId).Select(delegate(GatheringPoint x)
{
uint rowId = x.RowId;
GatheringPointId id = new GatheringPointId((ushort)x.GatheringPointBase.RowId);
EClassJob classJob;
switch (x.GatheringPointBase.Value.GatheringType.RowId)
{
case 0u:
case 1u:
classJob = EClassJob.Miner;
break;
case 2u:
case 3u:
classJob = EClassJob.Botanist;
break;
default:
classJob = EClassJob.Fisher;
break;
}
return new
{
GatheringPointId = rowId,
Point = new DefaultGatheringPoint(id, classJob, x.GatheringPointBase.Value.GatheringLevel, (from y in x.GatheringPointBase.Value.Item
where y.RowId != 0
select (ushort)y.RowId).ToList(), (EExpansionVersion)(((byte?)x.TerritoryType.ValueNullable?.ExVersion.RowId) ?? byte.MaxValue), (ushort)x.TerritoryType.RowId, x.TerritoryType.ValueNullable?.PlaceName.ValueNullable?.Name.ToString(), $"{x.GatheringPointBase.RowId} - {x.PlaceName.ValueNullable?.Name}")
};
})
where x.Point.ClassJob != EClassJob.Fisher
select x).Select(x =>
{
if (leveGatheringPoints.Contains(x.GatheringPointId))
{
return (DefaultGatheringPoint)null;
}
if (x.Point.TerritoryType == 1 && gatheringJournalComponent._gatheringPointRegistry.TryGetGatheringPoint(x.Point.Id, out GatheringRoot gatheringRoot))
{
TerritoryType row = territoryTypeSheet.GetRow(gatheringRoot.Steps.Last().TerritoryId);
return x.Point with
{
Expansion = (EExpansionVersion)row.ExVersion.RowId,
TerritoryType = (ushort)row.RowId,
TerritoryName = row.PlaceName.ValueNullable?.Name.ToString()
};
}
return x.Point;
})
where x != null
select x
where x.Expansion != (EExpansionVersion)255
where x.GatheringItemIds.Count > 0
select x).Where(delegate(DefaultGatheringPoint x)
{
ushort territoryType = x.TerritoryType;
return territoryType != 901 && territoryType != 929;
})
group x by x.Expansion into x
select new ExpansionPoints(x.Key, (from y in x
group y by new
{
TerritoryType = y.TerritoryType,
TerritoryName = $"{((!string.IsNullOrEmpty(y.TerritoryName)) ? y.TerritoryName : "???")} ({y.TerritoryType})"
} into y
select new TerritoryPoints(y.Key.TerritoryType, y.Key.TerritoryName, y.ToList()) into y
where y.Points.Count > 0
select y).ToList()) into x
orderby x.ExpansionVersion
select x).ToList();
gameInteropProvider.InitializeFromAttributes(this);
}
public void DrawGatheringItems()
{
using ImRaii.IEndObject endObject = ImRaii.TabItem("Gathering Points");
if (!endObject)
{
return;
}
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputTextWithHint(string.Empty, "Search areas, gathering points and items", ref _searchText, 256))
{
UpdateFilter();
}
if (_filteredExpansions.Count > 0)
{
using (ImRaii.IEndObject endObject2 = ImRaii.Table("GatheringPoints", 3, ImGuiTableFlags.NoSavedSettings))
{
if (!endObject2)
{
return;
}
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
ImGui.TableSetupColumn("Supported", ImGuiTableColumnFlags.WidthFixed, 100f * ImGui.GetIO().FontGlobalScale);
ImGui.TableSetupColumn("Collected", ImGuiTableColumnFlags.WidthFixed, 100f * ImGui.GetIO().FontGlobalScale);
ImGui.TableHeadersRow();
foreach (FilteredExpansion filteredExpansion in _filteredExpansions)
{
DrawExpansion(filteredExpansion);
}
return;
}
}
ImGui.Text("No area, gathering point or item matches your search text.");
}
private void DrawExpansion(FilteredExpansion expansion)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
bool num = ImGui.TreeNodeEx(expansion.Expansion.ExpansionVersion.ToFriendlyString(), ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
DrawCount(expansion.Expansion.CompletedPoints, expansion.Expansion.TotalPoints);
ImGui.TableNextColumn();
DrawCount(expansion.Expansion.CompletedItems, expansion.Expansion.TotalItems);
if (!num)
{
return;
}
foreach (FilteredTerritory territory in expansion.Territories)
{
DrawTerritory(territory);
}
ImGui.TreePop();
}
private void DrawTerritory(FilteredTerritory territory)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
bool num = ImGui.TreeNodeEx(territory.Territory.ToFriendlyString(), ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
DrawCount(territory.Territory.CompletedPoints, territory.Territory.TotalPoints);
ImGui.TableNextColumn();
DrawCount(territory.Territory.CompletedItems, territory.Territory.TotalItems);
if (!num)
{
return;
}
foreach (FilteredGatheringPoint gatheringPoint in territory.GatheringPoints)
{
DrawPoint(gatheringPoint);
}
ImGui.TreePop();
}
private void DrawPoint(FilteredGatheringPoint point)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImU8String id = new ImU8String(8, 3);
id.AppendFormatted(point.Point.PlaceName);
id.AppendLiteral(" (");
id.AppendFormatted(point.Point.ClassJob);
id.AppendLiteral(" Lv. ");
id.AppendFormatted(point.Point.Level);
id.AppendLiteral(")");
bool flag = ImGui.TreeNodeEx(id, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
float num;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
num = ImGui.GetColumnWidth() / 2f - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
_uiUtils.ChecklistItem(string.Empty, point.Point.IsComplete);
ImGui.TableNextColumn();
DrawCount(point.Point.CompletedItems, point.Point.TotalItems);
if (!flag)
{
return;
}
foreach (ushort gatheringItemId in point.GatheringItemIds)
{
DrawItem(gatheringItemId);
}
ImGui.TreePop();
}
private void DrawItem(ushort item)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TreeNodeEx(_gatheringItems.GetValueOrDefault(item, "???"), ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
ImGui.TableNextColumn();
float num;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
num = ImGui.GetColumnWidth() / 2f - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
if (item < 10000)
{
_uiUtils.ChecklistItem(string.Empty, _gatheredItems.Contains(item));
}
else
{
_uiUtils.ChecklistItem(string.Empty, ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus);
}
}
private static void DrawCount(int count, int total)
{
string text = 999.ToString(CultureInfo.CurrentCulture);
ImGui.PushFont(UiBuilder.MonoFont);
string text2 = count.ToString(CultureInfo.CurrentCulture).PadLeft(text.Length) + " / " + total.ToString(CultureInfo.CurrentCulture).PadLeft(text.Length);
if (count == total)
{
ImGui.TextColored(ImGuiColors.ParsedGreen, text2);
}
else
{
ImGui.TextUnformatted(text2);
}
ImGui.PopFont();
}
public void UpdateFilter()
{
Predicate<string> match;
if (string.IsNullOrWhiteSpace(_searchText))
{
match = (string _) => true;
}
else
{
match = (string x) => x.Contains(_searchText, StringComparison.CurrentCultureIgnoreCase);
}
_filteredExpansions = (from section in _gatheringPointsByExpansion
select FilterExpansion(section, match) into x
where x != null
select x).Cast<FilteredExpansion>().ToList();
}
private FilteredExpansion? FilterExpansion(ExpansionPoints expansion, Predicate<string> match)
{
List<FilteredTerritory> list = (from x in expansion.PointsByTerritories
select FilterTerritory(x, match) into x
where x != null
select (x)).ToList();
if (list.Count > 0)
{
return new FilteredExpansion(expansion, list);
}
return null;
}
private FilteredTerritory? FilterTerritory(TerritoryPoints territory, Predicate<string> match)
{
if (match(territory.TerritoryName))
{
return new FilteredTerritory(territory, territory.Points.Select((DefaultGatheringPoint x) => FilterGatheringPoint(x, (string _) => true)).ToList());
}
List<FilteredGatheringPoint> list = (from x in territory.Points
select FilterGatheringPoint(x, match) into x
where x != null
select (x)).ToList();
if (list.Count > 0)
{
return new FilteredTerritory(territory, list);
}
return null;
}
private FilteredGatheringPoint? FilterGatheringPoint(DefaultGatheringPoint gatheringPoint, Predicate<string> match)
{
if (match(gatheringPoint.PlaceName ?? string.Empty))
{
return new FilteredGatheringPoint(gatheringPoint, gatheringPoint.GatheringItemIds);
}
List<ushort> list = gatheringPoint.GatheringItemIds.Where((ushort x) => match(_gatheringItems.GetValueOrDefault(x, string.Empty))).ToList();
if (list.Count > 0)
{
return new FilteredGatheringPoint(gatheringPoint, list);
}
return null;
}
internal void RefreshCounts()
{
_gatheredItems.Clear();
foreach (int key in _gatheringItems.Keys)
{
ushort item = (ushort)key;
if (IsGatheringItemGathered(item))
{
_gatheredItems.Add(item);
}
}
foreach (ExpansionPoints item2 in _gatheringPointsByExpansion)
{
foreach (TerritoryPoints pointsByTerritory in item2.PointsByTerritories)
{
foreach (DefaultGatheringPoint point in pointsByTerritory.Points)
{
point.TotalItems = point.GatheringItemIds.Count((ushort x) => x < 10000);
point.CompletedItems = point.GatheringItemIds.Count(_gatheredItems.Contains);
point.IsComplete = _gatheringPointRegistry.TryGetGatheringPoint(point.Id, out GatheringRoot _);
}
pointsByTerritory.TotalItems = pointsByTerritory.Points.Sum((DefaultGatheringPoint x) => x.TotalItems);
pointsByTerritory.CompletedItems = pointsByTerritory.Points.Sum((DefaultGatheringPoint x) => x.CompletedItems);
pointsByTerritory.CompletedPoints = pointsByTerritory.Points.Count((DefaultGatheringPoint x) => x.IsComplete);
}
item2.TotalItems = item2.PointsByTerritories.Sum((TerritoryPoints x) => x.TotalItems);
item2.CompletedItems = item2.PointsByTerritories.Sum((TerritoryPoints x) => x.CompletedItems);
item2.TotalPoints = item2.PointsByTerritories.Sum((TerritoryPoints x) => x.TotalPoints);
item2.CompletedPoints = item2.PointsByTerritories.Sum((TerritoryPoints x) => x.CompletedPoints);
}
}
public void ClearCounts(int type, int code)
{
foreach (ExpansionPoints item in _gatheringPointsByExpansion)
{
item.CompletedItems = 0;
item.CompletedPoints = 0;
foreach (TerritoryPoints pointsByTerritory in item.PointsByTerritories)
{
pointsByTerritory.CompletedItems = 0;
pointsByTerritory.CompletedPoints = 0;
foreach (DefaultGatheringPoint point in pointsByTerritory.Points)
{
point.IsComplete = false;
}
}
}
}
}