This commit is contained in:
Ottermandias 2023-07-01 19:22:30 +02:00
parent 8fbca07f3c
commit b63b02ae5e
2 changed files with 328 additions and 46 deletions

View file

@ -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<byte> 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

View file

@ -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<uint, long> _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<uint, (uint, UnlockType)> _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<uint, UnlockRequirements> Unlockable;
public IReadOnlyDictionary<uint, long> 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<uint>(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<uint>(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<uint, (uint, UnlockType)> CreateUnlockData(DataManager gameData, ItemManager items)
private static Dictionary<uint, UnlockRequirements> CreateUnlockData(DataManager gameData, ItemManager items)
{
var ret = new Dictionary<uint, (uint, UnlockType)>();
var ret = new Dictionary<uint, UnlockRequirements>();
var cabinet = gameData.GetExcelSheet<Cabinet>()!;
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<GilShopItem>()!;
// 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;
}