mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-30 12:23:42 +01:00
.
This commit is contained in:
parent
d1d369a56b
commit
65ce391051
19 changed files with 757 additions and 158 deletions
|
|
@ -4,20 +4,29 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Filesystem;
|
||||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||||
|
|
||||||
namespace Glamourer;
|
namespace Glamourer;
|
||||||
|
|
||||||
public class Configuration : IPluginConfiguration, ISavable
|
public class Configuration : IPluginConfiguration, ISavable
|
||||||
{
|
{
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
public bool UseRestrictedGearProtection { get; set; } = true;
|
public bool UseRestrictedGearProtection { get; set; } = true;
|
||||||
|
public bool OpenFoldersByDefault { get; set; } = false;
|
||||||
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
||||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||||
|
|
||||||
|
[JsonConverter(typeof(SortModeConverter))]
|
||||||
|
[JsonProperty(Order = int.MaxValue)]
|
||||||
|
public ISortMode<Design> SortMode { get; set; } = ISortMode<Design>.FoldersFirst;
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public bool DebugMode { get; set; } = true;
|
public bool DebugMode { get; set; } = true;
|
||||||
|
|
@ -86,5 +95,42 @@ public class Configuration : IPluginConfiguration, ISavable
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
public const int CurrentVersion = 2;
|
public const int CurrentVersion = 2;
|
||||||
|
|
||||||
|
public static readonly ISortMode<Design>[] ValidSortModes =
|
||||||
|
{
|
||||||
|
ISortMode<Design>.FoldersFirst,
|
||||||
|
ISortMode<Design>.Lexicographical,
|
||||||
|
new DesignFileSystem.CreationDate(),
|
||||||
|
new DesignFileSystem.InverseCreationDate(),
|
||||||
|
new DesignFileSystem.UpdateDate(),
|
||||||
|
new DesignFileSystem.InverseUpdateDate(),
|
||||||
|
ISortMode<Design>.InverseFoldersFirst,
|
||||||
|
ISortMode<Design>.InverseLexicographical,
|
||||||
|
ISortMode<Design>.FoldersLast,
|
||||||
|
ISortMode<Design>.InverseFoldersLast,
|
||||||
|
ISortMode<Design>.InternalOrder,
|
||||||
|
ISortMode<Design>.InverseInternalOrder,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Convert SortMode Types to their name. </summary>
|
||||||
|
private class SortModeConverter : JsonConverter<ISortMode<Design>>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, ISortMode<Design>? value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
value ??= ISortMode<Design>.FoldersFirst;
|
||||||
|
serializer.Serialize(writer, value.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ISortMode<Design> ReadJson(JsonReader reader, Type objectType, ISortMode<Design>? existingValue,
|
||||||
|
bool hasExistingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var name = serializer.Deserialize<string>(reader);
|
||||||
|
if (name == null || !Constants.ValidSortModes.FindFirst(s => s.GetType().Name == name, out var mode))
|
||||||
|
return existingValue ?? ISortMode<Design>.FoldersFirst;
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@ namespace Glamourer.Events;
|
||||||
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class UpdatedSlot : EventWrapper<Action<Model, EquipSlot, Ref<CharacterArmor>, Ref<ulong>>, UpdatedSlot.Priority>
|
public sealed class SlotUpdating : EventWrapper<Action<Model, EquipSlot, Ref<CharacterArmor>, Ref<ulong>>, SlotUpdating.Priority>
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="State.StateManager.OnSlotUpdated"/>
|
/// <seealso cref="State.StateListener.OnSlotUpdating"/>
|
||||||
StateManager = 0,
|
StateListener = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdatedSlot()
|
public SlotUpdating()
|
||||||
: base(nameof(UpdatedSlot))
|
: base(nameof(SlotUpdating))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public void Invoke(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
|
public void Invoke(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
|
||||||
35
Glamourer/Events/WeaponLoading.cs
Normal file
35
Glamourer/Events/WeaponLoading.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a model flags an equipment slot for an update.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the actor that has its weapons changed. </item>
|
||||||
|
/// <item>Parameter is the equipment slot changed (Mainhand or Offhand). </item>
|
||||||
|
/// <item>Parameter is the model values to change the weapon to. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WeaponLoading : EventWrapper<Action<Actor, EquipSlot, Ref<CharacterWeapon>>, WeaponLoading.Priority>
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnWeaponLoading"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeaponLoading()
|
||||||
|
: base(nameof(WeaponLoading))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Invoke(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
|
||||||
|
{
|
||||||
|
var value = new Ref<CharacterWeapon>(weapon);
|
||||||
|
Invoke(this, actor, slot, value);
|
||||||
|
weapon = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,24 +9,25 @@ public enum ColorId
|
||||||
EquipmentDesign,
|
EquipmentDesign,
|
||||||
ActorAvailable,
|
ActorAvailable,
|
||||||
ActorUnavailable,
|
ActorUnavailable,
|
||||||
|
FolderExpanded,
|
||||||
|
FolderCollapsed,
|
||||||
|
FolderLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Colors
|
public static class Colors
|
||||||
{
|
{
|
||||||
public const uint DiscordColor = 0xFFDA8972;
|
|
||||||
public const uint ReniColorButton = 0xFFCC648D;
|
|
||||||
public const uint ReniColorHovered = 0xFFB070B0;
|
|
||||||
public const uint ReniColorActive = 0xFF9070E0;
|
|
||||||
|
|
||||||
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
|
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
|
||||||
=> color switch
|
=> color switch
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ),
|
ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ),
|
||||||
ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that only changes meta state on a character." ),
|
ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that only changes meta state on a character." ),
|
||||||
ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ),
|
ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ),
|
||||||
ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ),
|
ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ),
|
||||||
ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ),
|
ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ),
|
||||||
|
ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ),
|
||||||
|
ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ),
|
||||||
|
ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ),
|
||||||
_ => (0x00000000, string.Empty, string.Empty ),
|
_ => (0x00000000, string.Empty, string.Empty ),
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,16 @@ namespace Glamourer.Gui;
|
||||||
|
|
||||||
public class GlamourerWindowSystem : IDisposable
|
public class GlamourerWindowSystem : IDisposable
|
||||||
{
|
{
|
||||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||||
private readonly UiBuilder _uiBuilder;
|
private readonly UiBuilder _uiBuilder;
|
||||||
private readonly MainWindow _ui;
|
private readonly MainWindow _ui;
|
||||||
|
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
|
||||||
|
|
||||||
public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui)
|
public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, PenumbraChangedItemTooltip penumbraTooltip)
|
||||||
{
|
{
|
||||||
_uiBuilder = uiBuilder;
|
_uiBuilder = uiBuilder;
|
||||||
_ui = ui;
|
_ui = ui;
|
||||||
|
_penumbraTooltip = penumbraTooltip;
|
||||||
_windowSystem.AddWindow(ui);
|
_windowSystem.AddWindow(ui);
|
||||||
_uiBuilder.Draw += _windowSystem.Draw;
|
_uiBuilder.Draw += _windowSystem.Draw;
|
||||||
_uiBuilder.OpenConfigUi += _ui.Toggle;
|
_uiBuilder.OpenConfigUi += _ui.Toggle;
|
||||||
|
|
|
||||||
182
Glamourer/Gui/PenumbraChangedItemTooltip.cs
Normal file
182
Glamourer/Gui/PenumbraChangedItemTooltip.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Glamourer.Structs;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
public class PenumbraChangedItemTooltip : IDisposable
|
||||||
|
{
|
||||||
|
private readonly PenumbraService _penumbra;
|
||||||
|
private readonly StateManager _stateManager;
|
||||||
|
private readonly ItemManager _items;
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
|
||||||
|
private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2];
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<EquipSlot, EquipItem>> LastItems
|
||||||
|
=> EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems)
|
||||||
|
.Select(p => new KeyValuePair<EquipSlot, EquipItem>(p.First, p.Second));
|
||||||
|
|
||||||
|
public DateTime LastTooltip { get; private set; } = DateTime.MinValue;
|
||||||
|
public DateTime LastClick { get; private set; } = DateTime.MinValue;
|
||||||
|
|
||||||
|
public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects)
|
||||||
|
{
|
||||||
|
_penumbra = penumbra;
|
||||||
|
_stateManager = stateManager;
|
||||||
|
_items = items;
|
||||||
|
_objects = objects;
|
||||||
|
_penumbra.Tooltip += OnPenumbraTooltip;
|
||||||
|
_penumbra.Click += OnPenumbraClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_penumbra.Tooltip -= OnPenumbraTooltip;
|
||||||
|
_penumbra.Click -= OnPenumbraClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPenumbraTooltip(ChangedItemType type, uint id)
|
||||||
|
{
|
||||||
|
LastTooltip = DateTime.UtcNow;
|
||||||
|
if (!_objects.Player.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ChangedItemType.Item:
|
||||||
|
if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var slot = item.Type.ToSlot();
|
||||||
|
var last = _lastItems[slot.ToIndex()];
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case EquipSlot.MainHand when !CanApplyWeapon(EquipSlot.MainHand, item):
|
||||||
|
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
|
||||||
|
break;
|
||||||
|
case EquipSlot.RFinger:
|
||||||
|
ImGui.TextUnformatted("[Glamourer] Right-Click to apply to current actor (Right Finger).");
|
||||||
|
ImGui.TextUnformatted("[Glamourer] Shift + Right-Click to apply to current actor (Left Finger).");
|
||||||
|
if (last.Valid)
|
||||||
|
ImGui.TextUnformatted(
|
||||||
|
$"[Glamourer] Control + Right-Click to re-apply {last.Name} to current actor (Right Finger).");
|
||||||
|
|
||||||
|
var last2 = _lastItems[EquipSlot.LFinger.ToIndex()];
|
||||||
|
if (last2.Valid)
|
||||||
|
ImGui.TextUnformatted(
|
||||||
|
$"[Glamourer] Shift + Control + Right-Click to re-apply {last.Name} to current actor (Left Finger).");
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ImGui.TextUnformatted("[Glamourer] Right-Click to apply to current actor.");
|
||||||
|
if (last.Valid)
|
||||||
|
ImGui.TextUnformatted($"[Glamourer] Control + Right-Click to re-apply {last.Name} to current actor.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanApplyWeapon(EquipSlot slot, EquipItem item)
|
||||||
|
{
|
||||||
|
var main = _objects.Player.GetMainhand();
|
||||||
|
var mainItem = _items.Identify(slot, main.Set, main.Type, (byte)main.Variant);
|
||||||
|
if (slot == EquipSlot.MainHand)
|
||||||
|
return item.Type == mainItem.Type;
|
||||||
|
|
||||||
|
return item.Type == mainItem.Type.Offhand();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPenumbraClick(MouseButton button, ChangedItemType type, uint id)
|
||||||
|
{
|
||||||
|
LastClick = DateTime.UtcNow;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ChangedItemType.Item:
|
||||||
|
if (button is not MouseButton.Right)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var (identifier, data) = _objects.PlayerData;
|
||||||
|
if (!data.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_stateManager.GetOrCreate(identifier, data.Objects[0], out var state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_items.ItemService.AwaitedService.TryGetValue(id, out var item))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var slot = item.Type.ToSlot();
|
||||||
|
var last = _lastItems[slot.ToIndex()];
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case EquipSlot.MainHand when !CanApplyWeapon(EquipSlot.MainHand, item):
|
||||||
|
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
|
||||||
|
break;
|
||||||
|
case EquipSlot.RFinger:
|
||||||
|
switch (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift)
|
||||||
|
{
|
||||||
|
case (false, false):
|
||||||
|
Glamourer.Log.Information($"Applying {item.Name} to Right Finger.");
|
||||||
|
SetLastItem(EquipSlot.RFinger, item, state);
|
||||||
|
break;
|
||||||
|
case (false, true):
|
||||||
|
Glamourer.Log.Information($"Applying {item.Name} to Left Finger.");
|
||||||
|
SetLastItem(EquipSlot.LFinger, item, state);
|
||||||
|
break;
|
||||||
|
case (true, false) when last.Valid:
|
||||||
|
Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger.");
|
||||||
|
SetLastItem(EquipSlot.RFinger, default, state);
|
||||||
|
break;
|
||||||
|
case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid:
|
||||||
|
Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger.");
|
||||||
|
SetLastItem(EquipSlot.LFinger, default, state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
if (ImGui.GetIO().KeyCtrl && last.Valid)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}.");
|
||||||
|
SetLastItem(slot, default, state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}.");
|
||||||
|
SetLastItem(slot, item, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLastItem(EquipSlot slot, EquipItem item, ActorState state)
|
||||||
|
{
|
||||||
|
ref var last = ref _lastItems[slot.ToIndex()];
|
||||||
|
if (!item.Valid)
|
||||||
|
{
|
||||||
|
last = default;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var oldItem = state.ModelData.Item(slot);
|
||||||
|
if (oldItem.Id != item.Id)
|
||||||
|
_lastItems[slot.ToIndex()] = oldItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ public class ActorPanel
|
||||||
if (!child || _state == null)
|
if (!child || _state == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_customizationDrawer.Draw(_state.Data.Customize, false))
|
if (_customizationDrawer.Draw(_state.ModelData.Customize, false))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
// if (_currentData.Valid)
|
// if (_currentData.Valid)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ public unsafe class DebugTab : ITab
|
||||||
private readonly DesignManager _designManager;
|
private readonly DesignManager _designManager;
|
||||||
private readonly DesignFileSystem _designFileSystem;
|
private readonly DesignFileSystem _designFileSystem;
|
||||||
|
|
||||||
|
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
|
||||||
|
|
||||||
private readonly StateManager _state;
|
private readonly StateManager _state;
|
||||||
|
|
||||||
private int _gameObjectIndex;
|
private int _gameObjectIndex;
|
||||||
|
|
@ -51,7 +53,8 @@ public unsafe class DebugTab : ITab
|
||||||
public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, ObjectTable objects,
|
public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, ObjectTable objects,
|
||||||
UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra,
|
UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra,
|
||||||
ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager,
|
ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager,
|
||||||
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config)
|
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
|
||||||
|
PenumbraChangedItemTooltip penumbraTooltip)
|
||||||
{
|
{
|
||||||
_changeCustomizeService = changeCustomizeService;
|
_changeCustomizeService = changeCustomizeService;
|
||||||
_visorService = visorService;
|
_visorService = visorService;
|
||||||
|
|
@ -67,6 +70,7 @@ public unsafe class DebugTab : ITab
|
||||||
_designManager = designManager;
|
_designManager = designManager;
|
||||||
_state = state;
|
_state = state;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_penumbraTooltip = penumbraTooltip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -434,6 +438,23 @@ public unsafe class DebugTab : ITab
|
||||||
if (ImGui.SmallButton("Redraw"))
|
if (ImGui.SmallButton("Redraw"))
|
||||||
_penumbra.RedrawObject(_objects.GetObjectAddress(_gameObjectIndex), RedrawType.Redraw);
|
_penumbra.RedrawObject(_objects.GetObjectAddress(_gameObjectIndex), RedrawType.Redraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Last Tooltip Date");
|
||||||
|
ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? _penumbraTooltip.LastTooltip.ToLongTimeString() : "Never");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn("Last Click Date");
|
||||||
|
ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastClick > DateTime.MinValue ? _penumbraTooltip.LastClick.ToLongTimeString() : "Never");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Separator();
|
||||||
|
foreach (var (slot, item) in _penumbraTooltip.LastItems)
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item");
|
||||||
|
ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -990,7 +1011,7 @@ public unsafe class DebugTab : ITab
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (_state.GetOrCreate(identifier, actors.Objects[0], out var state))
|
if (_state.GetOrCreate(identifier, actors.Objects[0], out var state))
|
||||||
DrawDesignData(state.Data);
|
DrawDesignData(state.ModelData);
|
||||||
else
|
else
|
||||||
ImGui.TextUnformatted("Invalid actor.");
|
ImGui.TextUnformatted("Invalid actor.");
|
||||||
}
|
}
|
||||||
|
|
@ -1006,7 +1027,7 @@ public unsafe class DebugTab : ITab
|
||||||
{
|
{
|
||||||
using var t = ImRaii.TreeNode(identifier.ToString());
|
using var t = ImRaii.TreeNode(identifier.ToString());
|
||||||
if (t)
|
if (t)
|
||||||
DrawDesignData(state.Data);
|
DrawDesignData(state.ModelData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,21 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
||||||
_event.Unsubscribe(OnDesignChange);
|
_event.Unsubscribe(OnDesignChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ISortMode<Design> SortMode
|
||||||
|
=> _config.SortMode;
|
||||||
|
|
||||||
|
protected override uint ExpandedFolderColor
|
||||||
|
=> ColorId.FolderExpanded.Value();
|
||||||
|
|
||||||
|
protected override uint CollapsedFolderColor
|
||||||
|
=> ColorId.FolderCollapsed.Value();
|
||||||
|
|
||||||
|
protected override uint FolderLineColor
|
||||||
|
=> ColorId.FolderLine.Value();
|
||||||
|
|
||||||
|
protected override bool FoldersDefaultOpen
|
||||||
|
=> _config.OpenFoldersByDefault;
|
||||||
|
|
||||||
private void OnDesignChange(DesignChanged.Type type, Design design, object? oldData)
|
private void OnDesignChange(DesignChanged.Type type, Design design, object? oldData)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
|
@ -12,10 +12,16 @@ namespace Glamourer.Gui.Tabs;
|
||||||
|
|
||||||
public class SettingsTab : ITab
|
public class SettingsTab : ITab
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
|
private readonly DesignFileSystemSelector _selector;
|
||||||
|
private readonly StateListener _stateListener;
|
||||||
|
|
||||||
public SettingsTab(Configuration config)
|
public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener)
|
||||||
=> _config = config;
|
{
|
||||||
|
_config = config;
|
||||||
|
_selector = selector;
|
||||||
|
_stateListener = stateListener;
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
=> "Settings"u8;
|
=> "Settings"u8;
|
||||||
|
|
@ -25,7 +31,7 @@ public class SettingsTab : ITab
|
||||||
using var child = ImRaii.Child("MainWindowChild");
|
using var child = ImRaii.Child("MainWindowChild");
|
||||||
if (!child)
|
if (!child)
|
||||||
return;
|
return;
|
||||||
|
Checkbox("Enabled", "Enable main functionality of keeping and applying state.", _stateListener.Enabled, _stateListener.Enable);
|
||||||
Checkbox("Restricted Gear Protection",
|
Checkbox("Restricted Gear Protection",
|
||||||
"Use gender- and race-appropriate models when detecting certain items not available for a characters current gender and race.",
|
"Use gender- and race-appropriate models when detecting certain items not available for a characters current gender and race.",
|
||||||
_config.UseRestrictedGearProtection, v => _config.UseRestrictedGearProtection = v);
|
_config.UseRestrictedGearProtection, v => _config.UseRestrictedGearProtection = v);
|
||||||
|
|
@ -33,6 +39,10 @@ public class SettingsTab : ITab
|
||||||
"A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale,
|
"A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale,
|
||||||
_config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v))
|
_config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v))
|
||||||
_config.Save();
|
_config.Save();
|
||||||
|
DrawFolderSortType();
|
||||||
|
Checkbox("Auto-Open Design Folders",
|
||||||
|
"Have design folders open or closed as their default state after launching.", _config.OpenFoldersByDefault,
|
||||||
|
v => _config.OpenFoldersByDefault = v);
|
||||||
Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use.", _config.DebugMode, v => _config.DebugMode = v);
|
Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use.", _config.DebugMode, v => _config.DebugMode = v);
|
||||||
DrawColorSettings();
|
DrawColorSettings();
|
||||||
|
|
||||||
|
|
@ -70,4 +80,28 @@ public class SettingsTab : ITab
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGuiUtil.LabeledHelpMarker(label, tooltip);
|
ImGuiUtil.LabeledHelpMarker(label, tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Different supported sort modes as a combo. </summary>
|
||||||
|
private void DrawFolderSortType()
|
||||||
|
{
|
||||||
|
var sortMode = _config.SortMode;
|
||||||
|
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||||
|
using (var combo = ImRaii.Combo("##sortMode", sortMode.Name))
|
||||||
|
{
|
||||||
|
if (combo)
|
||||||
|
foreach (var val in Configuration.Constants.ValidSortModes)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType())
|
||||||
|
{
|
||||||
|
_config.SortMode = val;
|
||||||
|
_selector.SetFilterDirty();
|
||||||
|
_config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip(val.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the designs tab.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,17 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||||
public Actor Player
|
public Actor Player
|
||||||
=> _objects.GetObjectAddress(0);
|
=> _objects.GetObjectAddress(0);
|
||||||
|
|
||||||
|
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Update();
|
||||||
|
return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||||
|
? (ident, data)
|
||||||
|
: (ActorIdentifier.Invalid, ActorData.Invalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
|
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
|
||||||
=> Identifiers.GetEnumerator();
|
=> Identifiers.GetEnumerator();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ namespace Glamourer.Interop;
|
||||||
|
|
||||||
public unsafe class UpdateSlotService : IDisposable
|
public unsafe class UpdateSlotService : IDisposable
|
||||||
{
|
{
|
||||||
public readonly UpdatedSlot Event;
|
public readonly SlotUpdating Event;
|
||||||
|
|
||||||
public UpdateSlotService(UpdatedSlot updatedSlot)
|
public UpdateSlotService(SlotUpdating slotUpdating)
|
||||||
{
|
{
|
||||||
Event = updatedSlot;
|
Event = slotUpdating;
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_flagSlotForUpdateHook.Enable();
|
_flagSlotForUpdateHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,7 @@ public class VisorService : IDisposable
|
||||||
|
|
||||||
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary>
|
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary>
|
||||||
public unsafe bool GetVisorState(Model characterBase)
|
public unsafe bool GetVisorState(Model characterBase)
|
||||||
{
|
=> characterBase.IsCharacterBase && characterBase.AsCharacterBase->VisorToggled;
|
||||||
if (!characterBase.IsCharacterBase)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// TODO: use client structs.
|
|
||||||
return (characterBase.AsCharacterBase->UnkFlags_01 & Offsets.DrawObjectVisorStateFlag) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Manually set the state of the Visor for the given draw object. </summary>
|
/// <summary> Manually set the state of the Visor for the given draw object. </summary>
|
||||||
/// <param name="human"> The draw object. </param>
|
/// <param name="human"> The draw object. </param>
|
||||||
|
|
@ -45,7 +39,6 @@ public class VisorService : IDisposable
|
||||||
if (oldState == on)
|
if (oldState == on)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
||||||
SetupVisorHook(human, (ushort)human.AsHuman->HeadSetID, on);
|
SetupVisorHook(human, (ushort)human.AsHuman->HeadSetID, on);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -10,8 +11,11 @@ namespace Glamourer.Interop;
|
||||||
|
|
||||||
public unsafe class WeaponService : IDisposable
|
public unsafe class WeaponService : IDisposable
|
||||||
{
|
{
|
||||||
public WeaponService()
|
private readonly WeaponLoading _event;
|
||||||
|
|
||||||
|
public WeaponService(WeaponLoading @event)
|
||||||
{
|
{
|
||||||
|
_event = @event;
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_loadWeaponHook = Hook<LoadWeaponDelegate>.FromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
_loadWeaponHook = Hook<LoadWeaponDelegate>.FromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
||||||
_loadWeaponHook.Enable();
|
_loadWeaponHook.Enable();
|
||||||
|
|
@ -22,20 +26,38 @@ public unsafe class WeaponService : IDisposable
|
||||||
_loadWeaponHook.Dispose();
|
_loadWeaponHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weapons for a specific character are reloaded with this function.
|
||||||
|
// slot is 0 for main hand, 1 for offhand, 2 for combat effects.
|
||||||
|
// weapon argument is the new weapon data.
|
||||||
|
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||||
|
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||||
|
// unk4 seemed to be the same as unk1.
|
||||||
private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||||
byte skipGameObject, byte unk4);
|
byte skipGameObject, byte unk4);
|
||||||
|
|
||||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook;
|
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook;
|
||||||
|
|
||||||
private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
|
||||||
|
private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2,
|
||||||
|
byte skipGameObject,
|
||||||
byte unk4)
|
byte unk4)
|
||||||
{
|
{
|
||||||
var actor = (Actor) (nint)(drawData + 1);
|
var actor = (Actor)((nint*)drawData)[1];
|
||||||
|
var weapon = new CharacterWeapon(weaponValue);
|
||||||
|
var equipSlot = slot switch
|
||||||
|
{
|
||||||
|
0 => EquipSlot.MainHand,
|
||||||
|
1 => EquipSlot.OffHand,
|
||||||
|
_ => EquipSlot.Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
// First call the regular function.
|
// First call the regular function.
|
||||||
_loadWeaponHook.Original(drawData, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
if (equipSlot is not EquipSlot.Unknown)
|
||||||
|
_event.Invoke(actor, equipSlot, ref weapon);
|
||||||
|
|
||||||
|
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||||
Glamourer.Log.Excessive(
|
Glamourer.Log.Excessive(
|
||||||
$"Weapon reloaded for 0x{actor.Address:X} with attributes {slot} {weapon:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
|
$"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load a specific weapon for a character by its data and slot.
|
// Load a specific weapon for a character by its data and slot.
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,9 @@ public class ItemManager : IDisposable
|
||||||
RestrictedGear.Dispose();
|
RestrictedGear.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
|
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
|
||||||
=> _config.UseRestrictedGearProtection ? RestrictedGear.ResolveRestricted(armor, slot, race, gender) : (false, armor);
|
=> _config.UseRestrictedGearProtection ? RestrictedGear.ResolveRestricted(armor, slot, race, gender) : (false, armor);
|
||||||
|
|
||||||
|
|
||||||
public static uint NothingId(EquipSlot slot)
|
public static uint NothingId(EquipSlot slot)
|
||||||
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
|
=> uint.MaxValue - 128 - (uint)slot.ToSlot();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,10 @@ 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<UpdatedSlot>()
|
.AddSingleton<SlotUpdating>()
|
||||||
.AddSingleton<DesignChanged>()
|
.AddSingleton<DesignChanged>()
|
||||||
.AddSingleton<StateChanged>();
|
.AddSingleton<StateChanged>()
|
||||||
|
.AddSingleton<WeaponLoading>();
|
||||||
|
|
||||||
private static IServiceCollection AddData(this IServiceCollection services)
|
private static IServiceCollection AddData(this IServiceCollection services)
|
||||||
=> services.AddSingleton<IdentifierService>()
|
=> services.AddSingleton<IdentifierService>()
|
||||||
|
|
@ -75,7 +76,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<DesignFileSystem>();
|
.AddSingleton<DesignFileSystem>();
|
||||||
|
|
||||||
private static IServiceCollection AddState(this IServiceCollection services)
|
private static IServiceCollection AddState(this IServiceCollection services)
|
||||||
=> services.AddSingleton<StateManager>();
|
=> services.AddSingleton<StateManager>()
|
||||||
|
.AddSingleton<StateListener>();
|
||||||
|
|
||||||
private static IServiceCollection AddUi(this IServiceCollection services)
|
private static IServiceCollection AddUi(this IServiceCollection services)
|
||||||
=> services.AddSingleton<DebugTab>()
|
=> services.AddSingleton<DebugTab>()
|
||||||
|
|
@ -88,7 +90,8 @@ public static class ServiceManager
|
||||||
.AddSingleton<CustomizationDrawer>()
|
.AddSingleton<CustomizationDrawer>()
|
||||||
.AddSingleton<DesignFileSystemSelector>()
|
.AddSingleton<DesignFileSystemSelector>()
|
||||||
.AddSingleton<DesignPanel>()
|
.AddSingleton<DesignPanel>()
|
||||||
.AddSingleton<DesignTab>();
|
.AddSingleton<DesignTab>()
|
||||||
|
.AddSingleton<PenumbraChangedItemTooltip>();
|
||||||
|
|
||||||
private static IServiceCollection AddApi(this IServiceCollection services)
|
private static IServiceCollection AddApi(this IServiceCollection services)
|
||||||
=> services.AddSingleton<CommandService>();
|
=> services.AddSingleton<CommandService>();
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ public class ActorState
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActorIdentifier Identifier { get; internal init; }
|
public ActorIdentifier Identifier { get; internal init; }
|
||||||
public DesignData Data;
|
public DesignData ActorData;
|
||||||
|
public DesignData ModelData;
|
||||||
|
|
||||||
private readonly StateChanged.Source[] _sources = Enumerable
|
private readonly StateChanged.Source[] _sources = Enumerable
|
||||||
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 4).ToArray();
|
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 4).ToArray();
|
||||||
|
|
|
||||||
283
Glamourer/State/StateListener.cs
Normal file
283
Glamourer/State/StateListener.cs
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Events;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.State;
|
||||||
|
|
||||||
|
public class StateListener : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Configuration _config;
|
||||||
|
private readonly ActorService _actors;
|
||||||
|
private readonly StateManager _manager;
|
||||||
|
private readonly ItemManager _items;
|
||||||
|
private readonly PenumbraService _penumbra;
|
||||||
|
private readonly SlotUpdating _slotUpdating;
|
||||||
|
private readonly WeaponLoading _weaponLoading;
|
||||||
|
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get => _config.Enabled;
|
||||||
|
set => Enable(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
|
||||||
|
SlotUpdating slotUpdating, WeaponLoading weaponLoading)
|
||||||
|
{
|
||||||
|
_manager = manager;
|
||||||
|
_items = items;
|
||||||
|
_penumbra = penumbra;
|
||||||
|
_actors = actors;
|
||||||
|
_config = config;
|
||||||
|
_slotUpdating = slotUpdating;
|
||||||
|
_weaponLoading = weaponLoading;
|
||||||
|
|
||||||
|
if (Enabled)
|
||||||
|
Subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enable(bool value)
|
||||||
|
{
|
||||||
|
if (value == Enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_config.Enabled = value;
|
||||||
|
_config.Save();
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
Subscribe();
|
||||||
|
else
|
||||||
|
Unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
if (Enabled)
|
||||||
|
Unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OnCreatingCharacterBase(nint actorPtr, string _, nint modelPtr, nint customizePtr, nint equipDataPtr)
|
||||||
|
{
|
||||||
|
// TODO: Fixed Designs.
|
||||||
|
var actor = (Actor)actorPtr;
|
||||||
|
var identifier = actor.GetIdentifier(_actors.AwaitedService);
|
||||||
|
|
||||||
|
if (*(int*)modelPtr != actor.AsCharacter->ModelCharaId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ref var customize = ref *(Customize*)customizePtr;
|
||||||
|
if (_manager.TryGetValue(identifier, out var state))
|
||||||
|
{
|
||||||
|
ApplyCustomize(actor, state, ref customize);
|
||||||
|
ApplyEquipment(actor, state, (CharacterArmor*)equipDataPtr);
|
||||||
|
if (_config.UseRestrictedGearProtection)
|
||||||
|
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
||||||
|
}
|
||||||
|
else if (_config.UseRestrictedGearProtection && *(uint*)modelPtr == 0)
|
||||||
|
{
|
||||||
|
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSlotUpdating(Model model, EquipSlot slot, Ref<CharacterArmor> armor, Ref<ulong> returnValue)
|
||||||
|
{
|
||||||
|
// TODO handle hat state
|
||||||
|
// TODO handle fixed designs
|
||||||
|
var actor = _penumbra.GameObjectFromDrawObject(model);
|
||||||
|
var customize = model.GetCustomize();
|
||||||
|
if (actor.Identifier(_actors.AwaitedService, out var identifier)
|
||||||
|
&& _manager.TryGetValue(identifier, out var state))
|
||||||
|
ApplyEquipmentPiece(actor, state, slot, ref armor.Value);
|
||||||
|
|
||||||
|
var (replaced, replacedArmor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
||||||
|
if (replaced)
|
||||||
|
armor.Assign(replacedArmor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon)
|
||||||
|
{
|
||||||
|
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|
||||||
|
|| !_manager.TryGetValue(identifier, out var state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ref var actorWeapon = ref weapon.Value;
|
||||||
|
var stateItem = state.ModelData.Item(slot);
|
||||||
|
if (actorWeapon.Set.Value != stateItem.ModelId.Value
|
||||||
|
|| actorWeapon.Type.Value != stateItem.WeaponType
|
||||||
|
|| actorWeapon.Variant != stateItem.Variant)
|
||||||
|
{
|
||||||
|
var oldActorItem = state.ActorData.Item(slot);
|
||||||
|
if (oldActorItem.ModelId.Value == actorWeapon.Set.Value
|
||||||
|
&& oldActorItem.WeaponType.Value == actorWeapon.Type.Value
|
||||||
|
&& oldActorItem.Variant == actorWeapon.Variant)
|
||||||
|
{
|
||||||
|
actorWeapon.Set = stateItem.ModelId;
|
||||||
|
actorWeapon.Type = stateItem.WeaponType;
|
||||||
|
actorWeapon.Variant = stateItem.Variant;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var identified = _items.Identify(slot, actorWeapon.Set, actorWeapon.Type, (byte)actorWeapon.Variant,
|
||||||
|
slot == EquipSlot.OffHand ? state.ActorData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown);
|
||||||
|
state.ActorData.SetItem(slot, identified);
|
||||||
|
if (state[slot, false] is not StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
state.ModelData.SetItem(slot, identified);
|
||||||
|
state[slot, false] = StateChanged.Source.Game;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actorWeapon.Set = stateItem.ModelId;
|
||||||
|
actorWeapon.Type = stateItem.Variant;
|
||||||
|
actorWeapon.Variant = stateItem.Variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stateStain = state.ModelData.Stain(slot);
|
||||||
|
if (actorWeapon.Stain.Value != stateStain.Value)
|
||||||
|
{
|
||||||
|
var oldActorStain = state.ActorData.Stain(slot);
|
||||||
|
if (state[slot, true] is not StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
state.ModelData.SetStain(slot, actorWeapon.Stain);
|
||||||
|
state[slot, true] = StateChanged.Source.Game;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actorWeapon.Stain = stateStain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ApplyCustomize(Actor actor, ActorState state, ref Customize customize)
|
||||||
|
{
|
||||||
|
var actorCustomize = actor.GetCustomize();
|
||||||
|
ref var oldActorCustomize = ref state.ActorData.Customize;
|
||||||
|
ref var stateCustomize = ref state.ModelData.Customize;
|
||||||
|
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
var value = customize[idx];
|
||||||
|
var actorValue = actorCustomize[idx];
|
||||||
|
if (value.Value != actorValue.Value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var stateValue = stateCustomize[idx];
|
||||||
|
if (value.Value == stateValue.Value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (oldActorCustomize[idx].Value == actorValue.Value)
|
||||||
|
{
|
||||||
|
customize[idx] = stateValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
oldActorCustomize[idx] = actorValue;
|
||||||
|
if (state[idx] is StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
state.ModelData.Customize[idx] = value;
|
||||||
|
state[idx] = StateChanged.Source.Game;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
customize[idx] = stateValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ApplyEquipment(Actor actor, ActorState state, CharacterArmor* equipData)
|
||||||
|
{
|
||||||
|
// TODO: Handle hat state
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
ApplyEquipmentPiece(actor, state, slot, ref *equipData++);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyEquipmentPiece(Actor actor, ActorState state, EquipSlot slot, ref CharacterArmor armor)
|
||||||
|
{
|
||||||
|
var actorArmor = actor.GetArmor(slot);
|
||||||
|
if (armor.Value != actorArmor.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var stateArmor = state.ModelData.Item(slot);
|
||||||
|
if (armor.Set.Value != stateArmor.ModelId.Value || armor.Variant != stateArmor.Variant)
|
||||||
|
{
|
||||||
|
var oldActorArmor = state.ActorData.Item(slot);
|
||||||
|
if (oldActorArmor.ModelId.Value == actorArmor.Set.Value && oldActorArmor.Variant == actorArmor.Variant)
|
||||||
|
{
|
||||||
|
armor.Set = stateArmor.ModelId;
|
||||||
|
armor.Variant = stateArmor.Variant;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var identified = _items.Identify(slot, actorArmor.Set, actorArmor.Variant);
|
||||||
|
state.ActorData.SetItem(slot, identified);
|
||||||
|
if (state[slot, false] is not StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
state.ModelData.SetItem(slot, identified);
|
||||||
|
state[slot, false] = StateChanged.Source.Game;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
armor.Set = stateArmor.ModelId;
|
||||||
|
armor.Variant = stateArmor.Variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stateStain = state.ModelData.Stain(slot);
|
||||||
|
if (armor.Stain.Value != stateStain.Value)
|
||||||
|
{
|
||||||
|
var oldActorStain = state.ActorData.Stain(slot);
|
||||||
|
if (oldActorStain.Value == actorArmor.Stain.Value)
|
||||||
|
{
|
||||||
|
armor.Stain = stateStain;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.ActorData.SetStain(slot, actorArmor.Stain);
|
||||||
|
if (state[slot, true] is not StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
state.ModelData.SetStain(slot, actorArmor.Stain);
|
||||||
|
state[slot, true] = StateChanged.Source.Game;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
armor.Stain = stateStain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ProtectRestrictedGear(nint equipDataPtr, Race race, Gender gender)
|
||||||
|
{
|
||||||
|
var idx = 0;
|
||||||
|
var ptr = (CharacterArmor*)equipDataPtr;
|
||||||
|
for (var end = ptr + 10; ptr < end; ++ptr)
|
||||||
|
{
|
||||||
|
var (_, newArmor) =
|
||||||
|
_items.RestrictedGear.ResolveRestricted(*ptr, EquipSlotExtensions.EqdpSlots[idx++], race, gender);
|
||||||
|
*ptr = newArmor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Subscribe()
|
||||||
|
{
|
||||||
|
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
|
||||||
|
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
|
||||||
|
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Unsubscribe()
|
||||||
|
{
|
||||||
|
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
|
||||||
|
_slotUpdating.Unsubscribe(OnSlotUpdating);
|
||||||
|
_weaponLoading.Unsubscribe(OnWeaponLoading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
||||||
public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>, IDisposable
|
public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
{
|
{
|
||||||
private readonly ActorService _actors;
|
private readonly ActorService _actors;
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
|
|
@ -26,57 +26,20 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>, ID
|
||||||
private readonly StateChanged _event;
|
private readonly StateChanged _event;
|
||||||
|
|
||||||
private readonly PenumbraService _penumbra;
|
private readonly PenumbraService _penumbra;
|
||||||
private readonly UpdatedSlot _updatedSlot;
|
|
||||||
|
|
||||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
||||||
|
|
||||||
|
|
||||||
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor, StateChanged @event,
|
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor, StateChanged @event,
|
||||||
UpdatedSlot updatedSlot, PenumbraService penumbra)
|
PenumbraService penumbra)
|
||||||
{
|
{
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_items = items;
|
_items = items;
|
||||||
_customizations = customizations;
|
_customizations = customizations;
|
||||||
_visor = visor;
|
_visor = visor;
|
||||||
_event = @event;
|
_event = @event;
|
||||||
_updatedSlot = updatedSlot;
|
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_updatedSlot.Subscribe(OnSlotUpdated, UpdatedSlot.Priority.StateManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_updatedSlot.Unsubscribe(OnSlotUpdated);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void OnSlotUpdated(Model model, EquipSlot slot, Ref<CharacterArmor> armor, Ref<ulong> returnValue)
|
|
||||||
{
|
|
||||||
var actor = _penumbra.GameObjectFromDrawObject(model);
|
|
||||||
var customize = model.GetCustomize();
|
|
||||||
if (!actor.AsCharacter->DrawData.IsHatHidden && actor.Identifier(_actors.AwaitedService, out var identifier) && _states.TryGetValue(identifier, out var state))
|
|
||||||
{
|
|
||||||
ref var armorState = ref state[slot, false];
|
|
||||||
ref var stainState = ref state[slot, true];
|
|
||||||
if (armorState != StateChanged.Source.Fixed)
|
|
||||||
{
|
|
||||||
armorState = StateChanged.Source.Game;
|
|
||||||
var current = state.Data.Item(slot);
|
|
||||||
if (current.ModelId.Value != armor.Value.Set.Value || current.Variant != armor.Value.Variant)
|
|
||||||
{
|
|
||||||
var item = _items.Identify(slot, armor.Value.Set, armor.Value.Variant);
|
|
||||||
state.Data.SetItem(slot, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stainState != StateChanged.Source.Fixed)
|
|
||||||
{
|
|
||||||
stainState = StateChanged.Source.Game;
|
|
||||||
state.Data.SetStain(slot, armor.Value.Stain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (replaced, replacedArmor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
|
||||||
if (replaced)
|
|
||||||
armor.Assign(replacedArmor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||||
|
|
@ -90,7 +53,12 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>, ID
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var designData = FromActor(actor);
|
var designData = FromActor(actor);
|
||||||
_states.Add(identifier, new ActorState(identifier) { Data = designData });
|
state = new ActorState(identifier)
|
||||||
|
{
|
||||||
|
ModelData = designData,
|
||||||
|
ActorData = designData
|
||||||
|
};
|
||||||
|
_states.Add(identifier, state);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -100,84 +68,68 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>, ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Update(ref DesignData data, Actor actor)
|
public void UpdateEquip(ActorState state, EquipSlot slot, CharacterArmor armor)
|
||||||
|
{
|
||||||
|
var current = state.ModelData.Item(slot);
|
||||||
|
if (armor.Set.Value != current.ModelId.Value || armor.Variant != current.Variant)
|
||||||
|
{
|
||||||
|
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||||
|
state.ModelData.SetItem(slot, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ModelData.SetStain(slot, armor.Stain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateWeapon(ActorState state, EquipSlot slot, CharacterWeapon weapon)
|
||||||
|
{
|
||||||
|
var current = state.ModelData.Item(slot);
|
||||||
|
if (weapon.Set.Value != current.ModelId.Value || weapon.Variant != current.Variant || weapon.Type.Value != current.WeaponType.Value)
|
||||||
|
{
|
||||||
|
var item = _items.Identify(slot, weapon.Set, weapon.Type, (byte)weapon.Variant,
|
||||||
|
slot == EquipSlot.OffHand ? state.ModelData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown);
|
||||||
|
state.ModelData.SetItem(slot, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ModelData.SetStain(slot, weapon.Stain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Update(ActorState state, Actor actor)
|
||||||
{
|
{
|
||||||
if (!actor.IsCharacter)
|
if (!actor.IsCharacter)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (actor.AsCharacter->ModelCharaId != data.ModelId)
|
if (actor.AsCharacter->ModelCharaId != state.ModelData.ModelId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var model = actor.Model;
|
var model = actor.Model;
|
||||||
|
|
||||||
static bool EqualArmor(CharacterArmor armor, EquipItem item)
|
state.ModelData.SetHatVisible(!actor.AsCharacter->DrawData.IsHatHidden);
|
||||||
=> armor.Set.Value == item.ModelId.Value && armor.Variant == item.Variant;
|
state.ModelData.SetIsWet(actor.AsCharacter->IsGPoseWet);
|
||||||
|
state.ModelData.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
||||||
|
|
||||||
static bool EqualWeapon(CharacterWeapon weapon, EquipItem item)
|
|
||||||
=> weapon.Set.Value == item.ModelId.Value && weapon.Type.Value == item.WeaponType.Value && weapon.Variant == item.Variant;
|
|
||||||
|
|
||||||
data.SetHatVisible(!actor.AsCharacter->DrawData.IsHatHidden);
|
|
||||||
data.SetIsWet(actor.AsCharacter->IsGPoseWet);
|
|
||||||
data.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
|
|
||||||
|
|
||||||
CharacterWeapon main;
|
|
||||||
CharacterWeapon off;
|
|
||||||
if (model.IsHuman)
|
if (model.IsHuman)
|
||||||
{
|
{
|
||||||
var head = data.IsHatVisible() ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
|
var head = state.ModelData.IsHatVisible() ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
|
||||||
data.SetStain(EquipSlot.Head, head.Stain);
|
UpdateEquip(state, EquipSlot.Head, head);
|
||||||
if (!EqualArmor(head, data.Item(EquipSlot.Head)))
|
|
||||||
{
|
|
||||||
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant);
|
|
||||||
data.SetItem(EquipSlot.Head, headItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
|
||||||
{
|
UpdateEquip(state, slot, model.GetArmor(slot));
|
||||||
var armor = model.GetArmor(slot);
|
|
||||||
data.SetStain(slot, armor.Stain);
|
|
||||||
if (EqualArmor(armor, data.Item(slot)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
state.ModelData.Customize = model.GetCustomize();
|
||||||
data.SetItem(slot, item);
|
var (_, _, main, off) = model.GetWeapons(actor);
|
||||||
}
|
UpdateWeapon(state, EquipSlot.MainHand, main);
|
||||||
|
UpdateWeapon(state, EquipSlot.OffHand, off);
|
||||||
data.Customize = model.GetCustomize();
|
state.ModelData.SetVisor(_visor.GetVisorState(model));
|
||||||
(_, _, main, off) = model.GetWeapons(actor);
|
|
||||||
data.SetVisor(_visor.GetVisorState(model));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
UpdateEquip(state, slot, actor.GetArmor(slot));
|
||||||
var armor = actor.GetArmor(slot);
|
|
||||||
data.SetStain(slot, armor.Stain);
|
|
||||||
if (EqualArmor(armor, data.Item(slot)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
state.ModelData.Customize = actor.GetCustomize();
|
||||||
data.SetItem(slot, item);
|
UpdateWeapon(state, EquipSlot.MainHand, actor.GetMainhand());
|
||||||
}
|
UpdateWeapon(state, EquipSlot.OffHand, actor.GetOffhand());
|
||||||
|
state.ModelData.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
|
||||||
data.Customize = actor.GetCustomize();
|
|
||||||
main = actor.GetMainhand();
|
|
||||||
off = actor.GetOffhand();
|
|
||||||
data.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.SetStain(EquipSlot.MainHand, main.Stain);
|
|
||||||
data.SetStain(EquipSlot.OffHand, off.Stain);
|
|
||||||
if (!EqualWeapon(main, data.Item(EquipSlot.MainHand)))
|
|
||||||
{
|
|
||||||
var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, (byte)main.Variant);
|
|
||||||
data.SetItem(EquipSlot.MainHand, mainItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EqualWeapon(off, data.Item(EquipSlot.OffHand)))
|
|
||||||
{
|
|
||||||
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, (byte)off.Variant, data.Item(EquipSlot.MainHand).Type);
|
|
||||||
data.SetItem(EquipSlot.OffHand, offItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,11 +233,11 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>, ID
|
||||||
if (s is StateChanged.Source.Fixed && source is StateChanged.Source.Game)
|
if (s is StateChanged.Source.Fixed && source is StateChanged.Source.Game)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var oldValue = state.Data.Customize[idx];
|
var oldValue = state.ModelData.Customize[idx];
|
||||||
if (oldValue == value && !force)
|
if (oldValue == value && !force)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
state.Data.Customize[idx] = value;
|
state.ModelData.Customize[idx] = value;
|
||||||
|
|
||||||
Glamourer.Log.Excessive(
|
Glamourer.Log.Excessive(
|
||||||
$"Changed customize {idx.ToDefaultName()} for {state.Identifier} ({string.Join(", ", data.Objects.Select(o => $"0x{o.Address}"))}) from {oldValue.Value} to {value.Value}.");
|
$"Changed customize {idx.ToDefaultName()} for {state.Identifier} ({string.Join(", ", data.Objects.Select(o => $"0x{o.Address}"))}) from {oldValue.Value} to {value.Value}.");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue