mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
5e6f797af4
commit
803fd1b247
16 changed files with 521 additions and 132 deletions
|
|
@ -72,4 +72,22 @@ public static class EquipFlagExtensions
|
|||
EquipSlot.LFinger => EquipFlag.LFingerStain,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static EquipFlag ToBothFlags(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => EquipFlag.Mainhand | EquipFlag.MainhandStain,
|
||||
EquipSlot.OffHand => EquipFlag.Offhand | EquipFlag.OffhandStain,
|
||||
EquipSlot.Head => EquipFlag.Head | EquipFlag.HeadStain,
|
||||
EquipSlot.Body => EquipFlag.Body | EquipFlag.BodyStain,
|
||||
EquipSlot.Hands => EquipFlag.Hands | EquipFlag.HandsStain,
|
||||
EquipSlot.Legs => EquipFlag.Legs | EquipFlag.LegsStain,
|
||||
EquipSlot.Feet => EquipFlag.Feet | EquipFlag.FeetStain,
|
||||
EquipSlot.Ears => EquipFlag.Ears | EquipFlag.EarsStain,
|
||||
EquipSlot.Neck => EquipFlag.Neck | EquipFlag.NeckStain,
|
||||
EquipSlot.Wrists => EquipFlag.Wrist | EquipFlag.WristStain,
|
||||
EquipSlot.RFinger => EquipFlag.RFinger | EquipFlag.RFingerStain,
|
||||
EquipSlot.LFinger => EquipFlag.LFinger | EquipFlag.LFingerStain,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public class Configuration : IPluginConfiguration, ISavable
|
|||
public bool Enabled { get; set; } = true;
|
||||
public bool UseRestrictedGearProtection { get; set; } = true;
|
||||
public bool OpenFoldersByDefault { get; set; } = false;
|
||||
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
||||
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class Design : ISavable
|
|||
|
||||
/// <summary> Unconditionally apply a design to a designdata. </summary>
|
||||
/// <returns>Whether a redraw is required for the changes to take effect.</returns>
|
||||
public bool ApplyDesign(ref DesignData data)
|
||||
public (bool, CustomizeFlag, EquipFlag) ApplyDesign(ref DesignData data)
|
||||
{
|
||||
var modelChanged = data.ModelId != DesignData.ModelId;
|
||||
data.ModelId = DesignData.ModelId;
|
||||
|
|
@ -52,13 +52,16 @@ public class Design : ISavable
|
|||
customizeFlags |= index.ToFlag();
|
||||
}
|
||||
|
||||
EquipFlag equipFlags = 0;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
|
||||
{
|
||||
if (DoApplyEquip(slot))
|
||||
data.SetItem(slot, DesignData.Item(slot));
|
||||
if (data.SetItem(slot, DesignData.Item(slot)))
|
||||
equipFlags |= slot.ToFlag();
|
||||
|
||||
if (DoApplyStain(slot))
|
||||
data.SetStain(slot, DesignData.Stain(slot));
|
||||
if (data.SetStain(slot, DesignData.Stain(slot)))
|
||||
equipFlags |= slot.ToStainFlag();
|
||||
}
|
||||
|
||||
if (DoApplyHatVisible())
|
||||
|
|
@ -72,7 +75,7 @@ public class Design : ISavable
|
|||
|
||||
if (DoApplyWetness())
|
||||
data.SetIsWet(DesignData.IsWet());
|
||||
return modelChanged || customizeFlags.RequiresRedraw();
|
||||
return (modelChanged, customizeFlags, equipFlags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -63,6 +63,24 @@ public unsafe struct DesignData
|
|||
// @formatter:on
|
||||
};
|
||||
|
||||
public readonly CharacterArmor Armor(EquipSlot slot)
|
||||
{
|
||||
fixed (byte* ptr = _equipmentBytes)
|
||||
{
|
||||
var armorPtr = (CharacterArmor*)ptr;
|
||||
return armorPtr[slot.ToIndex()];
|
||||
}
|
||||
}
|
||||
|
||||
public readonly CharacterWeapon Weapon(EquipSlot slot)
|
||||
{
|
||||
fixed (byte* ptr = _equipmentBytes)
|
||||
{
|
||||
var armorPtr = (CharacterArmor*)ptr;
|
||||
return armorPtr[slot is EquipSlot.MainHand ? 10 : 11].ToWeapon(_secondaryMainhand);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetItem(EquipSlot slot, EquipItem item)
|
||||
{
|
||||
var index = slot.ToIndex();
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Interop.Structs;
|
||||
|
|
@ -854,11 +856,81 @@ public unsafe class DebugTab : ITab
|
|||
}
|
||||
}
|
||||
|
||||
public void DrawState(ActorData data, ActorState state)
|
||||
{
|
||||
using var table = ImRaii.Table("##state", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Name");
|
||||
ImGuiUtil.DrawTableColumn(state.Identifier.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Reset"))
|
||||
_state.ResetState(state);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
|
||||
static void PrintRow<T>(string label, T actor, T model, StateChanged.Source source) where T : notnull
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(label);
|
||||
ImGuiUtil.DrawTableColumn(actor.ToString()!);
|
||||
ImGuiUtil.DrawTableColumn(model.ToString()!);
|
||||
ImGuiUtil.DrawTableColumn(source.ToString());
|
||||
}
|
||||
|
||||
static string ItemString(in DesignData data, EquipSlot slot)
|
||||
{
|
||||
var item = data.Item(slot);
|
||||
return $"{item.Name} ({item.ModelId.Value}{(item.WeaponType != 0 ? $"-{item.WeaponType.Value}" : string.Empty)}-{item.Variant})";
|
||||
}
|
||||
|
||||
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaFlag.ModelId]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaFlag.Wetness]);
|
||||
ImGui.TableNextRow();
|
||||
|
||||
if (state.BaseData.ModelId == 0 && state.ModelData.ModelId == 0)
|
||||
{
|
||||
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaFlag.HatState]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
|
||||
state[ActorState.MetaFlag.VisorState]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
|
||||
state[ActorState.MetaFlag.WeaponState]);
|
||||
ImGui.TableNextRow();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||
{
|
||||
PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]);
|
||||
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Value.ToString());
|
||||
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Value.ToString());
|
||||
ImGuiUtil.DrawTableColumn(state[slot, true].ToString());
|
||||
}
|
||||
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]);
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(string.Join(" ", state.BaseData.GetCustomizeBytes().Select(b => b.ToString("X2"))));
|
||||
ImGuiUtil.DrawTableColumn(string.Join(" ", state.ModelData.GetCustomizeBytes().Select(b => b.ToString("X2"))));
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn(string.Join(" ", state.BaseData.GetEquipmentBytes().Select(b => b.ToString("X2"))));
|
||||
ImGuiUtil.DrawTableColumn(string.Join(" ", state.ModelData.GetEquipmentBytes().Select(b => b.ToString("X2"))));
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawDesignData(in DesignData data)
|
||||
{
|
||||
if (data.ModelId == 0)
|
||||
{
|
||||
using var table = ImRaii.Table("##equip", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||
{
|
||||
var item = data.Item(slot);
|
||||
|
|
@ -1011,7 +1083,7 @@ public unsafe class DebugTab : ITab
|
|||
continue;
|
||||
|
||||
if (_state.GetOrCreate(identifier, actors.Objects[0], out var state))
|
||||
DrawDesignData(state.ModelData);
|
||||
DrawState(actors, state);
|
||||
else
|
||||
ImGui.TextUnformatted("Invalid actor.");
|
||||
}
|
||||
|
|
@ -1026,8 +1098,10 @@ public unsafe class DebugTab : ITab
|
|||
foreach (var (identifier, state) in _state.Where(kvp => !_objectManager.ContainsKey(kvp.Key)))
|
||||
{
|
||||
using var t = ImRaii.TreeNode(identifier.ToString());
|
||||
if (t)
|
||||
DrawDesignData(state.ModelData);
|
||||
if (!t)
|
||||
return;
|
||||
|
||||
DrawState(ActorData.Invalid, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,44 @@
|
|||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||
|
||||
public class DesignPanel
|
||||
{
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly DesignFileSystemSelector _selector;
|
||||
private readonly DesignManager _manager;
|
||||
private readonly CustomizationDrawer _customizationDrawer;
|
||||
private readonly StateManager _state;
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly UpdateSlotService _updateSlot;
|
||||
private readonly WeaponService _weaponService;
|
||||
private readonly ChangeCustomizeService _changeCustomizeService;
|
||||
|
||||
public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager)
|
||||
public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, ObjectManager objects,
|
||||
StateManager state, PenumbraService penumbra, ChangeCustomizeService changeCustomizeService, WeaponService weaponService,
|
||||
UpdateSlotService updateSlot)
|
||||
{
|
||||
_selector = selector;
|
||||
_customizationDrawer = customizationDrawer;
|
||||
_manager = manager;
|
||||
_selector = selector;
|
||||
_customizationDrawer = customizationDrawer;
|
||||
_manager = manager;
|
||||
_objects = objects;
|
||||
_state = state;
|
||||
_penumbra = penumbra;
|
||||
_changeCustomizeService = changeCustomizeService;
|
||||
_weaponService = weaponService;
|
||||
_updateSlot = updateSlot;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -28,6 +51,14 @@ public class DesignPanel
|
|||
if (!child)
|
||||
return;
|
||||
|
||||
if (ImGui.Button("TEST"))
|
||||
{
|
||||
var (id, data) = _objects.PlayerData;
|
||||
|
||||
if (data.Valid && _state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
_state.ApplyDesign(design, state);
|
||||
}
|
||||
|
||||
_customizationDrawer.Draw(design.DesignData.Customize, design.WriteProtected());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Gui.Tabs.DesignTab;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
@ -15,12 +16,14 @@ public class SettingsTab : ITab
|
|||
private readonly Configuration _config;
|
||||
private readonly DesignFileSystemSelector _selector;
|
||||
private readonly StateListener _stateListener;
|
||||
private readonly PenumbraAutoRedraw _autoRedraw;
|
||||
|
||||
public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener)
|
||||
public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener, PenumbraAutoRedraw autoRedraw)
|
||||
{
|
||||
_config = config;
|
||||
_selector = selector;
|
||||
_config = config;
|
||||
_selector = selector;
|
||||
_stateListener = stateListener;
|
||||
_autoRedraw = autoRedraw;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -31,10 +34,14 @@ public class SettingsTab : ITab
|
|||
using var child = ImRaii.Child("MainWindowChild");
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
Checkbox("Enabled", "Enable main functionality of keeping and applying state.", _stateListener.Enabled, _stateListener.Enable);
|
||||
Checkbox("Restricted Gear Protection",
|
||||
"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);
|
||||
Checkbox("Auto-Reload Gear",
|
||||
"Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection.",
|
||||
_config.AutoRedrawEquipOnChanges, _autoRedraw.SetState);
|
||||
if (Widget.DoubleModifierSelector("Design Deletion Modifier",
|
||||
"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))
|
||||
|
|
|
|||
67
Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs
Normal file
67
Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using Glamourer.State;
|
||||
using Penumbra.Api.Enums;
|
||||
|
||||
namespace Glamourer.Interop.Penumbra;
|
||||
|
||||
public class PenumbraAutoRedraw : IDisposable
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly StateManager _state;
|
||||
private readonly ObjectManager _objects;
|
||||
private bool _enabled;
|
||||
|
||||
public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects)
|
||||
{
|
||||
_penumbra = penumbra;
|
||||
_config = config;
|
||||
_state = state;
|
||||
_objects = objects;
|
||||
if (_config.AutoRedrawEquipOnChanges)
|
||||
Enable();
|
||||
}
|
||||
|
||||
public void SetState(bool value)
|
||||
{
|
||||
if (value == _config.AutoRedrawEquipOnChanges)
|
||||
return;
|
||||
|
||||
_config.AutoRedrawEquipOnChanges = value;
|
||||
_config.Save();
|
||||
if (value)
|
||||
Enable();
|
||||
else
|
||||
Disable();
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (_enabled)
|
||||
return;
|
||||
|
||||
_penumbra.ModSettingChanged += OnModSettingChange;
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
_penumbra.ModSettingChanged -= OnModSettingChange;
|
||||
_enabled = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disable();
|
||||
}
|
||||
|
||||
private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited)
|
||||
{
|
||||
var playerName = _penumbra.GetCurrentPlayerCollection();
|
||||
if (playerName == name)
|
||||
_state.ReapplyState(_objects.Player);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,14 +13,16 @@ public unsafe class PenumbraService : IDisposable
|
|||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
||||
|
||||
private readonly EventSubscriber _initializedEvent;
|
||||
private readonly EventSubscriber _disposedEvent;
|
||||
|
|
@ -35,6 +37,7 @@ public unsafe class PenumbraService : IDisposable
|
|||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi);
|
||||
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi);
|
||||
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi);
|
||||
_modSettingChanged = Ipc.ModSettingChanged.Subscriber(pi);
|
||||
Reattach();
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +66,22 @@ public unsafe class PenumbraService : IDisposable
|
|||
remove => _createdCharacterBase.Event -= value;
|
||||
}
|
||||
|
||||
public event Action<ModSettingChange, string, string, bool> ModSettingChanged
|
||||
{
|
||||
add => _modSettingChanged.Event += value;
|
||||
remove => _modSettingChanged.Event -= value;
|
||||
}
|
||||
|
||||
/// <summary> Obtain the name of the collection currently assigned to the player. </summary>
|
||||
public string GetCurrentPlayerCollection()
|
||||
{
|
||||
if (!Available)
|
||||
return string.Empty;
|
||||
|
||||
var (valid, _, name) = _objectCollection.Invoke(0);
|
||||
return valid ? name : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary> Obtain the game object corresponding to a draw object. </summary>
|
||||
public Actor GameObjectFromDrawObject(Model drawObject)
|
||||
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null;
|
||||
|
|
@ -103,9 +122,11 @@ public unsafe class PenumbraService : IDisposable
|
|||
_clickSubscriber.Enable();
|
||||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_modSettingChanged.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||
Available = true;
|
||||
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
||||
}
|
||||
|
|
@ -122,6 +143,7 @@ public unsafe class PenumbraService : IDisposable
|
|||
_clickSubscriber.Disable();
|
||||
_creatingCharacterBase.Disable();
|
||||
_createdCharacterBase.Disable();
|
||||
_modSettingChanged.Disable();
|
||||
if (Available)
|
||||
{
|
||||
Available = false;
|
||||
|
|
@ -138,5 +160,6 @@ public unsafe class PenumbraService : IDisposable
|
|||
_createdCharacterBase.Dispose();
|
||||
_initializedEvent.Dispose();
|
||||
_disposedEvent.Dispose();
|
||||
_modSettingChanged.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OtterGui.Log;
|
||||
|
||||
namespace Glamourer.Interop.Structs;
|
||||
|
||||
|
|
@ -8,7 +10,7 @@ namespace Glamourer.Interop.Structs;
|
|||
public readonly struct ActorData
|
||||
{
|
||||
public readonly List<Actor> Objects;
|
||||
public readonly string Label;
|
||||
public readonly string Label;
|
||||
|
||||
public bool Valid
|
||||
=> Objects.Count > 0;
|
||||
|
|
@ -16,7 +18,7 @@ public readonly struct ActorData
|
|||
public ActorData(Actor actor, string label)
|
||||
{
|
||||
Objects = new List<Actor> { actor };
|
||||
Label = label;
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public static readonly ActorData Invalid = new(false);
|
||||
|
|
@ -24,6 +26,14 @@ public readonly struct ActorData
|
|||
private ActorData(bool _)
|
||||
{
|
||||
Objects = new List<Actor>(0);
|
||||
Label = string.Empty;
|
||||
Label = string.Empty;
|
||||
}
|
||||
|
||||
public LazyString ToLazyString(string invalid)
|
||||
{
|
||||
var objects = Objects;
|
||||
return Valid
|
||||
? new LazyString(() => string.Join(", ", objects.Select(o => o.ToString())))
|
||||
: new LazyString(() => invalid);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Glamourer.Events;
|
|||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -31,24 +32,18 @@ public unsafe class UpdateSlotService : IDisposable
|
|||
{
|
||||
if (!drawObject.IsCharacterBase)
|
||||
return;
|
||||
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
if (!drawObject.IsCharacterBase)
|
||||
return;
|
||||
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain)
|
||||
=> UpdateSlot(drawObject, slot, armor.With(stain));
|
||||
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data.With(drawObject.GetArmor(slot).Stain));
|
||||
}
|
||||
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
||||
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain);
|
||||
|
||||
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
if (!drawObject.IsHuman)
|
||||
return;
|
||||
|
||||
FlagSlotForUpdateInterop(drawObject, slot, drawObject.GetArmor(slot).With(stain));
|
||||
}
|
||||
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain);
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ public static class ServiceManager
|
|||
.AddSingleton<UpdateSlotService>()
|
||||
.AddSingleton<WeaponService>()
|
||||
.AddSingleton<PenumbraService>()
|
||||
.AddSingleton<ObjectManager>();
|
||||
.AddSingleton<ObjectManager>()
|
||||
.AddSingleton<PenumbraAutoRedraw>();
|
||||
|
||||
private static IServiceCollection AddDesigns(this IServiceCollection services)
|
||||
=> services.AddSingleton<DesignManager>()
|
||||
|
|
@ -77,6 +78,7 @@ public static class ServiceManager
|
|||
|
||||
private static IServiceCollection AddState(this IServiceCollection services)
|
||||
=> services.AddSingleton<StateManager>()
|
||||
.AddSingleton<StateEditor>()
|
||||
.AddSingleton<StateListener>();
|
||||
|
||||
private static IServiceCollection AddUi(this IServiceCollection services)
|
||||
|
|
|
|||
|
|
@ -17,14 +17,20 @@ public class ActorState
|
|||
HatState,
|
||||
VisorState,
|
||||
WeaponState,
|
||||
ModelId,
|
||||
}
|
||||
|
||||
public ActorIdentifier Identifier { get; internal init; }
|
||||
public DesignData ActorData;
|
||||
public DesignData ModelData;
|
||||
|
||||
/// <summary> This should always represent the unmodified state of the draw object. </summary>
|
||||
public DesignData BaseData;
|
||||
|
||||
/// <summary> This should be the desired state of the draw object. </summary>
|
||||
public DesignData ModelData;
|
||||
|
||||
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
|
||||
private readonly StateChanged.Source[] _sources = Enumerable
|
||||
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 4).ToArray();
|
||||
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray();
|
||||
|
||||
internal ActorState(ActorIdentifier identifier)
|
||||
=> Identifier = identifier;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public class StateEditor
|
|||
_items = items;
|
||||
}
|
||||
|
||||
|
||||
public void ChangeCustomize(ActorData data, Customize customize)
|
||||
{
|
||||
foreach (var actor in data.Objects)
|
||||
|
|
@ -43,19 +44,15 @@ public class StateEditor
|
|||
}
|
||||
}
|
||||
|
||||
public void ChangeArmor(ActorData data, EquipSlot slot, EquipItem item)
|
||||
public void ChangeArmor(ActorState state, ActorData data, EquipSlot slot)
|
||||
{
|
||||
var idx = slot.ToIndex();
|
||||
if (idx >= 10)
|
||||
return;
|
||||
|
||||
var armor = item.Armor();
|
||||
var armor = state.ModelData.Armor(slot);
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
{
|
||||
var mdl = actor.Model;
|
||||
var customize = mdl.IsHuman ? mdl.GetCustomize() : actor.GetCustomize();
|
||||
var (_, resolvedItem) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
||||
_updateSlot.UpdateArmor(actor.Model, slot, resolvedItem);
|
||||
_updateSlot.UpdateSlot(actor.Model, slot, resolvedItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,42 +61,46 @@ public class StateListener : IDisposable
|
|||
Unsubscribe();
|
||||
}
|
||||
|
||||
private enum UpdateState
|
||||
{
|
||||
NoChange,
|
||||
Transformed,
|
||||
Change,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
var modelId = *(uint*)modelPtr;
|
||||
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)
|
||||
{
|
||||
switch (UpdateBaseData(actor, state, modelId, customizePtr, equipDataPtr))
|
||||
{
|
||||
case UpdateState.Change: break;
|
||||
case UpdateState.Transformed: break;
|
||||
case UpdateState.NoChange:
|
||||
UpdateBaseData(actor, state, customize);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_config.UseRestrictedGearProtection && modelId == 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);
|
||||
if (_config.UseRestrictedGearProtection)
|
||||
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
||||
}
|
||||
|
||||
private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref<CharacterWeapon> weapon)
|
||||
|
|
@ -111,7 +115,7 @@ public class StateListener : IDisposable
|
|||
|| actorWeapon.Type.Value != stateItem.WeaponType
|
||||
|| actorWeapon.Variant != stateItem.Variant)
|
||||
{
|
||||
var oldActorItem = state.ActorData.Item(slot);
|
||||
var oldActorItem = state.BaseData.Item(slot);
|
||||
if (oldActorItem.ModelId.Value == actorWeapon.Set.Value
|
||||
&& oldActorItem.WeaponType.Value == actorWeapon.Type.Value
|
||||
&& oldActorItem.Variant == actorWeapon.Variant)
|
||||
|
|
@ -123,8 +127,8 @@ public class StateListener : IDisposable
|
|||
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);
|
||||
slot == EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown);
|
||||
state.BaseData.SetItem(slot, identified);
|
||||
if (state[slot, false] is not StateChanged.Source.Fixed)
|
||||
{
|
||||
state.ModelData.SetItem(slot, identified);
|
||||
|
|
@ -142,7 +146,7 @@ public class StateListener : IDisposable
|
|||
var stateStain = state.ModelData.Stain(slot);
|
||||
if (actorWeapon.Stain.Value != stateStain.Value)
|
||||
{
|
||||
var oldActorStain = state.ActorData.Stain(slot);
|
||||
var oldActorStain = state.BaseData.Stain(slot);
|
||||
if (state[slot, true] is not StateChanged.Source.Fixed)
|
||||
{
|
||||
state.ModelData.SetStain(slot, actorWeapon.Stain);
|
||||
|
|
@ -159,7 +163,7 @@ public class StateListener : IDisposable
|
|||
private void ApplyCustomize(Actor actor, ActorState state, ref Customize customize)
|
||||
{
|
||||
var actorCustomize = actor.GetCustomize();
|
||||
ref var oldActorCustomize = ref state.ActorData.Customize;
|
||||
ref var oldActorCustomize = ref state.BaseData.Customize;
|
||||
ref var stateCustomize = ref state.ModelData.Customize;
|
||||
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
|
|
@ -201,57 +205,34 @@ public class StateListener : IDisposable
|
|||
|
||||
private void ApplyEquipmentPiece(Actor actor, ActorState state, EquipSlot slot, ref CharacterArmor armor)
|
||||
{
|
||||
var actorArmor = actor.GetArmor(slot);
|
||||
if (armor.Value != actorArmor.Value)
|
||||
var changeState = UpdateBaseData(actor, state, slot, armor);
|
||||
if (changeState is UpdateState.Transformed)
|
||||
return;
|
||||
|
||||
var stateArmor = state.ModelData.Item(slot);
|
||||
if (armor.Set.Value != stateArmor.ModelId.Value || armor.Variant != stateArmor.Variant)
|
||||
if (changeState is UpdateState.NoChange)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
armor = state.ModelData.Armor(slot);
|
||||
}
|
||||
|
||||
var stateStain = state.ModelData.Stain(slot);
|
||||
if (armor.Stain.Value != stateStain.Value)
|
||||
else
|
||||
{
|
||||
var oldActorStain = state.ActorData.Stain(slot);
|
||||
if (oldActorStain.Value == actorArmor.Stain.Value)
|
||||
var modelArmor = state.ModelData.Armor(slot);
|
||||
if (armor.Value == modelArmor.Value)
|
||||
return;
|
||||
|
||||
if (state[slot, false] is StateChanged.Source.Fixed)
|
||||
{
|
||||
armor.Stain = stateStain;
|
||||
armor.Set = modelArmor.Set;
|
||||
armor.Variant = modelArmor.Variant;
|
||||
}
|
||||
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;
|
||||
}
|
||||
_manager.ChangeEquip(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
|
||||
}
|
||||
|
||||
if (state[slot, true] is StateChanged.Source.Fixed)
|
||||
armor.Stain = modelArmor.Stain;
|
||||
else
|
||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,4 +261,81 @@ public class StateListener : IDisposable
|
|||
_slotUpdating.Unsubscribe(OnSlotUpdating);
|
||||
_weaponLoading.Unsubscribe(OnWeaponLoading);
|
||||
}
|
||||
|
||||
private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterArmor armor)
|
||||
{
|
||||
var actorArmor = actor.GetArmor(slot);
|
||||
// The actor armor does not correspond to the model armor, thus the actor is transformed.
|
||||
if (actorArmor.Value != armor.Value)
|
||||
return UpdateState.Transformed;
|
||||
|
||||
// TODO: Hat State.
|
||||
|
||||
var baseData = state.BaseData.Armor(slot);
|
||||
var change = UpdateState.NoChange;
|
||||
if (baseData.Stain != armor.Stain)
|
||||
{
|
||||
state.BaseData.SetStain(slot, armor.Stain);
|
||||
change = UpdateState.Change;
|
||||
}
|
||||
|
||||
if (baseData.Set.Value != armor.Set.Value || baseData.Variant != armor.Variant)
|
||||
{
|
||||
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||
state.BaseData.SetItem(slot, item);
|
||||
change = UpdateState.Change;
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
var baseData = state.BaseData.Weapon(slot);
|
||||
var change = UpdateState.NoChange;
|
||||
|
||||
if (baseData.Stain != weapon.Stain)
|
||||
{
|
||||
state.BaseData.SetStain(slot, weapon.Stain);
|
||||
change = UpdateState.Change;
|
||||
}
|
||||
|
||||
if (baseData.Set.Value != weapon.Set.Value || baseData.Type.Value != weapon.Type.Value || baseData.Variant != weapon.Variant)
|
||||
{
|
||||
var item = _items.Identify(slot, weapon.Set, weapon.Type, (byte)weapon.Variant,
|
||||
slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown);
|
||||
state.BaseData.SetItem(slot, item);
|
||||
change = UpdateState.Change;
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, uint modelId, nint customizeData, nint equipData)
|
||||
{
|
||||
if (modelId != (uint)actor.AsCharacter->CharacterData.ModelCharaId)
|
||||
return UpdateState.Transformed;
|
||||
|
||||
if (modelId == state.BaseData.ModelId)
|
||||
return UpdateState.NoChange;
|
||||
|
||||
if (modelId == 0)
|
||||
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, (byte*)equipData);
|
||||
else
|
||||
state.BaseData = _manager.FromActor(actor);
|
||||
|
||||
return UpdateState.Change;
|
||||
}
|
||||
|
||||
private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize)
|
||||
{
|
||||
if (!actor.GetCustomize().Equals(customize))
|
||||
return UpdateState.Transformed;
|
||||
|
||||
if (state.BaseData.Customize.Equals(customize))
|
||||
return UpdateState.NoChange;
|
||||
|
||||
state.BaseData.Customize.Load(customize);
|
||||
return UpdateState.Change;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
|
|
@ -11,6 +12,7 @@ using Glamourer.Interop.Penumbra;
|
|||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -24,13 +26,15 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
private readonly CustomizationService _customizations;
|
||||
private readonly VisorService _visor;
|
||||
private readonly StateChanged _event;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly StateEditor _editor;
|
||||
|
||||
private readonly PenumbraService _penumbra;
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
||||
|
||||
public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor, StateChanged @event,
|
||||
PenumbraService penumbra)
|
||||
PenumbraService penumbra, ObjectManager objects, StateEditor editor)
|
||||
{
|
||||
_actors = actors;
|
||||
_items = items;
|
||||
|
|
@ -38,7 +42,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
_visor = visor;
|
||||
_event = @event;
|
||||
_penumbra = penumbra;
|
||||
|
||||
_objects = objects;
|
||||
_editor = editor;
|
||||
}
|
||||
|
||||
public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state)
|
||||
|
|
@ -55,7 +60,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
state = new ActorState(identifier)
|
||||
{
|
||||
ModelData = designData,
|
||||
ActorData = designData
|
||||
BaseData = designData,
|
||||
};
|
||||
_states.Add(identifier, state);
|
||||
return true;
|
||||
|
|
@ -115,7 +120,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
UpdateEquip(state, slot, model.GetArmor(slot));
|
||||
|
||||
state.ModelData.Customize = model.GetCustomize();
|
||||
var (_, _, main, off) = model.GetWeapons(actor);
|
||||
var (_, _, main, off) = model.GetWeapons(actor);
|
||||
UpdateWeapon(state, EquipSlot.MainHand, main);
|
||||
UpdateWeapon(state, EquipSlot.OffHand, off);
|
||||
state.ModelData.SetVisor(_visor.GetVisorState(model));
|
||||
|
|
@ -165,9 +170,9 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (actor.AsCharacter->ModelCharaId != 0)
|
||||
if (actor.AsCharacter->CharacterData.ModelCharaId != 0)
|
||||
{
|
||||
ret.LoadNonHuman((uint)actor.AsCharacter->ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData,
|
||||
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData,
|
||||
(byte*)&actor.AsCharacter->DrawData.Head);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -242,6 +247,94 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
$"Changed customize {idx.ToDefaultName()} for {state.Identifier} ({string.Join(", ", data.Objects.Select(o => $"0x{o.Address}"))}) from {oldValue.Value} to {value.Value}.");
|
||||
_event.Invoke(StateChanged.Type.Customize, source, state, data, (oldValue, value, idx));
|
||||
}
|
||||
|
||||
public void ApplyDesign(Design design, ActorState state)
|
||||
{
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
switch (design.DoApplyEquip(slot), design.DoApplyStain(slot))
|
||||
{
|
||||
case (false, false): continue;
|
||||
case (true, false):
|
||||
ChangeEquip(state, slot, design.DesignData.Item(slot), StateChanged.Source.Manual);
|
||||
break;
|
||||
case (false, true):
|
||||
ChangeStain(state, slot, design.DesignData.Stain(slot), StateChanged.Source.Manual);
|
||||
break;
|
||||
case (true, true):
|
||||
ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), StateChanged.Source.Manual);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetState(ActorState state)
|
||||
{
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
ChangeEquip(state, slot, state.BaseData.Item(slot), state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReapplyState(Actor actor)
|
||||
{
|
||||
if (!GetOrCreate(actor, out var state))
|
||||
return;
|
||||
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
}
|
||||
|
||||
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.Item(slot);
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state[slot, false] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} equipment piece in state {state.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {objects.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Equip, source, state, objects, (old, item, slot));
|
||||
}
|
||||
|
||||
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.Item(slot);
|
||||
var oldStain = state.ModelData.Stain(slot);
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state[slot, false] = source;
|
||||
state[slot, true] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} equipment piece in state {state.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}) and its stain from {oldStain.Value} to {stain.Value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Equip, source, state, objects, (old, item, slot));
|
||||
_event.Invoke(StateChanged.Type.Stain, source, state, objects, (oldStain, stain, slot));
|
||||
}
|
||||
|
||||
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source)
|
||||
{
|
||||
var old = state.ModelData.Stain(slot);
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state[slot, true] = source;
|
||||
_objects.Update();
|
||||
var objects = _objects.TryGetValue(state.Identifier, out var d) ? d : ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual)
|
||||
_editor.ChangeArmor(state, objects, slot);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} stain in state {state.Identifier} from {old.Value} to {stain.Value}. [Affecting {objects.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Stain, source, state, objects, (old, stain, slot));
|
||||
}
|
||||
|
||||
//
|
||||
///// <summary> Change whether to apply a specific customize value. </summary>
|
||||
//public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||
|
|
@ -255,21 +348,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// _event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
|
||||
//}
|
||||
//
|
||||
///// <summary> Change a non-weapon equipment piece. </summary>
|
||||
//public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
|
||||
//{
|
||||
// if (_items.ValidateItem(slot, item.Id, out item).Length > 0)
|
||||
// return;
|
||||
//
|
||||
// var old = design.DesignData.Item(slot);
|
||||
// if (!design.DesignData.SetItem(slot, item))
|
||||
// return;
|
||||
//
|
||||
// Glamourer.Log.Debug(
|
||||
// $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}).");
|
||||
// _saveService.QueueSave(design);
|
||||
// _event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
|
||||
//}
|
||||
|
||||
//
|
||||
///// <summary> Change a weapon. </summary>
|
||||
//public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue