diff --git a/Penumbra.GameData/Data/EquipmentIdentificationList.cs b/Penumbra.GameData/Data/EquipmentIdentificationList.cs index e4ba59d1..d8b946a4 100644 --- a/Penumbra.GameData/Data/EquipmentIdentificationList.cs +++ b/Penumbra.GameData/Data/EquipmentIdentificationList.cs @@ -6,10 +6,11 @@ using Dalamud.Plugin; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using PseudoEquipItem = System.ValueTuple; namespace Penumbra.GameData.Data; -internal sealed class EquipmentIdentificationList : KeyList +internal sealed class EquipmentIdentificationList : KeyList { private const string Tag = "EquipmentIdentification"; @@ -20,11 +21,11 @@ internal sealed class EquipmentIdentificationList : KeyList public IEnumerable Between(SetId modelId, EquipSlot slot = EquipSlot.Unknown, byte variant = 0) { if (slot == EquipSlot.Unknown) - return Between(ToKey(modelId, 0, 0), ToKey(modelId, (EquipSlot)0xFF, 0xFF)); + return Between(ToKey(modelId, 0, 0), ToKey(modelId, (EquipSlot)0xFF, 0xFF)).Select(e => (EquipItem)e); if (variant == 0) - return Between(ToKey(modelId, slot, 0), ToKey(modelId, slot, 0xFF)); + return Between(ToKey(modelId, slot, 0), ToKey(modelId, slot, 0xFF)).Select(e => (EquipItem)e); - return Between(ToKey(modelId, slot, variant), ToKey(modelId, slot, variant)); + return Between(ToKey(modelId, slot, variant), ToKey(modelId, slot, variant)).Select(e => (EquipItem)e); } public void Dispose(DalamudPluginInterface pi, ClientLanguage language) @@ -34,9 +35,9 @@ internal sealed class EquipmentIdentificationList : KeyList => ((ulong)modelId << 32) | ((ulong)slot << 16) | variant; public static ulong ToKey(EquipItem i) - => ToKey(i.ModelId, i.Slot, i.Variant); + => ToKey(i.ModelId, i.Type.ToSlot(), i.Variant); - protected override IEnumerable ToKeys(EquipItem i) + protected override IEnumerable ToKeys(PseudoEquipItem i) { yield return ToKey(i); } @@ -44,12 +45,12 @@ internal sealed class EquipmentIdentificationList : KeyList protected override bool ValidKey(ulong key) => key != 0; - protected override int ValueKeySelector(EquipItem data) - => (int)data.Id; + protected override int ValueKeySelector(PseudoEquipItem data) + => (int)data.Item2; - private static IEnumerable CreateEquipmentList(DataManager gameData, ClientLanguage language) + private static IEnumerable CreateEquipmentList(DataManager gameData, ClientLanguage language) { var items = gameData.GetExcelSheet(language)!; - return items.Where(i => ((EquipSlot)i.EquipSlotCategory.Row).IsEquipmentPiece()).Select(EquipItem.FromArmor); + return items.Where(i => ((EquipSlot)i.EquipSlotCategory.Row).IsEquipmentPiece()).Select(i => (PseudoEquipItem)EquipItem.FromArmor(i)); } } diff --git a/Penumbra.GameData/Data/GamePaths.cs b/Penumbra.GameData/Data/GamePaths.cs index ed6078c7..5df91600 100644 --- a/Penumbra.GameData/Data/GamePaths.cs +++ b/Penumbra.GameData/Data/GamePaths.cs @@ -1,5 +1,4 @@ using System.Text.RegularExpressions; -using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -18,7 +17,6 @@ public static partial class GamePaths : GenderRace.Unknown; } - public static partial class Monster { public static partial class Imc diff --git a/Penumbra.GameData/Data/ItemData.cs b/Penumbra.GameData/Data/ItemData.cs index 7cbd819c..cbde9ede 100644 --- a/Penumbra.GameData/Data/ItemData.cs +++ b/Penumbra.GameData/Data/ItemData.cs @@ -8,14 +8,17 @@ using Dalamud.Plugin; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using PseudoEquipItem = System.ValueTuple; namespace Penumbra.GameData.Data; public sealed class ItemData : DataSharer, IReadOnlyDictionary> { - private readonly IReadOnlyList> _items; + private readonly IReadOnlyDictionary _mainItems; + private readonly IReadOnlyDictionary _offItems; + private readonly IReadOnlyList> _byType; - private static IReadOnlyList> CreateItems(DataManager dataManager, ClientLanguage language) + private static IReadOnlyList> CreateItems(DataManager dataManager, ClientLanguage language) { var tmp = Enum.GetValues().Select(_ => new List(1024)).ToArray(); @@ -28,7 +31,7 @@ public sealed class ItemData : DataSharer, IReadOnlyDictionary[tmp.Length]; - ret[0] = Array.Empty(); + var ret = new IReadOnlyList[tmp.Length]; + ret[0] = Array.Empty(); for (var i = 1; i < tmp.Length; ++i) - ret[i] = tmp[i].OrderBy(item => item.Name).ToArray(); + ret[i] = tmp[i].OrderBy(item => item.Name).Select(s => (PseudoEquipItem)s).ToArray(); return ret; } + private static IReadOnlyDictionary CreateMainItems(IReadOnlyList> items) + { + var dict = new Dictionary(1024 * 4); + foreach (var type in Enum.GetValues().Where(v => !FullEquipTypeExtensions.OffhandTypes.Contains(v))) + { + var list = items[(int)type]; + foreach (var item in list) + dict.TryAdd(item.Item2, item); + } + + dict.TrimExcess(); + return dict; + } + + private static IReadOnlyDictionary CreateOffItems(IReadOnlyList> items) + { + var dict = new Dictionary(128); + foreach (var type in FullEquipTypeExtensions.OffhandTypes) + { + var list = items[(int)type]; + foreach (var item in list) + dict.TryAdd(item.Item2, item); + } + + dict.TrimExcess(); + return dict; + } + public ItemData(DalamudPluginInterface pluginInterface, DataManager dataManager, ClientLanguage language) : base(pluginInterface, language, 1) { - _items = TryCatchData("ItemList", () => CreateItems(dataManager, language)); + _byType = TryCatchData("ItemList", () => CreateItems(dataManager, language)); + _mainItems = TryCatchData("ItemDictMain", () => CreateMainItems(_byType)); + _offItems = TryCatchData("ItemDictOff", () => CreateOffItems(_byType)); } protected override void DisposeInternal() - => DisposeTag("ItemList"); + { + DisposeTag("ItemList"); + DisposeTag("ItemDictMain"); + DisposeTag("ItemDictOff"); + } public IEnumerator>> GetEnumerator() { - for (var i = 1; i < _items.Count; ++i) - yield return new KeyValuePair>((FullEquipType)i, _items[i]); + for (var i = 1; i < _byType.Count; ++i) + yield return new KeyValuePair>((FullEquipType)i, new EquipItemList(_byType[i])); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count - => _items.Count - 1; + => _byType.Count - 1; public bool ContainsKey(FullEquipType key) - => (int)key < _items.Count && key != FullEquipType.Unknown; + => (int)key < _byType.Count && key != FullEquipType.Unknown; public bool TryGetValue(FullEquipType key, out IReadOnlyList value) { if (ContainsKey(key)) { - value = _items[(int)key]; + value = new EquipItemList(_byType[(int)key]); return true; } - value = _items[0]; + value = Array.Empty(); return false; } public IReadOnlyList this[FullEquipType key] => TryGetValue(key, out var ret) ? ret : throw new IndexOutOfRangeException(); + public bool ContainsKey(uint key, bool main = true) + => main ? _mainItems.ContainsKey(key) : _offItems.ContainsKey(key); + + public bool TryGetValue(uint key, out EquipItem value) + { + if (_mainItems.TryGetValue(key, out var v)) + { + value = v; + return true; + } + + value = default; + return false; + } + + public IEnumerable<(uint, EquipItem)> AllItems(bool main) + => (main ? _mainItems : _offItems).Select(i => (i.Key, (EquipItem)i.Value)); + + public bool TryGetValue(uint key, bool main, out EquipItem value) + { + var dict = main ? _mainItems : _offItems; + if (dict.TryGetValue(key, out var v)) + { + value = v; + return true; + } + + value = default; + return false; + } + public IEnumerable Keys => Enum.GetValues().Skip(1); public IEnumerable> Values - => _items.Skip(1); + => _byType.Skip(1).Select(l => (IReadOnlyList)new EquipItemList(l)); + + private readonly struct EquipItemList : IReadOnlyList + { + private readonly IReadOnlyList _items; + + public EquipItemList(IReadOnlyList items) + => _items = items; + + public IEnumerator GetEnumerator() + => _items.Select(i => (EquipItem)i).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _items.Count; + + public EquipItem this[int index] + => _items[index]; + } } diff --git a/Penumbra.GameData/Data/WeaponIdentificationList.cs b/Penumbra.GameData/Data/WeaponIdentificationList.cs index a5e4ddf8..8f7bb131 100644 --- a/Penumbra.GameData/Data/WeaponIdentificationList.cs +++ b/Penumbra.GameData/Data/WeaponIdentificationList.cs @@ -6,10 +6,11 @@ using Dalamud.Plugin; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using PseudoEquipItem = System.ValueTuple; namespace Penumbra.GameData.Data; -internal sealed class WeaponIdentificationList : KeyList +internal sealed class WeaponIdentificationList : KeyList { private const string Tag = "WeaponIdentification"; private const int Version = 1; @@ -19,16 +20,16 @@ internal sealed class WeaponIdentificationList : KeyList { } public IEnumerable Between(SetId modelId) - => Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF)); + => Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF)).Select(e => (EquipItem)e); public IEnumerable Between(SetId modelId, WeaponType type, byte variant = 0) { if (type == 0) - return Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF)); + return Between(ToKey(modelId, 0, 0), ToKey(modelId, 0xFFFF, 0xFF)).Select(e => (EquipItem)e); if (variant == 0) - return Between(ToKey(modelId, type, 0), ToKey(modelId, type, 0xFF)); + return Between(ToKey(modelId, type, 0), ToKey(modelId, type, 0xFF)).Select(e => (EquipItem)e); - return Between(ToKey(modelId, type, variant), ToKey(modelId, type, variant)); + return Between(ToKey(modelId, type, variant), ToKey(modelId, type, variant)).Select(e => (EquipItem)e); } public void Dispose(DalamudPluginInterface pi, ClientLanguage language) @@ -40,7 +41,7 @@ internal sealed class WeaponIdentificationList : KeyList public static ulong ToKey(EquipItem i) => ToKey(i.ModelId, i.WeaponType, i.Variant); - protected override IEnumerable ToKeys(EquipItem data) + protected override IEnumerable ToKeys(PseudoEquipItem data) { yield return ToKey(data); } @@ -48,20 +49,20 @@ internal sealed class WeaponIdentificationList : KeyList protected override bool ValidKey(ulong key) => key != 0; - protected override int ValueKeySelector(EquipItem data) - => (int)data.Id; + protected override int ValueKeySelector(PseudoEquipItem data) + => (int)data.Item2; - private static IEnumerable CreateWeaponList(DataManager gameData, ClientLanguage language) + private static IEnumerable CreateWeaponList(DataManager gameData, ClientLanguage language) => gameData.GetExcelSheet(language)!.SelectMany(ToEquipItems); - private static IEnumerable ToEquipItems(Item item) + private static IEnumerable ToEquipItems(Item item) { if ((EquipSlot)item.EquipSlotCategory.Row is not (EquipSlot.MainHand or EquipSlot.OffHand or EquipSlot.BothHand)) yield break; if (item.ModelMain != 0) - yield return EquipItem.FromMainhand(item); + yield return (PseudoEquipItem)EquipItem.FromMainhand(item); if (item.ModelSub != 0) - yield return EquipItem.FromOffhand(item); + yield return (PseudoEquipItem)EquipItem.FromOffhand(item); } } diff --git a/Penumbra.GameData/Enums/FullEquipType.cs b/Penumbra.GameData/Enums/FullEquipType.cs index a45d0800..6220b1b8 100644 --- a/Penumbra.GameData/Enums/FullEquipType.cs +++ b/Penumbra.GameData/Enums/FullEquipType.cs @@ -403,4 +403,7 @@ public static class FullEquipTypeExtensions public static readonly IReadOnlyList AccessoryTypes = Enum.GetValues().Where(v => v.IsAccessory()).ToArray(); + + public static readonly IReadOnlyList OffhandTypes + = Enum.GetValues().Where(v => v.OffhandTypeSuffix().Length > 0).ToArray(); } diff --git a/Penumbra.GameData/Structs/EquipItem.cs b/Penumbra.GameData/Structs/EquipItem.cs index 78d73870..718ea2ad 100644 --- a/Penumbra.GameData/Structs/EquipItem.cs +++ b/Penumbra.GameData/Structs/EquipItem.cs @@ -2,6 +2,7 @@ using Dalamud.Utility; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; +using PseudoEquipItem = System.ValueTuple; namespace Penumbra.GameData.Structs; @@ -15,7 +16,6 @@ public readonly struct EquipItem public readonly WeaponType WeaponType; public readonly byte Variant; public readonly FullEquipType Type; - public readonly EquipSlot Slot; public bool Valid => Type != FullEquipType.Unknown; @@ -35,8 +35,7 @@ public readonly struct EquipItem public EquipItem() => Name = string.Empty; - public EquipItem(string name, uint id, ushort iconId, SetId modelId, WeaponType weaponType, byte variant, FullEquipType type, - EquipSlot slot) + public EquipItem(string name, uint id, ushort iconId, SetId modelId, WeaponType weaponType, byte variant, FullEquipType type) { Name = string.Intern(name); Id = id; @@ -45,20 +44,24 @@ public readonly struct EquipItem WeaponType = weaponType; Variant = variant; Type = type; - Slot = slot; } + public static implicit operator EquipItem(PseudoEquipItem it) + => new(it.Item1, it.Item2, it.Item3, it.Item4, it.Item5, it.Item6, (FullEquipType)it.Item7); + + public static explicit operator PseudoEquipItem(EquipItem it) + => (it.Name, it.Id, it.IconId, (ushort)it.ModelId, (ushort)it.WeaponType, it.Variant, (byte)it.Type); + public static EquipItem FromArmor(Item item) { var type = item.ToEquipType(); - var slot = type.ToSlot(); var name = item.Name.ToDalamudString().TextValue; var id = item.RowId; var icon = item.Icon; var model = (SetId)item.ModelMain; var weapon = (WeaponType)0; var variant = (byte)(item.ModelMain >> 16); - return new EquipItem(name, id, icon, model, weapon, variant, type, slot); + return new EquipItem(name, id, icon, model, weapon, variant, type); } public static EquipItem FromMainhand(Item item) @@ -70,7 +73,7 @@ public readonly struct EquipItem var model = (SetId)item.ModelMain; var weapon = (WeaponType)(item.ModelMain >> 16); var variant = (byte)(item.ModelMain >> 32); - return new EquipItem(name, id, icon, model, weapon, variant, type, EquipSlot.MainHand); + return new EquipItem(name, id, icon, model, weapon, variant, type); } public static EquipItem FromOffhand(Item item) @@ -82,6 +85,6 @@ public readonly struct EquipItem var model = (SetId)item.ModelSub; var weapon = (WeaponType)(item.ModelSub >> 16); var variant = (byte)(item.ModelSub >> 32); - return new EquipItem(name, id, icon, model, weapon, variant, type, EquipSlot.OffHand); + return new EquipItem(name, id, icon, model, weapon, variant, type); } } diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index 902f5e08..3d444e1f 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -242,10 +242,10 @@ public static class EquipmentSwap private static void LookupItem(EquipItem i, out EquipSlot slot, out SetId modelId, out byte variant) { - if (!i.Slot.IsEquipmentPiece()) + slot = i.Type.ToSlot(); + if (!slot.IsEquipmentPiece()) throw new ItemSwap.InvalidItemTypeException(); - slot = i.Slot; modelId = i.ModelId; variant = i.Variant; } diff --git a/Penumbra/Services/ServiceWrapper.cs b/Penumbra/Services/ServiceWrapper.cs index 5a43cf9a..783acc49 100644 --- a/Penumbra/Services/ServiceWrapper.cs +++ b/Penumbra/Services/ServiceWrapper.cs @@ -5,7 +5,7 @@ using Penumbra.Util; namespace Penumbra.Services; -public abstract class SyncServiceWrapper +public abstract class SyncServiceWrapper : IDisposable { public string Name { get; } public T Service { get; } @@ -34,7 +34,7 @@ public abstract class SyncServiceWrapper } } -public abstract class AsyncServiceWrapper +public abstract class AsyncServiceWrapper : IDisposable { public string Name { get; } public T? Service { get; private set; } diff --git a/Penumbra/UI/ChangedItemDrawer.cs b/Penumbra/UI/ChangedItemDrawer.cs index 40c4e72a..11d044b9 100644 --- a/Penumbra/UI/ChangedItemDrawer.cs +++ b/Penumbra/UI/ChangedItemDrawer.cs @@ -219,7 +219,7 @@ public class ChangedItemDrawer : IDisposable switch (obj) { case EquipItem it: - iconType = it.Slot switch + iconType = it.Type.ToSlot() switch { EquipSlot.MainHand => ChangedItemIcon.Mainhand, EquipSlot.OffHand => ChangedItemIcon.Offhand,