From a992a841e86290e3534d7e5e0dda10612512e186 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Mon, 28 Oct 2024 22:43:19 +0100 Subject: [PATCH] Show when items have unobtained folklore books in 'Locked Items' tab --- ARControl/ARControl.csproj | 2 +- ARControl/AutoRetainerControlPlugin.Sync.cs | 10 ++++ ARControl/Configuration.cs | 1 + ARControl/GameData/FolkloreBook.cs | 18 ++++++ ARControl/GameData/GameCache.cs | 65 +++++++++++++++++++++ ARControl/Windows/Config/LockedItemsTab.cs | 19 +++++- 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 ARControl/GameData/FolkloreBook.cs diff --git a/ARControl/ARControl.csproj b/ARControl/ARControl.csproj index 109156a..bad06b9 100644 --- a/ARControl/ARControl.csproj +++ b/ARControl/ARControl.csproj @@ -1,6 +1,6 @@ - 5.7 + 5.8 dist diff --git a/ARControl/AutoRetainerControlPlugin.Sync.cs b/ARControl/AutoRetainerControlPlugin.Sync.cs index 55453c1..43d1aba 100644 --- a/ARControl/AutoRetainerControlPlugin.Sync.cs +++ b/ARControl/AutoRetainerControlPlugin.Sync.cs @@ -44,6 +44,16 @@ partial class AutoRetainerControlPlugin save = true; } + if (_clientState.LocalContentId == registeredCharacterId) + { + var unlockedFolkloreBooks = _gameCache.FolkloreBooks.Values.Where(x => x.IsUnlocked()).Select(x => x.ItemId).ToHashSet(); + if (character.UnlockedFolkloreBooks != unlockedFolkloreBooks) + { + character.UnlockedFolkloreBooks = unlockedFolkloreBooks; + save = true; + } + } + // remove retainers without name save |= character.Retainers.RemoveAll(x => string.IsNullOrEmpty(x.Name)) > 0; diff --git a/ARControl/Configuration.cs b/ARControl/Configuration.cs index e49c913..9f4da77 100644 --- a/ARControl/Configuration.cs +++ b/ARControl/Configuration.cs @@ -81,6 +81,7 @@ internal sealed class Configuration : IPluginConfiguration public List Retainers { get; set; } = new(); public HashSet GatheredItems { get; set; } = new(); + public HashSet UnlockedFolkloreBooks { get; set; } = new(); public override string ToString() => $"{CharacterName} @ {WorldName}"; } diff --git a/ARControl/GameData/FolkloreBook.cs b/ARControl/GameData/FolkloreBook.cs new file mode 100644 index 0000000..b9a59bc --- /dev/null +++ b/ARControl/GameData/FolkloreBook.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using FFXIVClientStructs.FFXIV.Client.Game.UI; + +namespace ARControl.GameData; + +internal sealed class FolkloreBook +{ + public required uint ItemId { get; init; } + public required string Name { get; init; } + public required IReadOnlyList GatheringSubCategories { get; init; } + public List GatheringItemIds { get; } = []; + public ushort TomeId { get; set; } + + public unsafe bool IsUnlocked() + { + return PlayerState.Instance()->IsFolkloreBookUnlocked(TomeId); + } +} diff --git a/ARControl/GameData/GameCache.cs b/ARControl/GameData/GameCache.cs index 1753dc8..3cbd059 100644 --- a/ARControl/GameData/GameCache.cs +++ b/ARControl/GameData/GameCache.cs @@ -22,9 +22,74 @@ internal sealed class GameCache .OrderBy(x => x.Name) .ToList() .AsReadOnly(); + FolkloreBooks = dataManager.GetExcelSheet()! + .Where(x => x.RowId > 0) + .Where(x => x.Item.Row != 0) + .Select(x => new + { + x.RowId, + ItemId = x.Item.Row, + ItemName = x.Item.Value!.Name.ToString() + }) + .GroupBy(x => (x.ItemId, x.ItemName)) + .Select(x => + new FolkloreBook + { + ItemId = x.Key.ItemId, + Name = x.Key.ItemName, + GatheringSubCategories = x.Select(y => (ushort)y.RowId).ToList(), + }) + .ToDictionary(x => x.ItemId, x => x); + + var gatheringNodes = dataManager.GetExcelSheet()! + .Where(x => x.RowId > 0 && x.GatheringType.Row <= 3) + .Select(x => + new + { + GatheringPointBaseId = x.RowId, + GatheringPoint = + dataManager.GetExcelSheet()!.FirstOrDefault(y => + y.GatheringPointBase.Row == x.RowId), + Items = x.Item.Where(y => y > 0).ToList() + }) + .Where(x => x.GatheringPoint != null) + .Select(x => + new + { + x.GatheringPointBaseId, + CategoryId = (ushort)x.GatheringPoint!.GatheringSubCategory.Row, + x.Items, + }) + .ToList(); + var itemsWithoutTomes = gatheringNodes + .Where(x => !FolkloreBooks.Values.Any(y => y.GatheringSubCategories.Contains(x.CategoryId))) + .SelectMany(x => x.Items) + .ToList(); + var itemsWithTomes = gatheringNodes + .SelectMany(x => x.Items + .Where(y => !itemsWithoutTomes.Contains(y)) + .Select( + y => + new + { + x.CategoryId, + ItemId = (uint)y + })) + .GroupBy(x => x.CategoryId) + .ToDictionary(x => x.Key, x => x.Select(y => y.ItemId).ToList()); + foreach (var book in FolkloreBooks.Values) + { + book.TomeId = dataManager.GetExcelSheet()!.GetRow(book.ItemId)!.ItemAction.Value!.Data[0]; + foreach (var category in book.GatheringSubCategories) + { + if (itemsWithTomes.TryGetValue(category, out var itemsInCategory)) + book.GatheringItemIds.AddRange(itemsInCategory); + } + } } public IReadOnlyDictionary Jobs { get; } public IReadOnlyList Ventures { get; } public IReadOnlyList ItemsToGather { get; } + public Dictionary FolkloreBooks { get; } } diff --git a/ARControl/Windows/Config/LockedItemsTab.cs b/ARControl/Windows/Config/LockedItemsTab.cs index c802173..deda5eb 100644 --- a/ARControl/Windows/Config/LockedItemsTab.cs +++ b/ARControl/Windows/Config/LockedItemsTab.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; using ARControl.GameData; +using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; @@ -114,10 +115,16 @@ internal sealed class LockedItemsTab : ITab var color = ch.Items[item.ItemId]; if (color != ColorGrey) { + string itemName = item.GatheredItem.Name; + var folkloreBook = _gameCache.FolkloreBooks.Values.FirstOrDefault(x => + x.GatheringItemIds.Contains(item.GatheredItem.GatheredItemId)); + if (folkloreBook != null && !ch.Character.UnlockedFolkloreBooks.Contains(folkloreBook.ItemId)) + itemName += $" ({SeIconChar.Prohibited.ToIconString()} {folkloreBook.Name})"; + ImGui.PushStyleColor(ImGuiCol.Text, color); if (currentCharacter && color == ColorRed) { - ImGui.Selectable(item.GatheredItem.Name); + ImGui.Selectable(itemName); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { uint classJob = _clientState.LocalPlayer!.ClassJob.Id; @@ -133,7 +140,7 @@ internal sealed class LockedItemsTab : ITab } else { - ImGui.Text(item.GatheredItem.Name); + ImGui.Text(itemName); } ImGui.PopStyleColor(); @@ -149,6 +156,8 @@ internal sealed class LockedItemsTab : ITab foreach (var item in itemsToCheck.Where(x => charactersToCheck.Any(y => y.ToCheck(onlyShowMissing).ContainsKey(x.ItemId)))) { + var folkloreBook = _gameCache.FolkloreBooks.Values.FirstOrDefault(x => + x.GatheringItemIds.Contains(item.GatheredItem.GatheredItemId)); if (ImGui.CollapsingHeader($"{item.GatheredItem.Name}##Gathered{item.GatheredItem.ItemId}")) { ImGui.Indent(_configWindow.MainIndentSize + ImGui.GetStyle().FramePadding.X); @@ -171,8 +180,12 @@ internal sealed class LockedItemsTab : ITab ImGui.PopFont(); } + string characterName = ch.Character.ToString(); + if (folkloreBook != null && !ch.Character.UnlockedFolkloreBooks.Contains(folkloreBook.ItemId)) + characterName += $" ({SeIconChar.Prohibited.ToIconString()} {folkloreBook.Name})"; + ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TextUnformatted(ch.Character.ToString()); + ImGui.TextUnformatted(characterName); ImGui.PopStyleColor(); } }