Add handling for Monk fist weapon hack, remove LoadEquipment hooks, and add item movement and plate apply hooks instead.

This commit is contained in:
Ottermandias 2023-07-22 02:16:54 +02:00
parent 323924fba2
commit 5874688838
19 changed files with 403 additions and 128 deletions

View file

@ -1,31 +0,0 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a game object updates an equipment piece in its model data.
/// <list type="number">
/// <item>Parameter is the character updating. </item>
/// <item>Parameter is the equipment slot changed. </item>
/// <item>Parameter is the model values to change the equipment piece to. </item>
/// </list>
/// </summary>
public sealed class EquipmentLoading : EventWrapper<Action<Actor, EquipSlot, CharacterArmor>, EquipmentLoading.Priority>
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnEquipmentLoading"/>
StateListener = 0,
}
public EquipmentLoading()
: base(nameof(EquipmentLoading))
{ }
public void Invoke(Actor actor, EquipSlot slot, CharacterArmor armor)
=> Invoke(this, actor, slot, armor);
}

View file

@ -0,0 +1,28 @@
using System;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a game object updates an equipment piece in its model data.
/// <list type="number">
/// <item>Parameter is an array of slots updated and corresponding item ids and stains. </item>
/// </list>
/// </summary>
public sealed class MovedEquipment : EventWrapper<Action<(EquipSlot, uint, StainId)[]>, MovedEquipment.Priority>
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnMovedEquipment"/>
StateListener = 0,
}
public MovedEquipment()
: base(nameof(MovedEquipment))
{ }
public void Invoke((EquipSlot, uint, StainId)[] items)
=> Invoke(this, items);
}

View file

@ -20,8 +20,7 @@ public partial class CustomizationDrawer
using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
{ {
// Print 1-based index instead of 0. if (ImGui.ColorButton($"{current}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
ImGui.OpenPopup(ColorPickerPopupName); ImGui.OpenPopup(ColorPickerPopupName);
} }
@ -65,7 +64,7 @@ public partial class CustomizationDrawer
for (var i = 0; i < _currentCount; ++i) for (var i = 0; i < _currentCount; ++i)
{ {
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]); 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); UpdateValue(custom.Value);
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();

View file

@ -31,6 +31,9 @@ public class EquipmentDrawer
private readonly TextureService _textures; private readonly TextureService _textures;
private readonly Configuration _config; private readonly Configuration _config;
private float _requiredComboWidthUnscaled;
private float _requiredComboWidth;
public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config) public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config)
{ {
_items = items; _items = items;
@ -60,6 +63,13 @@ public class EquipmentDrawer
{ {
_iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y); _iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y);
_comboLength = DefaultWidth * ImGuiHelpers.GlobalScale; _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) private bool VerifyRestrictedGear(EquipSlot slot, EquipItem gear, Gender gender, Race race)
@ -171,7 +181,7 @@ public class EquipmentDrawer
label = combo.Label; label = combo.Label;
using var disabled = ImRaii.Disabled(locked); 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; return false;
weapon = combo.CurrentSelection; weapon = combo.CurrentSelection;
@ -189,7 +199,7 @@ public class EquipmentDrawer
label = combo.Label; label = combo.Label;
using var disabled = ImRaii.Disabled(locked); 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) if (change)
weapon = combo.CurrentSelection; weapon = combo.CurrentSelection;
@ -225,7 +235,7 @@ public class EquipmentDrawer
label = combo.Label; label = combo.Label;
armor = current; armor = current;
using var disabled = ImRaii.Disabled(locked); 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) if (change)
armor = combo.CurrentSelection; armor = combo.CurrentSelection;

View file

@ -19,6 +19,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
public readonly string Label; public readonly string Label;
private uint _currentItem; private uint _currentItem;
private float _innerWidth;
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot, TextureService textures) public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot, TextureService textures)
: base(() => GetItems(items, slot)) : base(() => GetItems(items, slot))
@ -46,12 +47,16 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
return base.UpdateCurrentSelected(CurrentSelectionIdx); 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; _currentItem = previewIdx;
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); 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) protected override bool DrawSelectable(int globalIdx, bool selected)
{ {
var obj = Items[globalIdx]; var obj = Items[globalIdx];

View file

@ -16,6 +16,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
{ {
public readonly string Label; public readonly string Label;
private uint _currentItemId; private uint _currentItemId;
private float _innerWidth;
public WeaponCombo(ItemManager items, FullEquipType type) public WeaponCombo(ItemManager items, FullEquipType type)
: base(() => GetWeapons(items, type)) : base(() => GetWeapons(items, type))
@ -41,9 +42,13 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
return base.UpdateCurrentSelected(CurrentSelectionIdx); 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; _currentItemId = previewId;
_innerWidth = innerWidth;
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
} }

View file

@ -162,7 +162,7 @@ public class PenumbraChangedItemTooltip : IDisposable
switch (type) switch (type)
{ {
case ChangedItemType.Item: case ChangedItemType.Item:
if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item)) if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item))
return; return;
CreateTooltip(item, "[Glamourer] ", false); CreateTooltip(item, "[Glamourer] ", false);
@ -192,7 +192,7 @@ public class PenumbraChangedItemTooltip : IDisposable
if (!Player(out var state)) if (!Player(out var state))
return; return;
if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item)) if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item))
return; return;
ApplyItem(state, item); ApplyItem(state, item);

View file

@ -9,6 +9,7 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Plugin; using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Api; using Glamourer.Api;
using Glamourer.Automation; using Glamourer.Automation;
@ -42,6 +43,7 @@ public unsafe class DebugTab : ITab
private readonly UpdateSlotService _updateSlotService; private readonly UpdateSlotService _updateSlotService;
private readonly WeaponService _weaponService; private readonly WeaponService _weaponService;
private readonly MetaService _metaService; private readonly MetaService _metaService;
private readonly InventoryService _inventoryService;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly ObjectTable _objects; private readonly ObjectTable _objects;
private readonly ObjectManager _objectManager; private readonly ObjectManager _objectManager;
@ -76,7 +78,7 @@ public unsafe class DebugTab : ITab
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService) ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService)
{ {
_changeCustomizeService = changeCustomizeService; _changeCustomizeService = changeCustomizeService;
_visorService = visorService; _visorService = visorService;
@ -103,6 +105,7 @@ public unsafe class DebugTab : ITab
_itemUnlocks = itemUnlocks; _itemUnlocks = itemUnlocks;
_designConverter = designConverter; _designConverter = designConverter;
_datFileService = datFileService; _datFileService = datFileService;
_inventoryService = inventoryService;
} }
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
@ -120,6 +123,7 @@ public unsafe class DebugTab : ITab
DrawDesigns(); DrawDesigns();
DrawState(); DrawState();
DrawAutoDesigns(); DrawAutoDesigns();
DrawInventory();
DrawUnlocks(); DrawUnlocks();
DrawIpc(); DrawIpc();
} }
@ -860,7 +864,7 @@ public unsafe class DebugTab : ITab
if (!table) if (!table)
return; return;
foreach(var (index, value) in set.NpcOptions) foreach (var (index, value) in set.NpcOptions)
{ {
ImGuiUtil.DrawTableColumn(index.ToString()); ImGuiUtil.DrawTableColumn(index.ToString());
ImGuiUtil.DrawTableColumn(value.Value.ToString()); ImGuiUtil.DrawTableColumn(value.Value.ToString());
@ -980,6 +984,7 @@ public unsafe class DebugTab : ITab
ImGui.SameLine(); ImGui.SameLine();
} }
ImGui.NewLine(); ImGui.NewLine();
} }
@ -996,6 +1001,7 @@ public unsafe class DebugTab : ITab
ImGui.SameLine(); ImGui.SameLine();
} }
ImGui.NewLine(); ImGui.NewLine();
} }
} }
@ -1442,7 +1448,7 @@ public unsafe class DebugTab : ITab
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlocked, skips, t => var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlocked, skips, t =>
{ {
ImGuiUtil.DrawTableColumn(t.Key.ToString()); 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.Name);
ImGuiUtil.DrawTableColumn(equip.Type.ToName()); ImGuiUtil.DrawTableColumn(equip.Type.ToName());
@ -1489,7 +1495,7 @@ public unsafe class DebugTab : ITab
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t =>
{ {
ImGuiUtil.DrawTableColumn(t.Key.ToString()); 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.Name);
ImGuiUtil.DrawTableColumn(equip.Type.ToName()); ImGuiUtil.DrawTableColumn(equip.Type.ToName());
@ -1514,6 +1520,50 @@ public unsafe class DebugTab : ITab
#endregion #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 #region IPC
private string _gameObjectName = string.Empty; private string _gameObjectName = string.Empty;

View file

@ -179,7 +179,7 @@ public class UnlockOverview
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
if (item.Type.ValidOffhand().IsOffhandType()) if (item.Type.ValidOffhand().IsOffhandType())
ImGui.TextUnformatted( 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 else
ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}");
ImGui.TextUnformatted( ImGui.TextUnformatted(

View file

@ -242,7 +242,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
ImGuiUtil.RightAlign(item.ModelString); ImGuiUtil.RightAlign(item.ModelString);
if (ImGui.IsItemHovered() if (ImGui.IsItemHovered()
&& item.Type.ValidOffhand().IsOffhandType() && 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(); using var tt = ImRaii.Tooltip();
ImGui.TextUnformatted("Offhand: " + offhand.ModelString); ImGui.TextUnformatted("Offhand: " + offhand.ModelString);
@ -260,7 +260,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase)) if (FilterRegex?.IsMatch(item.ModelString) ?? item.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase))
return true; 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) return FilterRegex?.IsMatch(offhand.ModelString)
?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase); ?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase);

View file

@ -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<MoveItemDelegate>.FromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
_equipGearsetHook =
Hook<EquipGearsetDelegate>.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<EquipGearsetDelegate> _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<MoveItemDelegate> _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,
};
}

View file

@ -1,7 +1,6 @@
using System; using System;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -11,24 +10,18 @@ namespace Glamourer.Interop;
public unsafe class UpdateSlotService : IDisposable public unsafe class UpdateSlotService : IDisposable
{ {
public readonly SlotUpdating SlotUpdatingEvent; public readonly SlotUpdating SlotUpdatingEvent;
public readonly EquipmentLoading EquipmentLoadingEvent;
public UpdateSlotService(SlotUpdating slotUpdating, EquipmentLoading equipmentLoadingEvent) public UpdateSlotService(SlotUpdating slotUpdating)
{ {
SlotUpdatingEvent = slotUpdating; SlotUpdatingEvent = slotUpdating;
EquipmentLoadingEvent = equipmentLoadingEvent;
SignatureHelper.Initialise(this); SignatureHelper.Initialise(this);
_flagSlotForUpdateHook.Enable(); _flagSlotForUpdateHook.Enable();
_loadEquipmentHook =
Hook<LoadEquipmentDelegateIntern>.FromAddress((nint) DrawDataContainer.MemberFunctionPointers.LoadEquipment, LoadEquipmentDetour);
_loadEquipmentHook.Enable();
} }
public void Dispose() public void Dispose()
{ {
_flagSlotForUpdateHook.Dispose(); _flagSlotForUpdateHook.Dispose();
_loadEquipmentHook.Dispose();
} }
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
@ -53,10 +46,6 @@ public unsafe class UpdateSlotService : IDisposable
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))] [Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!; private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
private delegate void LoadEquipmentDelegateIntern(DrawDataContainer* drawDataContainer, uint slotIdx, CharacterArmor data, bool force);
private readonly Hook<LoadEquipmentDelegateIntern> _loadEquipmentHook = null!;
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{ {
var slot = slotIdx.ToEquipSlot(); var slot = slotIdx.ToEquipSlot();
@ -66,14 +55,6 @@ public unsafe class UpdateSlotService : IDisposable
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; 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) private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
} }

View file

@ -4,10 +4,8 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using ImGuiNET;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using static FFXIVClientStructs.FFXIV.Client.UI.UIModule;
namespace Glamourer.Interop; namespace Glamourer.Interop;
@ -75,14 +73,14 @@ public unsafe class WeaponService : IDisposable
switch (slot) switch (slot)
{ {
case EquipSlot.MainHand: 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; return;
case EquipSlot.OffHand: 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; return;
case EquipSlot.BothHand: case EquipSlot.BothHand:
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.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, 0, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0);
return; return;
// function can also be called with '2', but does not seem to ever be. // function can also be called with '2', but does not seem to ever be.
} }

View file

@ -74,7 +74,7 @@ public class ItemManager : IDisposable
if (itemId == SmallclothesId(slot)) if (itemId == SmallclothesId(slot))
return SmallClothesItem(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); return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0);
if (item.Type.ToSlot() != slot) if (item.Type.ToSlot() != slot)
@ -88,7 +88,7 @@ public class ItemManager : IDisposable
if (itemId == NothingId(type)) if (itemId == NothingId(type))
return NothingItem(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); return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0);
if (item.Type != type) if (item.Type != type)

View file

@ -61,7 +61,6 @@ public static class ServiceManager
private static IServiceCollection AddEvents(this IServiceCollection services) private static IServiceCollection AddEvents(this IServiceCollection services)
=> services.AddSingleton<VisorStateChanged>() => services.AddSingleton<VisorStateChanged>()
.AddSingleton<SlotUpdating>() .AddSingleton<SlotUpdating>()
.AddSingleton<EquipmentLoading>()
.AddSingleton<DesignChanged>() .AddSingleton<DesignChanged>()
.AddSingleton<AutomationChanged>() .AddSingleton<AutomationChanged>()
.AddSingleton<StateChanged>() .AddSingleton<StateChanged>()
@ -69,7 +68,8 @@ public static class ServiceManager
.AddSingleton<HeadGearVisibilityChanged>() .AddSingleton<HeadGearVisibilityChanged>()
.AddSingleton<WeaponVisibilityChanged>() .AddSingleton<WeaponVisibilityChanged>()
.AddSingleton<ObjectUnlocked>() .AddSingleton<ObjectUnlocked>()
.AddSingleton<TabSelected>(); .AddSingleton<TabSelected>()
.AddSingleton<MovedEquipment>();
private static IServiceCollection AddData(this IServiceCollection services) private static IServiceCollection AddData(this IServiceCollection services)
=> services.AddSingleton<IdentifierService>() => services.AddSingleton<IdentifierService>()
@ -91,7 +91,8 @@ public static class ServiceManager
.AddSingleton<JobService>() .AddSingleton<JobService>()
.AddSingleton<CustomizeUnlockManager>() .AddSingleton<CustomizeUnlockManager>()
.AddSingleton<ItemUnlockManager>() .AddSingleton<ItemUnlockManager>()
.AddSingleton<DatFileService>(); .AddSingleton<DatFileService>()
.AddSingleton<InventoryService>();
private static IServiceCollection AddDesigns(this IServiceCollection services) private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton<DesignManager>() => services.AddSingleton<DesignManager>()

View file

@ -75,8 +75,8 @@ public abstract class AsyncServiceWrapper<T> : IDisposable
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier> public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
{ {
public IdentifierService(DalamudPluginInterface pi, DataManager data) public IdentifierService(DalamudPluginInterface pi, DataManager data, ItemService itemService)
: base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data)) : base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data, itemService.AwaitedService))
{ } { }
} }

View file

@ -2,6 +2,7 @@
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
@ -22,12 +23,12 @@ public class StateListener : IDisposable
{ {
private readonly Configuration _config; private readonly Configuration _config;
private readonly ActorService _actors; private readonly ActorService _actors;
private readonly ObjectManager _objects;
private readonly StateManager _manager; private readonly StateManager _manager;
private readonly StateApplier _applier; private readonly StateApplier _applier;
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly SlotUpdating _slotUpdating; private readonly SlotUpdating _slotUpdating;
private readonly EquipmentLoading _equipmentLoading;
private readonly WeaponLoading _weaponLoading; private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState; private readonly VisorStateChanged _visorState;
@ -35,9 +36,11 @@ public class StateListener : IDisposable
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly FunModule _funModule; private readonly FunModule _funModule;
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
private readonly MovedEquipment _movedEquipment;
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
private ActorState? _creatingState = null; private ActorState? _creatingState;
private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
public bool Enabled public bool Enabled
{ {
@ -48,7 +51,7 @@ public class StateListener : IDisposable
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
EquipmentLoading equipmentLoading, StateApplier applier) StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects)
{ {
_manager = manager; _manager = manager;
_items = items; _items = items;
@ -63,8 +66,9 @@ public class StateListener : IDisposable
_autoDesignApplier = autoDesignApplier; _autoDesignApplier = autoDesignApplier;
_funModule = funModule; _funModule = funModule;
_humans = humans; _humans = humans;
_equipmentLoading = equipmentLoading;
_applier = applier; _applier = applier;
_movedEquipment = movedEquipment;
_objects = objects;
if (Enabled) if (Enabled)
Subscribe(); Subscribe();
@ -168,40 +172,36 @@ public class StateListener : IDisposable
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); (_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
} }
/// <summary> private void OnMovedEquipment((EquipSlot, uint, StainId)[] items)
/// The game object does not actually invoke changes when the model id is identical,
/// so we need to handle that case too.
/// </summary>
private void OnEquipmentLoading(Actor actor, EquipSlot slot, CharacterArmor armor)
{ {
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; return;
if (!actor.Identifier(_actors.AwaitedService, out var identifier) foreach (var (slot, item, stain) in items)
|| !_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)
{ {
case (true, true): var currentItem = state.BaseData.Item(slot);
_manager.ChangeEquip(state, slot, state.BaseData.Item(slot), state.BaseData.Stain(slot), StateChanged.Source.Manual); var model = state.ModelData.Weapon(slot);
state[slot, false] = StateChanged.Source.Game; var current = currentItem.Weapon(state.BaseData.Stain(slot));
state[slot, true] = StateChanged.Source.Game; if (model.Value == current.Value || !_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var changedItem))
break; continue;
case (true, false):
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Manual); var changed = changedItem.Weapon(stain);
state[slot, false] = StateChanged.Source.Game; if (current.Value == changed.Value && state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
break; {
case (false, true): _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game);
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Manual); switch (slot)
state[slot, true] = StateChanged.Source.Game; {
break; 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
/// </summary> /// </summary>
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon) private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> 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) if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|| !_manager.TryGetValue(identifier, out var state)) || !_manager.TryGetValue(identifier, out var state))
return; return;
@ -229,7 +236,7 @@ public class StateListener : IDisposable
else else
apply = true; 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); _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
@ -249,6 +256,11 @@ public class StateListener : IDisposable
else if (actorWeapon.Set.Value != 0) else if (actorWeapon.Set.Value != 0)
actorWeapon = actorWeapon.With(newWeapon.Stain); 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);
} }
/// <summary> Update base data for a single changed equipment slot. </summary> /// <summary> Update base data for a single changed equipment slot. </summary>
@ -257,7 +269,7 @@ public class StateListener : IDisposable
var actorArmor = actor.GetArmor(slot); var actorArmor = actor.GetArmor(slot);
// The actor armor does not correspond to the model armor, thus the actor is transformed. // 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. // 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; return UpdateState.Transformed;
var baseData = state.BaseData.Armor(slot); var baseData = state.BaseData.Armor(slot);
@ -491,7 +503,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase; _penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener); _slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
_equipmentLoading.Subscribe(OnEquipmentLoading, EquipmentLoading.Priority.StateListener); _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener); _headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
@ -503,7 +515,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
_slotUpdating.Unsubscribe(OnSlotUpdating); _slotUpdating.Unsubscribe(OnSlotUpdating);
_equipmentLoading.Unsubscribe(OnEquipmentLoading); _movedEquipment.Unsubscribe(OnMovedEquipment);
_weaponLoading.Unsubscribe(OnWeaponLoading); _weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange); _visorState.Unsubscribe(OnVisorChange);
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
@ -515,7 +527,7 @@ public class StateListener : IDisposable
if (_creatingState == null) if (_creatingState == null)
return; 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()); _applier.ChangeWeaponState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWeaponVisible());
} }
} }

View file

@ -169,6 +169,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
main = actor.GetMainhand(); main = actor.GetMainhand();
off = actor.GetOffhand(); off = actor.GetOffhand();
FistWeaponHack(ref ret, ref main, ref off);
ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
} }
@ -190,6 +191,19 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
return ret; return ret;
} }
/// <summary> This is hardcoded in the game. </summary>
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 #region Change Values
/// <summary> Turn an actor human. </summary> /// <summary> Turn an actor human. </summary>
@ -433,7 +447,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
if (!GetOrCreate(actor, out var state)) if (!GetOrCreate(actor, out var state))
return; 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) public void DeleteState(ActorIdentifier identifier)

View file

@ -107,7 +107,7 @@ public class ItemUnlockManager : ISavable, IDisposable
private bool AddItem(uint itemId, long time) private bool AddItem(uint itemId, long time)
{ {
itemId = HandleHq(itemId); 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; return false;
_event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time)); _event.Invoke(ObjectUnlocked.Type.Item, equip.ItemId, DateTimeOffset.FromUnixTimeMilliseconds(time));
@ -278,7 +278,7 @@ public class ItemUnlockManager : ISavable, IDisposable
private void Load() private void Load()
{ {
var version = UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, 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); UpdateModels(version);
} }
@ -291,7 +291,7 @@ public class ItemUnlockManager : ISavable, IDisposable
var cabinet = gameData.GetExcelSheet<Cabinet>()!; var cabinet = gameData.GetExcelSheet<Cabinet>()!;
foreach (var row in cabinet) 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)); 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<GilShop>()!; var gilShop = gameData.GetExcelSheet<GilShop>()!;
foreach (var row in gilShopItem) 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; continue;
var quest1 = row.QuestRequired[0].Row; var quest1 = row.QuestRequired[0].Row;
@ -332,7 +332,7 @@ public class ItemUnlockManager : ISavable, IDisposable
foreach (var (item, time) in _unlocked.ToArray()) 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; continue;
var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot()); var ident = _identifier.AwaitedService.Identify(equip.ModelId, equip.WeaponType, equip.Variant, equip.Type.ToSlot());