diff --git a/Glamourer/Events/EquipmentLoading.cs b/Glamourer/Events/EquipmentLoading.cs
deleted file mode 100644
index 93d2656..0000000
--- a/Glamourer/Events/EquipmentLoading.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using Glamourer.Interop.Structs;
-using OtterGui.Classes;
-using Penumbra.GameData.Enums;
-using Penumbra.GameData.Structs;
-
-namespace Glamourer.Events;
-
-///
-/// Triggered when a game object updates an equipment piece in its model data.
-///
-/// - Parameter is the character updating.
-/// - Parameter is the equipment slot changed.
-/// - Parameter is the model values to change the equipment piece to.
-///
-///
-public sealed class EquipmentLoading : EventWrapper, EquipmentLoading.Priority>
-{
- public enum Priority
- {
- ///
- StateListener = 0,
- }
-
- public EquipmentLoading()
- : base(nameof(EquipmentLoading))
- { }
-
- public void Invoke(Actor actor, EquipSlot slot, CharacterArmor armor)
- => Invoke(this, actor, slot, armor);
-}
diff --git a/Glamourer/Events/MovedEquipment.cs b/Glamourer/Events/MovedEquipment.cs
new file mode 100644
index 0000000..4548575
--- /dev/null
+++ b/Glamourer/Events/MovedEquipment.cs
@@ -0,0 +1,28 @@
+using System;
+using OtterGui.Classes;
+using Penumbra.GameData.Enums;
+using Penumbra.GameData.Structs;
+
+namespace Glamourer.Events;
+
+///
+/// Triggered when a game object updates an equipment piece in its model data.
+///
+/// - Parameter is an array of slots updated and corresponding item ids and stains.
+///
+///
+public sealed class MovedEquipment : EventWrapper, MovedEquipment.Priority>
+{
+ public enum Priority
+ {
+ ///
+ StateListener = 0,
+ }
+
+ public MovedEquipment()
+ : base(nameof(MovedEquipment))
+ { }
+
+ public void Invoke((EquipSlot, uint, StainId)[] items)
+ => Invoke(this, items);
+}
diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
index 200cca6..2d47a34 100644
--- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
+++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
@@ -20,8 +20,7 @@ public partial class CustomizationDrawer
using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
{
- // Print 1-based index instead of 0.
- if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
+ if (ImGui.ColorButton($"{current}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
ImGui.OpenPopup(ColorPickerPopupName);
}
@@ -65,7 +64,7 @@ public partial class CustomizationDrawer
for (var i = 0; i < _currentCount; ++i)
{
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]);
- if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
+ if (ImGui.ColorButton(i.ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
{
UpdateValue(custom.Value);
ImGui.CloseCurrentPopup();
diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs
index 92e2c8f..1f8034a 100644
--- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs
+++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs
@@ -31,6 +31,9 @@ public class EquipmentDrawer
private readonly TextureService _textures;
private readonly Configuration _config;
+ private float _requiredComboWidthUnscaled;
+ private float _requiredComboWidth;
+
public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config)
{
_items = items;
@@ -60,6 +63,13 @@ public class EquipmentDrawer
{
_iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y);
_comboLength = DefaultWidth * ImGuiHelpers.GlobalScale;
+ if (_requiredComboWidthUnscaled == 0)
+ {
+ _requiredComboWidthUnscaled = _items.ItemService.AwaitedService.AllItems(true).Concat(_items.ItemService.AwaitedService.AllItems(false))
+ .Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) / ImGuiHelpers.GlobalScale;
+ }
+
+ _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale;
}
private bool VerifyRestrictedGear(EquipSlot slot, EquipItem gear, Gender gender, Race race)
@@ -171,7 +181,7 @@ public class EquipmentDrawer
label = combo.Label;
using var disabled = ImRaii.Disabled(locked);
- if (!combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength))
+ if (!combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth))
return false;
weapon = combo.CurrentSelection;
@@ -189,7 +199,7 @@ public class EquipmentDrawer
label = combo.Label;
using var disabled = ImRaii.Disabled(locked);
- var change = combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength);
+ var change = combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth);
if (change)
weapon = combo.CurrentSelection;
@@ -225,7 +235,7 @@ public class EquipmentDrawer
label = combo.Label;
armor = current;
using var disabled = ImRaii.Disabled(locked);
- var change = combo.Draw(armor.Name, armor.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength);
+ var change = combo.Draw(armor.Name, armor.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth);
if (change)
armor = combo.CurrentSelection;
diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs
index 9052894..3ced7c4 100644
--- a/Glamourer/Gui/Equipment/ItemCombo.cs
+++ b/Glamourer/Gui/Equipment/ItemCombo.cs
@@ -19,6 +19,7 @@ public sealed class ItemCombo : FilterComboCache
public readonly string Label;
private uint _currentItem;
+ private float _innerWidth;
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot, TextureService textures)
: base(() => GetItems(items, slot))
@@ -46,12 +47,16 @@ public sealed class ItemCombo : FilterComboCache
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
- public bool Draw(string previewName, uint previewIdx, float width)
+ public bool Draw(string previewName, uint previewIdx, float width, float innerWidth)
{
+ _innerWidth = innerWidth;
_currentItem = previewIdx;
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
+ protected override float GetFilterWidth()
+ => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X;
+
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var obj = Items[globalIdx];
diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs
index 5d6085a..75d2adb 100644
--- a/Glamourer/Gui/Equipment/WeaponCombo.cs
+++ b/Glamourer/Gui/Equipment/WeaponCombo.cs
@@ -16,6 +16,7 @@ public sealed class WeaponCombo : FilterComboCache
{
public readonly string Label;
private uint _currentItemId;
+ private float _innerWidth;
public WeaponCombo(ItemManager items, FullEquipType type)
: base(() => GetWeapons(items, type))
@@ -41,9 +42,13 @@ public sealed class WeaponCombo : FilterComboCache
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
- public bool Draw(string previewName, uint previewId, float width)
+ protected override float GetFilterWidth()
+ => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X;
+
+ public bool Draw(string previewName, uint previewId, float width, float innerWidth)
{
_currentItemId = previewId;
+ _innerWidth = innerWidth;
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs
index fe93876..62e9ba2 100644
--- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs
+++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs
@@ -162,7 +162,7 @@ public class PenumbraChangedItemTooltip : IDisposable
switch (type)
{
case ChangedItemType.Item:
- if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item))
+ if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item))
return;
CreateTooltip(item, "[Glamourer] ", false);
@@ -192,7 +192,7 @@ public class PenumbraChangedItemTooltip : IDisposable
if (!Player(out var state))
return;
- if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item))
+ if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item))
return;
ApplyItem(state, item);
diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs
index 718271f..8392b57 100644
--- a/Glamourer/Gui/Tabs/DebugTab.cs
+++ b/Glamourer/Gui/Tabs/DebugTab.cs
@@ -9,6 +9,7 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using Dalamud.Plugin;
+using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Api;
using Glamourer.Automation;
@@ -42,6 +43,7 @@ public unsafe class DebugTab : ITab
private readonly UpdateSlotService _updateSlotService;
private readonly WeaponService _weaponService;
private readonly MetaService _metaService;
+ private readonly InventoryService _inventoryService;
private readonly PenumbraService _penumbra;
private readonly ObjectTable _objects;
private readonly ObjectManager _objectManager;
@@ -76,7 +78,7 @@ public unsafe class DebugTab : ITab
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
- ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService)
+ ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService)
{
_changeCustomizeService = changeCustomizeService;
_visorService = visorService;
@@ -103,6 +105,7 @@ public unsafe class DebugTab : ITab
_itemUnlocks = itemUnlocks;
_designConverter = designConverter;
_datFileService = datFileService;
+ _inventoryService = inventoryService;
}
public ReadOnlySpan Label
@@ -120,6 +123,7 @@ public unsafe class DebugTab : ITab
DrawDesigns();
DrawState();
DrawAutoDesigns();
+ DrawInventory();
DrawUnlocks();
DrawIpc();
}
@@ -860,7 +864,7 @@ public unsafe class DebugTab : ITab
if (!table)
return;
- foreach(var (index, value) in set.NpcOptions)
+ foreach (var (index, value) in set.NpcOptions)
{
ImGuiUtil.DrawTableColumn(index.ToString());
ImGuiUtil.DrawTableColumn(value.Value.ToString());
@@ -980,6 +984,7 @@ public unsafe class DebugTab : ITab
ImGui.SameLine();
}
+
ImGui.NewLine();
}
@@ -996,6 +1001,7 @@ public unsafe class DebugTab : ITab
ImGui.SameLine();
}
+
ImGui.NewLine();
}
}
@@ -1442,7 +1448,7 @@ public unsafe class DebugTab : ITab
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlocked, skips, t =>
{
ImGuiUtil.DrawTableColumn(t.Key.ToString());
- if (_items.ItemService.AwaitedService.TryGetValue(t.Key, out var equip))
+ if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip))
{
ImGuiUtil.DrawTableColumn(equip.Name);
ImGuiUtil.DrawTableColumn(equip.Type.ToName());
@@ -1489,7 +1495,7 @@ public unsafe class DebugTab : ITab
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t =>
{
ImGuiUtil.DrawTableColumn(t.Key.ToString());
- if (_items.ItemService.AwaitedService.TryGetValue(t.Key, out var equip))
+ if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip))
{
ImGuiUtil.DrawTableColumn(equip.Name);
ImGuiUtil.DrawTableColumn(equip.Type.ToName());
@@ -1514,6 +1520,50 @@ public unsafe class DebugTab : ITab
#endregion
+ #region Inventory
+
+ private void DrawInventory()
+ {
+ if (!ImGui.CollapsingHeader("Inventory"))
+ return;
+
+ var inventory = InventoryManager.Instance();
+ if (inventory == null)
+ return;
+
+ ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}");
+
+ var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems);
+ if (equip == null || equip->Loaded == 0)
+ return;
+
+ ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}");
+
+ using var table = ImRaii.Table("items", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ for (var i = 0; i < equip->Size; ++i)
+ {
+ ImGuiUtil.DrawTableColumn(i.ToString());
+ var item = equip->GetInventorySlot(i);
+ if (item == null)
+ {
+ ImGuiUtil.DrawTableColumn("NULL");
+ ImGui.TableNextRow();
+ }
+ else
+ {
+ ImGuiUtil.DrawTableColumn(item->ItemID.ToString());
+ ImGuiUtil.DrawTableColumn(item->GlamourID.ToString());
+ ImGui.TableNextColumn();
+ ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}");
+ }
+ }
+ }
+
+ #endregion
+
#region IPC
private string _gameObjectName = string.Empty;
diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs
index 2efa4d8..4a51af8 100644
--- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs
+++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs
@@ -179,7 +179,7 @@ public class UnlockOverview
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
if (item.Type.ValidOffhand().IsOffhandType())
ImGui.TextUnformatted(
- $"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.ItemId, false, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}");
+ $"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}");
else
ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}");
ImGui.TextUnformatted(
diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs
index e0a6f11..8473ce7 100644
--- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs
+++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs
@@ -242,7 +242,7 @@ public class UnlockTable : Table, IDisposable
ImGuiUtil.RightAlign(item.ModelString);
if (ImGui.IsItemHovered()
&& item.Type.ValidOffhand().IsOffhandType()
- && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, false, out var offhand))
+ && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
{
using var tt = ImRaii.Tooltip();
ImGui.TextUnformatted("Offhand: " + offhand.ModelString);
@@ -260,7 +260,7 @@ public class UnlockTable : Table, IDisposable
if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase))
return true;
- if (item.Type.ValidOffhand().IsOffhandType() && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, false, out var offhand))
+ if (item.Type.ValidOffhand().IsOffhandType() && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
return FilterRegex?.IsMatch(offhand.ModelString)
?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase);
diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs
new file mode 100644
index 0000000..fd56364
--- /dev/null
+++ b/Glamourer/Interop/InventoryService.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using Dalamud.Hooking;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.UI.Misc;
+using Glamourer.Events;
+using Penumbra.GameData.Enums;
+using Penumbra.GameData.Structs;
+
+namespace Glamourer.Interop;
+
+public unsafe class InventoryService : IDisposable
+{
+ private readonly MovedEquipment _event;
+ private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12);
+
+ public InventoryService(MovedEquipment @event)
+ {
+ _event = @event;
+ _moveItemHook = Hook.FromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
+ _equipGearsetHook =
+ Hook.FromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour);
+ _moveItemHook.Enable();
+ _equipGearsetHook.Enable();
+ }
+
+ public void Dispose()
+ {
+ _moveItemHook.Dispose();
+ _equipGearsetHook.Dispose();
+ }
+
+ private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId);
+
+ private readonly Hook _equipGearsetHook;
+
+ private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId)
+ {
+ var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId);
+ Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})");
+ if (ret == 0)
+ {
+ var entry = module->GetGearset(gearsetId);
+ if (entry == null)
+ return ret;
+
+ if (glamourPlateId == 0)
+ glamourPlateId = entry->GlamourSetLink;
+
+ _itemList.Clear();
+
+
+ if (glamourPlateId != 0)
+ {
+ void Add(EquipSlot slot, uint glamourId, StainId glamourStain, ref RaptureGearsetModule.GearsetItem item)
+ {
+ if (item.ItemID == 0)
+ _itemList.Add((slot, 0, 0));
+ else if (glamourId != 0)
+ _itemList.Add((slot, glamourId, glamourStain));
+ else if (item.GlamourId != 0)
+ _itemList.Add((slot, item.GlamourId, item.Stain));
+ else
+ _itemList.Add((slot, item.ItemID, item.Stain));
+ }
+
+ var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1];
+ Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->MainHand);
+ Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[10], ref entry->OffHand);
+ Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->Head);
+ Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->Body);
+ Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->Hands);
+ Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->Legs);
+ Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->Feet);
+ Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->Ears);
+ Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->Neck);
+ Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->Wrists);
+ Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->RingRight);
+ Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->RightLeft);
+ }
+ else
+ {
+ void Add(EquipSlot slot, ref RaptureGearsetModule.GearsetItem item)
+ {
+ if (item.ItemID == 0)
+ _itemList.Add((slot, 0, 0));
+ else if (item.GlamourId != 0)
+ _itemList.Add((slot, item.GlamourId, item.Stain));
+ else
+ _itemList.Add((slot, item.ItemID, item.Stain));
+ }
+
+ Add(EquipSlot.MainHand, ref entry->MainHand);
+ Add(EquipSlot.OffHand, ref entry->OffHand);
+ Add(EquipSlot.Head, ref entry->Head);
+ Add(EquipSlot.Body, ref entry->Body);
+ Add(EquipSlot.Hands, ref entry->Hands);
+ Add(EquipSlot.Legs, ref entry->Legs);
+ Add(EquipSlot.Feet, ref entry->Feet);
+ Add(EquipSlot.Ears, ref entry->Ears);
+ Add(EquipSlot.Neck, ref entry->Neck);
+ Add(EquipSlot.Wrists, ref entry->Wrists);
+ Add(EquipSlot.RFinger, ref entry->RingRight);
+ Add(EquipSlot.LFinger, ref entry->RightLeft);
+ }
+
+ _event.Invoke(_itemList.ToArray());
+ }
+
+ return ret;
+ }
+
+ private delegate int MoveItemDelegate(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot,
+ InventoryType targetContainer, ushort targetSlot, byte unk);
+
+ private readonly Hook _moveItemHook;
+
+ private int MoveItemDetour(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot,
+ InventoryType targetContainer, ushort targetSlot, byte unk)
+ {
+ var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk);
+ Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})");
+ if (ret == 0)
+ {
+ if (InvokeSource(sourceContainer, sourceSlot, out var source))
+ if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
+ _event.Invoke(new[]
+ {
+ source,
+ target,
+ });
+ else
+ _event.Invoke(new[]
+ {
+ source,
+ });
+ else if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
+ _event.Invoke(new[]
+ {
+ target,
+ });
+ }
+
+ return ret;
+ }
+
+ private static bool InvokeSource(InventoryType sourceContainer, uint sourceSlot, out (EquipSlot, uint, StainId) tuple)
+ {
+ tuple = default;
+ if (sourceContainer is not InventoryType.EquippedItems)
+ return false;
+
+ var slot = GetSlot(sourceSlot);
+ if (slot is EquipSlot.Unknown)
+ return false;
+
+ tuple = (slot, 0u, 0);
+ return true;
+ }
+
+ private static bool InvokeTarget(InventoryManager* manager, InventoryType targetContainer, uint targetSlot,
+ out (EquipSlot, uint, StainId) tuple)
+ {
+ tuple = default;
+ if (targetContainer is not InventoryType.EquippedItems)
+ return false;
+
+ var slot = GetSlot(targetSlot);
+ if (slot is EquipSlot.Unknown)
+ return false;
+
+ // Invoked after calling Original, so the item is already moved.
+ var inventory = manager->GetInventoryContainer(targetContainer);
+ if (inventory == null || inventory->Loaded == 0 || inventory->Size <= targetSlot)
+ return false;
+
+ var item = inventory->GetInventorySlot((int)targetSlot);
+ if (item == null)
+ return false;
+
+ tuple = (slot, item->GlamourID != 0 ? item->GlamourID : item->ItemID, item->Stain);
+ return true;
+ }
+
+ private static EquipSlot GetSlot(uint slot)
+ => slot switch
+ {
+ 0 => EquipSlot.MainHand,
+ 1 => EquipSlot.OffHand,
+ 2 => EquipSlot.Head,
+ 3 => EquipSlot.Body,
+ 4 => EquipSlot.Hands,
+ 6 => EquipSlot.Legs,
+ 7 => EquipSlot.Feet,
+ 8 => EquipSlot.Ears,
+ 9 => EquipSlot.Neck,
+ 10 => EquipSlot.Wrists,
+ 11 => EquipSlot.RFinger,
+ 12 => EquipSlot.LFinger,
+ _ => EquipSlot.Unknown,
+ };
+}
diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs
index 95cbfaa..916b6fe 100644
--- a/Glamourer/Interop/UpdateSlotService.cs
+++ b/Glamourer/Interop/UpdateSlotService.cs
@@ -1,7 +1,6 @@
using System;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
-using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Penumbra.GameData.Enums;
@@ -11,24 +10,18 @@ namespace Glamourer.Interop;
public unsafe class UpdateSlotService : IDisposable
{
- public readonly SlotUpdating SlotUpdatingEvent;
- public readonly EquipmentLoading EquipmentLoadingEvent;
+ public readonly SlotUpdating SlotUpdatingEvent;
- public UpdateSlotService(SlotUpdating slotUpdating, EquipmentLoading equipmentLoadingEvent)
+ public UpdateSlotService(SlotUpdating slotUpdating)
{
- SlotUpdatingEvent = slotUpdating;
- EquipmentLoadingEvent = equipmentLoadingEvent;
+ SlotUpdatingEvent = slotUpdating;
SignatureHelper.Initialise(this);
_flagSlotForUpdateHook.Enable();
- _loadEquipmentHook =
- Hook.FromAddress((nint) DrawDataContainer.MemberFunctionPointers.LoadEquipment, LoadEquipmentDetour);
- _loadEquipmentHook.Enable();
}
public void Dispose()
{
_flagSlotForUpdateHook.Dispose();
- _loadEquipmentHook.Dispose();
}
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
@@ -53,10 +46,6 @@ public unsafe class UpdateSlotService : IDisposable
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook _flagSlotForUpdateHook = null!;
- private delegate void LoadEquipmentDelegateIntern(DrawDataContainer* drawDataContainer, uint slotIdx, CharacterArmor data, bool force);
-
- private readonly Hook _loadEquipmentHook = null!;
-
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{
var slot = slotIdx.ToEquipSlot();
@@ -66,14 +55,6 @@ public unsafe class UpdateSlotService : IDisposable
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
}
- private void LoadEquipmentDetour(DrawDataContainer* drawDataContainer, uint slotIdx, CharacterArmor data, bool force)
- {
- var slot = slotIdx.ToEquipSlot();
- EquipmentLoadingEvent.Invoke(drawDataContainer->Parent, slot, data);
- Glamourer.Log.Excessive($"[LoadEquipment] Called with 0x{(ulong)drawDataContainer:X} for slot {slot} with {data} ({force}).");
- _loadEquipmentHook.Original(drawDataContainer, slotIdx, data, force);
- }
-
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
}
diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs
index 9b3f7d8..dec17ea 100644
--- a/Glamourer/Interop/WeaponService.cs
+++ b/Glamourer/Interop/WeaponService.cs
@@ -4,10 +4,8 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events;
using Glamourer.Interop.Structs;
-using ImGuiNET;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
-using static FFXIVClientStructs.FFXIV.Client.UI.UIModule;
namespace Glamourer.Interop;
@@ -75,14 +73,14 @@ public unsafe class WeaponService : IDisposable
switch (slot)
{
case EquipSlot.MainHand:
- _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 0, 0, 1, 0);
+ _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
return;
case EquipSlot.OffHand:
- _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 0, 0, 1, 0);
+ _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0);
return;
case EquipSlot.BothHand:
- _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 0, 0, 1, 0);
- _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
+ _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
+ _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0);
return;
// function can also be called with '2', but does not seem to ever be.
}
diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs
index 1ccfcc3..dc2a1bf 100644
--- a/Glamourer/Services/ItemManager.cs
+++ b/Glamourer/Services/ItemManager.cs
@@ -74,7 +74,7 @@ public class ItemManager : IDisposable
if (itemId == SmallclothesId(slot))
return SmallClothesItem(slot);
- if (!ItemService.AwaitedService.TryGetValue(itemId, slot is not EquipSlot.OffHand, out var item))
+ if (!ItemService.AwaitedService.TryGetValue(itemId, slot, out var item))
return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0);
if (item.Type.ToSlot() != slot)
@@ -88,7 +88,7 @@ public class ItemManager : IDisposable
if (itemId == NothingId(type))
return NothingItem(type);
- if (!ItemService.AwaitedService.TryGetValue(itemId, type is FullEquipType.Shield, out var item))
+ if (!ItemService.AwaitedService.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, out var item))
return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0);
if (item.Type != type)
diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs
index 6a49232..2f62981 100644
--- a/Glamourer/Services/ServiceManager.cs
+++ b/Glamourer/Services/ServiceManager.cs
@@ -61,7 +61,6 @@ public static class ServiceManager
private static IServiceCollection AddEvents(this IServiceCollection services)
=> services.AddSingleton()
.AddSingleton()
- .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
@@ -69,7 +68,8 @@ public static class ServiceManager
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton();
+ .AddSingleton()
+ .AddSingleton();
private static IServiceCollection AddData(this IServiceCollection services)
=> services.AddSingleton()
@@ -91,7 +91,8 @@ public static class ServiceManager
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton();
+ .AddSingleton()
+ .AddSingleton();
private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton()
diff --git a/Glamourer/Services/ServiceWrapper.cs b/Glamourer/Services/ServiceWrapper.cs
index b8292a2..a0c150d 100644
--- a/Glamourer/Services/ServiceWrapper.cs
+++ b/Glamourer/Services/ServiceWrapper.cs
@@ -75,8 +75,8 @@ public abstract class AsyncServiceWrapper : IDisposable
public sealed class IdentifierService : AsyncServiceWrapper
{
- public IdentifierService(DalamudPluginInterface pi, DataManager data)
- : base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data))
+ public IdentifierService(DalamudPluginInterface pi, DataManager data, ItemService itemService)
+ : base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data, itemService.AwaitedService))
{ }
}
diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs
index 0e45427..1694d9f 100644
--- a/Glamourer/State/StateListener.cs
+++ b/Glamourer/State/StateListener.cs
@@ -2,6 +2,7 @@
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Events;
+using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services;
@@ -22,12 +23,12 @@ public class StateListener : IDisposable
{
private readonly Configuration _config;
private readonly ActorService _actors;
+ private readonly ObjectManager _objects;
private readonly StateManager _manager;
private readonly StateApplier _applier;
private readonly ItemManager _items;
private readonly PenumbraService _penumbra;
private readonly SlotUpdating _slotUpdating;
- private readonly EquipmentLoading _equipmentLoading;
private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState;
@@ -35,9 +36,11 @@ public class StateListener : IDisposable
private readonly AutoDesignApplier _autoDesignApplier;
private readonly FunModule _funModule;
private readonly HumanModelList _humans;
+ private readonly MovedEquipment _movedEquipment;
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
- private ActorState? _creatingState = null;
+ private ActorState? _creatingState;
+ private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
public bool Enabled
{
@@ -48,7 +51,7 @@ public class StateListener : IDisposable
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
- EquipmentLoading equipmentLoading, StateApplier applier)
+ StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects)
{
_manager = manager;
_items = items;
@@ -63,8 +66,9 @@ public class StateListener : IDisposable
_autoDesignApplier = autoDesignApplier;
_funModule = funModule;
_humans = humans;
- _equipmentLoading = equipmentLoading;
_applier = applier;
+ _movedEquipment = movedEquipment;
+ _objects = objects;
if (Enabled)
Subscribe();
@@ -168,40 +172,36 @@ public class StateListener : IDisposable
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
}
- ///
- /// The game object does not actually invoke changes when the model id is identical,
- /// so we need to handle that case too.
- ///
- private void OnEquipmentLoading(Actor actor, EquipSlot slot, CharacterArmor armor)
+ private void OnMovedEquipment((EquipSlot, uint, StainId)[] items)
{
- if (!actor.Model.Valid || armor != actor.GetArmor(slot))
+ _objects.Update();
+ var (identifier, objects) = _objects.PlayerData;
+ if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var state))
return;
- if (!actor.Identifier(_actors.AwaitedService, out var identifier)
- || !_manager.TryGetValue(identifier, out var state)
- || !state.BaseData.IsHuman)
- return;
-
- if (state.ModelData.Armor(slot) == armor)
- return;
-
- var setItem = state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc;
- var setStain = state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc;
- switch (setItem, setStain)
+ foreach (var (slot, item, stain) in items)
{
- case (true, true):
- _manager.ChangeEquip(state, slot, state.BaseData.Item(slot), state.BaseData.Stain(slot), StateChanged.Source.Manual);
- state[slot, false] = StateChanged.Source.Game;
- state[slot, true] = StateChanged.Source.Game;
- break;
- case (true, false):
- _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Manual);
- state[slot, false] = StateChanged.Source.Game;
- break;
- case (false, true):
- _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Manual);
- state[slot, true] = StateChanged.Source.Game;
- break;
+ var currentItem = state.BaseData.Item(slot);
+ var model = state.ModelData.Weapon(slot);
+ var current = currentItem.Weapon(state.BaseData.Stain(slot));
+ if (model.Value == current.Value || !_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var changedItem))
+ continue;
+
+ var changed = changedItem.Weapon(stain);
+ if (current.Value == changed.Value && state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
+ {
+ _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game);
+ switch (slot)
+ {
+ case EquipSlot.MainHand:
+ case EquipSlot.OffHand:
+ _applier.ChangeWeapon(objects, slot, currentItem, stain);
+ break;
+ default:
+ _applier.ChangeArmor(objects, slot, current.ToArmor(), state.ModelData.IsHatVisible());
+ break;
+ }
+ }
}
}
@@ -212,6 +212,13 @@ public class StateListener : IDisposable
///
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref weapon)
{
+ // Fist weapon gauntlet hack.
+ if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Set.Value != 0 && _lastFistOffhand.Set.Value != 0)
+ {
+ weapon.Value = _lastFistOffhand;
+ _lastFistOffhand = CharacterWeapon.Empty;
+ }
+
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|| !_manager.TryGetValue(identifier, out var state))
return;
@@ -229,7 +236,7 @@ public class StateListener : IDisposable
else
apply = true;
- if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
+ if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
else
apply = true;
@@ -249,6 +256,11 @@ public class StateListener : IDisposable
else if (actorWeapon.Set.Value != 0)
actorWeapon = actorWeapon.With(newWeapon.Stain);
}
+
+ // Fist Weapon Offhand hack.
+ if (slot is EquipSlot.MainHand && weapon.Value.Set.Value is > 1600 and < 1651)
+ _lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Value + 50), weapon.Value.Type, weapon.Value.Variant,
+ weapon.Value.Stain);
}
/// Update base data for a single changed equipment slot.
@@ -257,7 +269,7 @@ public class StateListener : IDisposable
var actorArmor = actor.GetArmor(slot);
// The actor armor does not correspond to the model armor, thus the actor is transformed.
// This also prevents it from changing values due to hat state.
- if (actorArmor.Value != armor.Value)
+ if (actorArmor.Value != armor.Value && armor.Set.Value != actor.GetOffhand().Set.Value)
return UpdateState.Transformed;
var baseData = state.BaseData.Armor(slot);
@@ -491,7 +503,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
- _equipmentLoading.Subscribe(OnEquipmentLoading, EquipmentLoading.Priority.StateListener);
+ _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
@@ -503,7 +515,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
_slotUpdating.Unsubscribe(OnSlotUpdating);
- _equipmentLoading.Unsubscribe(OnEquipmentLoading);
+ _movedEquipment.Unsubscribe(OnMovedEquipment);
_weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange);
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
@@ -515,7 +527,7 @@ public class StateListener : IDisposable
if (_creatingState == null)
return;
- _applier.ChangeHatState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsHatVisible());
+ _applier.ChangeHatState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsHatVisible());
_applier.ChangeWeaponState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWeaponVisible());
}
}
diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs
index dff7868..bfa3e4c 100644
--- a/Glamourer/State/StateManager.cs
+++ b/Glamourer/State/StateManager.cs
@@ -169,6 +169,7 @@ public class StateManager : IReadOnlyDictionary
main = actor.GetMainhand();
off = actor.GetOffhand();
+ FistWeaponHack(ref ret, ref main, ref off);
ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
}
@@ -190,6 +191,19 @@ public class StateManager : IReadOnlyDictionary
return ret;
}
+ /// This is hardcoded in the game.
+ private void FistWeaponHack(ref DesignData ret, ref CharacterWeapon mainhand, ref CharacterWeapon offhand)
+ {
+ if (mainhand.Set.Value is < 1601 or >= 1651)
+ return;
+
+ var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, 0, (byte)offhand.Variant);
+ offhand.Set = (SetId)(mainhand.Set.Value + 50);
+ offhand.Variant = mainhand.Variant;
+ offhand.Type = mainhand.Type;
+ ret.SetItem(EquipSlot.Hands, gauntlets);
+ }
+
#region Change Values
/// Turn an actor human.
@@ -433,7 +447,8 @@ public class StateManager : IReadOnlyDictionary
if (!GetOrCreate(actor, out var state))
return;
- ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
+ ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
+ false);
}
public void DeleteState(ActorIdentifier identifier)
diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs
index 68acd5b..6e2bed1 100644
--- a/Glamourer/Unlocks/ItemUnlockManager.cs
+++ b/Glamourer/Unlocks/ItemUnlockManager.cs
@@ -107,7 +107,7 @@ public class ItemUnlockManager : ISavable, IDisposable
private bool AddItem(uint itemId, long time)
{
itemId = HandleHq(itemId);
- if (!_items.ItemService.AwaitedService.TryGetValue(itemId, out var equip) || !_unlocked.TryAdd(equip.ItemId, time))
+ if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var equip) || !_unlocked.TryAdd(equip.ItemId, time))
return false;
_event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time));
@@ -278,7 +278,7 @@ public class ItemUnlockManager : ISavable, IDisposable
private void Load()
{
var version = UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked,
- id => _items.ItemService.AwaitedService.TryGetValue(id, out _), "item");
+ id => _items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out _), "item");
UpdateModels(version);
}
@@ -291,7 +291,7 @@ public class ItemUnlockManager : ISavable, IDisposable
var cabinet = gameData.GetExcelSheet()!;
foreach (var row in cabinet)
{
- if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item))
+ if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item))
ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet));
}
@@ -299,7 +299,7 @@ public class ItemUnlockManager : ISavable, IDisposable
var gilShop = gameData.GetExcelSheet()!;
foreach (var row in gilShopItem)
{
- if (!items.ItemService.AwaitedService.TryGetValue(row.Item.Row, out var item))
+ if (!items.ItemService.AwaitedService.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item))
continue;
var quest1 = row.QuestRequired[0].Row;
@@ -332,7 +332,7 @@ public class ItemUnlockManager : ISavable, IDisposable
foreach (var (item, time) in _unlocked.ToArray())
{
- if (!_items.ItemService.AwaitedService.TryGetValue(item, out var equip))
+ if (!_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var equip))
continue;
var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot());