mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Add handling for Monk fist weapon hack, remove LoadEquipment hooks, and add item movement and plate apply hooks instead.
This commit is contained in:
parent
323924fba2
commit
5874688838
19 changed files with 403 additions and 128 deletions
|
|
@ -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);
|
||||
}
|
||||
28
Glamourer/Events/MovedEquipment.cs
Normal file
28
Glamourer/Events/MovedEquipment.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
|
||||
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<EquipItem>
|
|||
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];
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
|||
{
|
||||
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<EquipItem>
|
|||
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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<byte> 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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ public class UnlockTable : Table<EquipItem>, 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<EquipItem>, 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);
|
||||
|
||||
|
|
|
|||
202
Glamourer/Interop/InventoryService.cs
Normal file
202
Glamourer/Interop/InventoryService.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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<LoadEquipmentDelegateIntern>.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<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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ public static class ServiceManager
|
|||
private static IServiceCollection AddEvents(this IServiceCollection services)
|
||||
=> services.AddSingleton<VisorStateChanged>()
|
||||
.AddSingleton<SlotUpdating>()
|
||||
.AddSingleton<EquipmentLoading>()
|
||||
.AddSingleton<DesignChanged>()
|
||||
.AddSingleton<AutomationChanged>()
|
||||
.AddSingleton<StateChanged>()
|
||||
|
|
@ -69,7 +68,8 @@ public static class ServiceManager
|
|||
.AddSingleton<HeadGearVisibilityChanged>()
|
||||
.AddSingleton<WeaponVisibilityChanged>()
|
||||
.AddSingleton<ObjectUnlocked>()
|
||||
.AddSingleton<TabSelected>();
|
||||
.AddSingleton<TabSelected>()
|
||||
.AddSingleton<MovedEquipment>();
|
||||
|
||||
private static IServiceCollection AddData(this IServiceCollection services)
|
||||
=> services.AddSingleton<IdentifierService>()
|
||||
|
|
@ -91,7 +91,8 @@ public static class ServiceManager
|
|||
.AddSingleton<JobService>()
|
||||
.AddSingleton<CustomizeUnlockManager>()
|
||||
.AddSingleton<ItemUnlockManager>()
|
||||
.AddSingleton<DatFileService>();
|
||||
.AddSingleton<DatFileService>()
|
||||
.AddSingleton<InventoryService>();
|
||||
|
||||
private static IServiceCollection AddDesigns(this IServiceCollection services)
|
||||
=> services.AddSingleton<DesignManager>()
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ public abstract class AsyncServiceWrapper<T> : IDisposable
|
|||
|
||||
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
|
||||
{
|
||||
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))
|
||||
{ }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
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
|
|||
/// </summary>
|
||||
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)
|
||||
|| !_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);
|
||||
}
|
||||
|
||||
/// <summary> Update base data for a single changed equipment slot. </summary>
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
|
||||
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<ActorIdentifier, ActorState>
|
|||
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
|
||||
|
||||
/// <summary> Turn an actor human. </summary>
|
||||
|
|
@ -433,7 +447,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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<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));
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ public class ItemUnlockManager : ISavable, IDisposable
|
|||
var gilShop = gameData.GetExcelSheet<GilShop>()!;
|
||||
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());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue