From b63b02ae5e0b57d476b7851aabe9b3de947f1863 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jul 2023 19:22:30 +0200 Subject: [PATCH] . --- Glamourer/Gui/Tabs/DebugTab.cs | 101 ++++++++- Glamourer/Unlocks/ItemUnlockManager.cs | 273 +++++++++++++++++++++---- 2 files changed, 328 insertions(+), 46 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index f6e647d..a88d49f 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -49,7 +49,7 @@ public unsafe class DebugTab : ITab private readonly CustomizationService _customization; private readonly JobService _jobs; private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly CustomizeUnlockManager _itemUnlocks; + private readonly ItemUnlockManager _itemUnlocks; private readonly DesignManager _designManager; private readonly DesignFileSystem _designFileSystem; @@ -69,7 +69,8 @@ public unsafe class DebugTab : ITab ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager, DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, - AutoDesignManager autoDesignManager, JobService jobs, PhrasingService phrasing, CustomizeUnlockManager customizeUnlocks) + AutoDesignManager autoDesignManager, JobService jobs, PhrasingService phrasing, CustomizeUnlockManager customizeUnlocks, + ItemUnlockManager itemUnlocks) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -93,6 +94,7 @@ public unsafe class DebugTab : ITab _jobs = jobs; _phrasing = phrasing; _customizeUnlocks = customizeUnlocks; + _itemUnlocks = itemUnlocks; } public ReadOnlySpan Label @@ -1250,6 +1252,7 @@ public unsafe class DebugTab : ITab DrawCustomizationUnlocks(); DrawItemUnlocks(); + DrawUnlockableItems(); } private void DrawCustomizationUnlocks() @@ -1260,7 +1263,7 @@ public unsafe class DebugTab : ITab using var table = ImRaii.Table("customizationUnlocks", 6, - ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); if (!table) return; @@ -1276,7 +1279,9 @@ public unsafe class DebugTab : ITab ImGuiUtil.DrawTableColumn(t.Value.Data.ToString()); ImGuiUtil.DrawTableColumn(t.Value.Name); ImGuiUtil.DrawTableColumn(_customizeUnlocks.IsUnlocked(t.Key, out var time) - ? time == DateTimeOffset.MaxValue ? "Always" : time.LocalDateTime.ToShortDateString() + ? time == DateTimeOffset.MaxValue + ? "Always" + : time.LocalDateTime.ToString("g") : "Never"); }, _customizeUnlocks.Unlockable.Count); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); @@ -1284,14 +1289,96 @@ public unsafe class DebugTab : ITab private void DrawItemUnlocks() { - using var tree = ImRaii.TreeNode("Item"); + using var tree = ImRaii.TreeNode("Unlocked Items"); if (!tree) return; - - using var table = ImRaii.Table("itemUnlocks", 6, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + using var table = ImRaii.Table("itemUnlocks", 5, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); if (!table) return; + + ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 400 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Unlock", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); + ImGui.TableNextRow(); + var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlocked, skips, t => + { + ImGuiUtil.DrawTableColumn(t.Key.ToString()); + if (_items.ItemService.AwaitedService.TryGetValue(t.Key, out var equip)) + { + ImGuiUtil.DrawTableColumn(equip.Name); + ImGuiUtil.DrawTableColumn(equip.Type.ToName()); + ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); + } + else + { + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + } + + ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time) + ? time == DateTimeOffset.MaxValue + ? "Always" + : time.LocalDateTime.ToString("g") + : "Never"); + }, _itemUnlocks.Unlocked.Count); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); + } + + private void DrawUnlockableItems() + { + using var tree = ImRaii.TreeNode("Unlockable Items"); + if (!tree) + return; + + using var table = ImRaii.Table("unlockableItem", 6, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); + if (!table) + return; + + ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 400 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Unlock", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Criteria", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); + ImGui.TableNextRow(); + var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => + { + ImGuiUtil.DrawTableColumn(t.Key.ToString()); + if (_items.ItemService.AwaitedService.TryGetValue(t.Key, out var equip)) + { + ImGuiUtil.DrawTableColumn(equip.Name); + ImGuiUtil.DrawTableColumn(equip.Type.ToName()); + ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); + } + else + { + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + } + + ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time) + ? time == DateTimeOffset.MaxValue + ? "Always" + : time.LocalDateTime.ToString("g") + : "Never"); + ImGuiUtil.DrawTableColumn(t.Value.ToString()); + }, _itemUnlocks.Unlockable.Count); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } #endregion diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 8a3cc9d..11e10a6 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -4,13 +4,11 @@ using System.IO; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState; -using Dalamud.Hooking; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Services; -using Lumina.Excel; using Lumina.Excel.GeneratedSheets; -using Achievement = FFXIVClientStructs.FFXIV.Client.Game.UI.Achievement; using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet; namespace Glamourer.Unlocks; @@ -23,15 +21,70 @@ public class ItemUnlockManager : ISavable, IDisposable private readonly Framework _framework; private readonly Dictionary _unlocked = new(); + private bool _lastArmoireState; + private bool _lastAchievementState; + private bool _lastGlamourState; + private bool _lastPlateState; + private byte _currentInventory; + private byte _currentInventoryIndex; + [Flags] public enum UnlockType : byte { - Cabinet, - Quest, - Achievement, + Quest1 = 0x01, + Quest2 = 0x02, + Achievement = 0x04, + Cabinet = 0x08, } - public readonly IReadOnlyDictionary _unlockable; + public readonly record struct UnlockRequirements(uint Quest1, uint Quest2, uint Achievement, ushort State, UnlockType Type) + { + public override string ToString() + { + return Type switch + { + UnlockType.Quest1 => $"Quest {Quest1}", + UnlockType.Quest1 | UnlockType.Quest2 => $"Quests {Quest1} & {Quest2}", + UnlockType.Achievement => $"Achievement {Achievement}", + UnlockType.Quest1 | UnlockType.Achievement => $"Quest {Quest1} & Achievement {Achievement}", + UnlockType.Quest1 | UnlockType.Quest2 | UnlockType.Achievement => $"Quests {Quest1} & {Quest2}, Achievement {Achievement}", + UnlockType.Cabinet => $"Cabinet {Quest1}", + _ => string.Empty, + }; + } + + public unsafe bool IsUnlocked(ItemUnlockManager manager) + { + if (Type == 0) + return true; + + var uiState = UIState.Instance(); + if (uiState == null) + return false; + + bool CheckQuest(uint quest) + => uiState->IsUnlockLinkUnlockedOrQuestCompleted(quest); + + // TODO ClientStructs + bool CheckAchievement(uint achievement) + => false; + + return Type switch + { + UnlockType.Quest1 => CheckQuest(Quest1), + UnlockType.Quest1 | UnlockType.Quest2 => CheckQuest(Quest1) && CheckQuest(Quest2), + UnlockType.Achievement => CheckAchievement(Achievement), + UnlockType.Quest1 | UnlockType.Achievement => CheckQuest(Quest1) && CheckAchievement(Achievement), + UnlockType.Quest1 | UnlockType.Quest2 | UnlockType.Achievement => CheckQuest(Quest1) + && CheckQuest(Quest2) + && CheckAchievement(Achievement), + UnlockType.Cabinet => uiState->Cabinet.IsCabinetLoaded() && uiState->Cabinet.IsItemInCabinet((int)Quest1), + _ => false, + }; + } + } + + public readonly IReadOnlyDictionary Unlockable; public IReadOnlyDictionary Unlocked => _unlocked; @@ -43,7 +96,7 @@ public class ItemUnlockManager : ISavable, IDisposable _items = items; _clientState = clientState; _framework = framework; - _unlockable = CreateUnlockData(gameData, items); + Unlockable = CreateUnlockData(gameData, items); Load(); _clientState.Login += OnLogin; _framework.Update += OnFramework; @@ -52,38 +105,156 @@ public class ItemUnlockManager : ISavable, IDisposable //private Achievement.AchievementState _achievementState = Achievement.AchievementState.Invalid; + private static readonly InventoryType[] ScannableInventories = + { + InventoryType.Inventory1, + InventoryType.Inventory2, + InventoryType.Inventory3, + InventoryType.Inventory4, + InventoryType.EquippedItems, + InventoryType.Mail, + InventoryType.ArmoryOffHand, + InventoryType.ArmoryHead, + InventoryType.ArmoryBody, + InventoryType.ArmoryHands, + InventoryType.ArmoryLegs, + InventoryType.ArmoryFeets, + InventoryType.ArmoryEar, + InventoryType.ArmoryNeck, + InventoryType.ArmoryWrist, + InventoryType.ArmoryRings, + InventoryType.ArmoryMainHand, + InventoryType.SaddleBag1, + InventoryType.SaddleBag2, + InventoryType.PremiumSaddleBag1, + InventoryType.PremiumSaddleBag2, + InventoryType.RetainerPage1, + InventoryType.RetainerPage2, + InventoryType.RetainerPage3, + InventoryType.RetainerPage4, + InventoryType.RetainerPage5, + InventoryType.RetainerPage6, + InventoryType.RetainerPage7, + InventoryType.RetainerEquippedItems, + InventoryType.RetainerMarket, + }; + private unsafe void OnFramework(Framework _) - { - //var achievement = Achievement.Instance(); - var uiState = UIState.Instance(); - } - - public bool IsUnlocked(uint itemId) - { - // Pseudo items are always unlocked. - if (itemId >= _items.ItemSheet.RowCount) - return true; - - if (_unlocked.ContainsKey(itemId)) - return true; - - // TODO - return false; - } - - public unsafe bool IsGameUnlocked(uint id, UnlockType type) { var uiState = UIState.Instance(); if (uiState == null) - return false; + return; - return type switch + var scan = false; + var newArmoireState = uiState->Cabinet.IsCabinetLoaded(); + if (newArmoireState != _lastArmoireState) { - UnlockType.Cabinet => uiState->Cabinet.IsCabinetLoaded() && uiState->Cabinet.IsItemInCabinet((int)id), - UnlockType.Quest => uiState->IsUnlockLinkUnlockedOrQuestCompleted(id), - UnlockType.Achievement => false, - _ => false, - }; + _lastArmoireState = newArmoireState; + scan |= newArmoireState; + } + + //var newAchievementState = uiState->Achievement.IsAchievementLoaded(); + //if (newAchievementState != _lastAchievementState) + //{ + // _lastAchievementState = newAchievementState; + // scan |= newAchievementState; + //} + + if (scan) + Scan(); + + var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + bool AddItem(uint itemId) + => _items.ItemService.AwaitedService.TryGetValue(itemId, out var equip) && _unlocked.TryAdd(equip.Id, time); + + var mirageManager = MirageManager.Instance(); + var changes = false; + if (mirageManager != null) + { + var newGlamourState = mirageManager->PrismBoxLoaded; + if (newGlamourState != _lastGlamourState) + { + _lastGlamourState = newGlamourState; + // TODO: Make independent from hardcoded value + var span = new ReadOnlySpan(mirageManager->PrismBoxItemIds, 800); + foreach (var item in span) + changes |= AddItem(item); + } + + var newPlateState = mirageManager->GlamourPlatesLoaded; + if (newPlateState != _lastPlateState) + { + _lastPlateState = newPlateState; + foreach (var plate in mirageManager->GlamourPlatesSpan) + { + // TODO: Make independent from hardcoded value + var span = new ReadOnlySpan(plate.ItemIds, 12); + foreach (var item in span) + changes |= AddItem(item); + } + } + } + + var inventoryManager = InventoryManager.Instance(); + if (inventoryManager != null) + { + var type = ScannableInventories[_currentInventory]; + var container = inventoryManager->GetInventoryContainer(type); + if (container != null && container->Loaded != 0 && _currentInventoryIndex < container->Size) + { + var item = container->GetInventorySlot(_currentInventoryIndex++); + if (item != null) + { + changes |= AddItem(item->ItemID); + changes |= AddItem(item->GlamourID); + } + } + else + { + _currentInventory = (byte)(_currentInventory + 1 == ScannableInventories.Length ? 0 : _currentInventory + 1); + _currentInventoryIndex = 0; + } + } + + if (changes) + Save(); + } + + public bool IsUnlocked(uint itemId, out DateTimeOffset time) + { + // Pseudo items are always unlocked. + if (itemId >= _items.ItemSheet.RowCount) + { + time = DateTimeOffset.MaxValue; + return true; + } + + if (_unlocked.TryGetValue(itemId, out var t)) + { + time = DateTimeOffset.FromUnixTimeMilliseconds(t); + return true; + } + + if (IsGameUnlocked(itemId)) + { + time = DateTimeOffset.UtcNow; + _unlocked.TryAdd(itemId, time.ToUnixTimeMilliseconds()); + Save(); + return true; + } + + time = DateTimeOffset.MinValue; + return false; + } + + public unsafe bool IsGameUnlocked(uint itemId) + { + if (Unlockable.TryGetValue(itemId, out var req)) + return req.IsUnlocked(this); + + // TODO inventory + return false; } public void Dispose() @@ -94,7 +265,18 @@ public class ItemUnlockManager : ISavable, IDisposable public void Scan() { - // TODO + var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var changes = false; + foreach (var (itemId, unlock) in Unlockable) + { + if (unlock.IsUnlocked(this)) + changes |= _unlocked.TryAdd(itemId, time); + } + + // TODO inventories + + if (changes) + Save(); } public string ToFilename(FilenameService fileNames) @@ -118,18 +300,31 @@ public class ItemUnlockManager : ISavable, IDisposable private void OnLogin(object? _, EventArgs _2) => Scan(); - private static Dictionary CreateUnlockData(DataManager gameData, ItemManager items) + private static Dictionary CreateUnlockData(DataManager gameData, ItemManager items) { - var ret = new Dictionary(); + var ret = new Dictionary(); var cabinet = gameData.GetExcelSheet()!; foreach (var row in cabinet) { if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item)) - ret.TryAdd(item.Id, (row.RowId, UnlockType.Cabinet)); + ret.TryAdd(item.Id, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet)); } var gilShop = gameData.GetExcelSheet()!; - // TODO + foreach (var row in gilShop) + { + if (!items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item)) + continue; + + var quest1 = row.QuestRequired[0].Row; + var quest2 = row.QuestRequired[1].Row; + var achievement = row.AchievementRequired.Row; + var state = row.StateRequired; + var type = (quest1 != 0 ? UnlockType.Quest1 : 0) + | (quest2 != 0 ? UnlockType.Quest2 : 0) + | (achievement != 0 ? UnlockType.Achievement : 0); + ret.TryAdd(item.Id, new UnlockRequirements(quest1, quest2, achievement, state, type)); + } return ret; }