mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-20 22:47:45 +01:00
Some more reworking
This commit is contained in:
parent
6a4b5fc3b2
commit
dad146d043
41 changed files with 1714 additions and 1320 deletions
|
|
@ -27,17 +27,17 @@ public class GlamourerIpc : IDisposable
|
|||
private readonly ObjectTable _objectTable;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
|
||||
//internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
|
||||
//internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
|
||||
//internal ICallGateProvider<string, string, object>? ProviderApplyAll;
|
||||
//internal ICallGateProvider<string, Character?, object>? ProviderApplyAllToCharacter;
|
||||
//internal ICallGateProvider<string, string, object>? ProviderApplyOnlyCustomization;
|
||||
//internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyCustomizationToCharacter;
|
||||
//internal ICallGateProvider<string, string, object>? ProviderApplyOnlyEquipment;
|
||||
//internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyEquipmentToCharacter;
|
||||
//internal ICallGateProvider<string, object>? ProviderRevert;
|
||||
//internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
|
||||
//internal ICallGateProvider<int>? ProviderGetApiVersion;
|
||||
internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
|
||||
internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
|
||||
internal ICallGateProvider<string, string, object>? ProviderApplyAll;
|
||||
internal ICallGateProvider<string, Character?, object>? ProviderApplyAllToCharacter;
|
||||
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyCustomization;
|
||||
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyCustomizationToCharacter;
|
||||
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyEquipment;
|
||||
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyEquipmentToCharacter;
|
||||
internal ICallGateProvider<string, object>? ProviderRevert;
|
||||
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
|
||||
internal ICallGateProvider<int>? ProviderGetApiVersion;
|
||||
|
||||
public GlamourerIpc(ClientState clientState, ObjectTable objectTable, DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
|
|
@ -53,31 +53,31 @@ public class GlamourerIpc : IDisposable
|
|||
|
||||
private void DisposeProviders()
|
||||
{
|
||||
// ProviderGetAllCustomization?.UnregisterFunc();
|
||||
// ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
|
||||
// ProviderApplyAll?.UnregisterAction();
|
||||
// ProviderApplyAllToCharacter?.UnregisterAction();
|
||||
// ProviderApplyOnlyCustomization?.UnregisterAction();
|
||||
// ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
|
||||
// ProviderApplyOnlyEquipment?.UnregisterAction();
|
||||
// ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
|
||||
// ProviderRevert?.UnregisterAction();
|
||||
// ProviderRevertCharacter?.UnregisterAction();
|
||||
// ProviderGetApiVersion?.UnregisterFunc();
|
||||
ProviderGetAllCustomization?.UnregisterFunc();
|
||||
ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
|
||||
ProviderApplyAll?.UnregisterAction();
|
||||
ProviderApplyAllToCharacter?.UnregisterAction();
|
||||
ProviderApplyOnlyCustomization?.UnregisterAction();
|
||||
ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
|
||||
ProviderApplyOnlyEquipment?.UnregisterAction();
|
||||
ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
|
||||
ProviderRevert?.UnregisterAction();
|
||||
ProviderRevertCharacter?.UnregisterAction();
|
||||
ProviderGetApiVersion?.UnregisterFunc();
|
||||
}
|
||||
|
||||
private void InitializeProviders()
|
||||
{
|
||||
//try
|
||||
//{
|
||||
// ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
|
||||
// ProviderGetApiVersion.RegisterFunc(GetApiVersion);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
|
||||
//}
|
||||
//
|
||||
try
|
||||
{
|
||||
ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
|
||||
ProviderGetApiVersion.RegisterFunc(GetApiVersion);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
|
||||
}
|
||||
|
||||
//try
|
||||
//{
|
||||
// ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
|
||||
|
|
@ -187,8 +187,8 @@ public class GlamourerIpc : IDisposable
|
|||
//}
|
||||
}
|
||||
|
||||
//private static int GetApiVersion()
|
||||
// => CurrentApiVersion;
|
||||
private static int GetApiVersion()
|
||||
=> CurrentApiVersion;
|
||||
//
|
||||
//private void ApplyAll(string customization, string characterName)
|
||||
//{
|
||||
|
|
|
|||
|
|
@ -1,41 +1,42 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Api;
|
||||
|
||||
public class PenumbraAttach : IDisposable
|
||||
public unsafe class PenumbraAttach : IDisposable
|
||||
{
|
||||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 12;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
|
||||
private ICallGateSubscriber<ChangedItemType, uint, object>? _tooltipSubscriber;
|
||||
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
||||
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
||||
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
||||
private ICallGateSubscriber<IntPtr, (IntPtr, string)>? _drawObjectInfo;
|
||||
private ICallGateSubscriber<IntPtr, string, IntPtr, IntPtr, IntPtr, object?>? _creatingCharacterBase;
|
||||
private ICallGateSubscriber<IntPtr, string, IntPtr, object?>? _createdCharacterBase;
|
||||
private ICallGateSubscriber<int, int>? _cutsceneParent;
|
||||
private EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private ActionSubscriber<GameObject, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
public EventSubscriber<nint, string, nint, nint, nint> CreatingCharacterBase;
|
||||
public EventSubscriber<nint, string, nint> CreatedCharacterBase;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
|
||||
private readonly ICallGateSubscriber<object?> _initializedEvent;
|
||||
private readonly ICallGateSubscriber<object?> _disposedEvent;
|
||||
|
||||
public event Action<IntPtr, IntPtr, IntPtr, IntPtr>? CreatingCharacterBase;
|
||||
public event Action<IntPtr, IntPtr>? CreatedCharacterBase;
|
||||
private readonly EventSubscriber _initializedEvent;
|
||||
private readonly EventSubscriber _disposedEvent;
|
||||
public bool Available { get; private set; }
|
||||
|
||||
public PenumbraAttach(bool attach)
|
||||
{
|
||||
_initializedEvent = Dalamud.PluginInterface.GetIpcSubscriber<object?>("Penumbra.Initialized");
|
||||
_disposedEvent = Dalamud.PluginInterface.GetIpcSubscriber<object?>("Penumbra.Disposed");
|
||||
_initializedEvent.Subscribe(Reattach);
|
||||
_disposedEvent.Subscribe(Unattach);
|
||||
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
|
||||
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
|
||||
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip);
|
||||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick);
|
||||
CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||
CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||
Reattach(attach);
|
||||
}
|
||||
|
||||
|
|
@ -48,31 +49,22 @@ public class PenumbraAttach : IDisposable
|
|||
{
|
||||
Unattach();
|
||||
|
||||
var versionSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions");
|
||||
var (breaking, feature) = versionSubscriber.InvokeFunc();
|
||||
var (breaking, feature) = Ipc.ApiVersions.Subscriber(Dalamud.PluginInterface).Invoke();
|
||||
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
|
||||
throw new Exception(
|
||||
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||
|
||||
_redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||
_redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
||||
_drawObjectInfo = Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, (IntPtr, string)>("Penumbra.GetDrawObjectInfo");
|
||||
_cutsceneParent = Dalamud.PluginInterface.GetIpcSubscriber<int, int>("Penumbra.GetCutsceneParentIndex");
|
||||
|
||||
if (!attach)
|
||||
return;
|
||||
|
||||
_tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<ChangedItemType, uint, object>("Penumbra.ChangedItemTooltip");
|
||||
_clickSubscriber =
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<MouseButton, ChangedItemType, uint, object>("Penumbra.ChangedItemClick");
|
||||
_creatingCharacterBase =
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, IntPtr, IntPtr, object?>("Penumbra.CreatingCharacterBase");
|
||||
_createdCharacterBase =
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, object?>("Penumbra.CreatedCharacterBase");
|
||||
_tooltipSubscriber.Subscribe(PenumbraTooltip);
|
||||
_clickSubscriber.Subscribe(PenumbraRightClick);
|
||||
_creatingCharacterBase.Subscribe(SubscribeCreatingCharacterBase);
|
||||
_createdCharacterBase.Subscribe(SubscribeCreatedCharacterBase);
|
||||
_tooltipSubscriber.Enable();
|
||||
_clickSubscriber.Enable();
|
||||
CreatingCharacterBase.Enable();
|
||||
CreatedCharacterBase.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface);
|
||||
Available = true;
|
||||
PluginLog.Debug("Glamourer attached to Penumbra.");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -81,35 +73,28 @@ public class PenumbraAttach : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void SubscribeCreatingCharacterBase(IntPtr gameObject, string _, IntPtr modelId, IntPtr customize, IntPtr equipment)
|
||||
=> CreatingCharacterBase?.Invoke(gameObject, modelId, customize, equipment);
|
||||
|
||||
private void SubscribeCreatedCharacterBase(IntPtr gameObject, string _, IntPtr drawObject)
|
||||
=> CreatedCharacterBase?.Invoke(gameObject, drawObject);
|
||||
|
||||
public void Unattach()
|
||||
{
|
||||
_tooltipSubscriber?.Unsubscribe(PenumbraTooltip);
|
||||
_clickSubscriber?.Unsubscribe(PenumbraRightClick);
|
||||
_creatingCharacterBase?.Unsubscribe(SubscribeCreatingCharacterBase);
|
||||
_createdCharacterBase?.Unsubscribe(SubscribeCreatedCharacterBase);
|
||||
_tooltipSubscriber = null;
|
||||
_clickSubscriber = null;
|
||||
_creatingCharacterBase = null;
|
||||
_redrawSubscriberName = null;
|
||||
_drawObjectInfo = null;
|
||||
if (_redrawSubscriberObject != null)
|
||||
_tooltipSubscriber.Disable();
|
||||
_clickSubscriber.Disable();
|
||||
CreatingCharacterBase.Disable();
|
||||
CreatedCharacterBase.Disable();
|
||||
if (Available)
|
||||
{
|
||||
PluginLog.Debug("Glamourer detached from Penumbra.");
|
||||
_redrawSubscriberObject = null;
|
||||
Available = false;
|
||||
Glamourer.Log.Debug("Glamourer detached from Penumbra.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_initializedEvent.Unsubscribe(Reattach);
|
||||
_disposedEvent.Unsubscribe(Unattach);
|
||||
Unattach();
|
||||
_tooltipSubscriber.Dispose();
|
||||
_clickSubscriber.Dispose();
|
||||
CreatingCharacterBase.Dispose();
|
||||
CreatedCharacterBase.Dispose();
|
||||
_initializedEvent.Dispose();
|
||||
_disposedEvent.Dispose();
|
||||
}
|
||||
|
||||
private static void PenumbraTooltip(ChangedItemType type, uint _)
|
||||
|
|
@ -118,7 +103,7 @@ public class PenumbraAttach : IDisposable
|
|||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||
}
|
||||
|
||||
private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
||||
private static void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
||||
{
|
||||
if (button != MouseButton.Right || type != ChangedItemType.Item)
|
||||
return;
|
||||
|
|
@ -166,43 +151,23 @@ public class PenumbraAttach : IDisposable
|
|||
}
|
||||
|
||||
public Actor GameObjectFromDrawObject(IntPtr drawObject)
|
||||
=> _drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero;
|
||||
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : IntPtr.Zero;
|
||||
|
||||
public int CutsceneParent(int idx)
|
||||
=> _cutsceneParent?.InvokeFunc(idx) ?? -1;
|
||||
=> Available ? _cutsceneParent.Invoke(idx) : -1;
|
||||
|
||||
public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat)
|
||||
public void RedrawObject(GameObject? actor, RedrawType settings)
|
||||
{
|
||||
if (actor == null)
|
||||
if (actor == null || !Available)
|
||||
return;
|
||||
|
||||
if (_redrawSubscriberObject != null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_redrawSubscriberObject.InvokeAction(actor, (int)settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (repeat)
|
||||
{
|
||||
Reattach(Glamourer.Config.AttachToPenumbra);
|
||||
RedrawObject(actor, settings, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||
}
|
||||
}
|
||||
_redrawSubscriber.Invoke(actor, settings);
|
||||
}
|
||||
else if (repeat)
|
||||
catch (Exception e)
|
||||
{
|
||||
Reattach(Glamourer.Config.AttachToPenumbra);
|
||||
RedrawObject(actor, settings, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug("Trying to redraw object, but not attached to Penumbra.");
|
||||
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class Design
|
||||
{
|
||||
public string Name { get; }
|
||||
public bool ReadOnly;
|
||||
|
||||
public DateTimeOffset CreationDate { get; }
|
||||
public DateTimeOffset LastUpdateDate { get; }
|
||||
public CharacterSave Data { get; }
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
||||
public struct ArmorData
|
||||
{
|
||||
public CharacterArmor Model;
|
||||
public bool Ignore;
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ using Glamourer.Structs;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Glamourer.Saves;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ public class Glamourer : IDalamudPlugin
|
|||
//public static RevertableDesigns RevertableDesigns = new();
|
||||
//public readonly GlamourerIpc GlamourerIpc;
|
||||
|
||||
public unsafe Glamourer(DalamudPluginInterface pluginInterface)
|
||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dalamud.Initialize(pluginInterface);
|
||||
Log = new Logger();
|
||||
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
RestrictedGear = GameData.RestrictedGear(Dalamud.GameData);
|
||||
Models = GameData.Models(Dalamud.GameData);
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
|
||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -118,7 +119,7 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll $(SolutionDir)$(SolutionName).zip" />
|
||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll, $(TargetDir)Penumbra.Api.dll $(SolutionDir)$(SolutionName).zip" />
|
||||
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -9,10 +9,10 @@ internal partial class CustomizationDrawer
|
|||
{
|
||||
private const string ColorPickerPopupName = "ColorPicker";
|
||||
|
||||
private void DrawColorPicker(CustomizationId id)
|
||||
private void DrawColorPicker(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
var (current, custom) = GetCurrentCustomization(id);
|
||||
using var _ = SetId(index);
|
||||
var (current, custom) = GetCurrentCustomization(index);
|
||||
var color = ImGui.ColorConvertU32ToFloat4(custom.Color);
|
||||
|
||||
// Print 1-based index instead of 0.
|
||||
|
|
@ -40,7 +40,7 @@ internal partial class CustomizationDrawer
|
|||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
var custom = _set.Data(_currentId, i, _customize[CustomizationId.Face]);
|
||||
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]);
|
||||
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||
{
|
||||
UpdateValue(custom.Value);
|
||||
|
|
@ -53,13 +53,13 @@ internal partial class CustomizationDrawer
|
|||
}
|
||||
|
||||
// Obtain the current customization and print a warning if it is not known.
|
||||
private (int, CustomizationData) GetCurrentCustomization(CustomizationId id)
|
||||
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
|
||||
{
|
||||
var current = _set.DataByValue(id, _customize[id], out var custom);
|
||||
if (!_set.IsAvailable(id) || current >= 0)
|
||||
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
||||
if (!_set.IsAvailable(index) || current >= 0)
|
||||
return (current, custom!.Value);
|
||||
|
||||
Glamourer.Log.Warning($"Read invalid customization value {_customize[id]} for {id}.");
|
||||
return (0, _set.Data(id, 0));
|
||||
Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}.");
|
||||
return (0, _set.Data(index, 0));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Glamourer.Util;
|
|||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
|
@ -39,7 +40,7 @@ internal partial class CustomizationDrawer
|
|||
return;
|
||||
|
||||
foreach (var actor in _actors.Where(a => a))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
}
|
||||
|
||||
private void DrawRaceCombo()
|
||||
|
|
@ -56,7 +57,7 @@ internal partial class CustomizationDrawer
|
|||
continue;
|
||||
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Glamourer.Customization;
|
|||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
|
@ -13,18 +14,18 @@ internal partial class CustomizationDrawer
|
|||
{
|
||||
private const string IconSelectorPopup = "Style Picker";
|
||||
|
||||
private void DrawIconSelector(CustomizationId id)
|
||||
private void DrawIconSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
using var _ = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var label = _currentOption;
|
||||
|
||||
var current = _set.DataByValue(id, _currentByte, out var custom);
|
||||
var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face);
|
||||
if (current < 0)
|
||||
{
|
||||
label = $"{_currentOption} (Custom #{_customize[id]})";
|
||||
label = $"{_currentOption} (Custom #{_customize[index]})";
|
||||
current = 0;
|
||||
custom = _set.Data(id, 0);
|
||||
custom = _set.Data(index, 0);
|
||||
}
|
||||
|
||||
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
||||
|
|
@ -35,7 +36,7 @@ internal partial class CustomizationDrawer
|
|||
ImGui.SameLine();
|
||||
using (var group = ImRaii.Group())
|
||||
{
|
||||
if (_currentId == CustomizationId.Face)
|
||||
if (_currentIndex == CustomizeIndex.Face)
|
||||
FaceInputInt(current);
|
||||
else
|
||||
DataInputInt(current);
|
||||
|
|
@ -45,7 +46,7 @@ internal partial class CustomizationDrawer
|
|||
DrawIconPickerPopup();
|
||||
}
|
||||
|
||||
private void UpdateFace(CustomizationData data)
|
||||
private void UpdateFace(CustomizeData data)
|
||||
{
|
||||
// Hrothgar Hack
|
||||
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
||||
|
|
@ -54,7 +55,7 @@ internal partial class CustomizationDrawer
|
|||
|
||||
_customize.Face = value;
|
||||
foreach (var actor in _actors)
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
}
|
||||
|
||||
private void FaceInputInt(int currentIndex)
|
||||
|
|
@ -64,7 +65,7 @@ internal partial class CustomizationDrawer
|
|||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||
{
|
||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||
var data = _set.Data(_currentId, currentIndex, _customize.Face);
|
||||
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||
UpdateFace(data);
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +82,13 @@ internal partial class CustomizationDrawer
|
|||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
var custom = _set.Data(_currentId, i, _customize.Face);
|
||||
var custom = _set.Data(_currentIndex, i, _customize.Face);
|
||||
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
{
|
||||
if (_currentId == CustomizationId.Face)
|
||||
if (_currentIndex == CustomizeIndex.Face)
|
||||
UpdateFace(custom);
|
||||
else
|
||||
UpdateValue(custom.Value);
|
||||
|
|
|
|||
|
|
@ -88,19 +88,8 @@ internal partial class CustomizationDrawer
|
|||
|
||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine);
|
||||
|
||||
d.Checkbox(d._set.Option(CustomizationId.HighlightsOnFlag), customize.HighlightsOn, b => customize.HighlightsOn = b);
|
||||
var xPos = d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SameLine(xPos);
|
||||
d.Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {d._set.Option(CustomizationId.FacePaint)}",
|
||||
customize.FacePaintReversed, b => customize.FacePaintReversed = b);
|
||||
d.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}",
|
||||
customize.SmallIris, b => customize.SmallIris = b);
|
||||
|
||||
if (customize.Race != Race.Hrothgar)
|
||||
{
|
||||
ImGui.SameLine(xPos);
|
||||
d.Checkbox(d._set.Option(CustomizationId.LipColor), customize.Lipstick, b => customize.Lipstick = b);
|
||||
}
|
||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.Checkmark], d.DrawCheckbox,
|
||||
() => ImGui.SameLine(d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X));
|
||||
}
|
||||
|
||||
public static void Draw(Customize customize, IReadOnlyCollection<Actor> actors, bool locked = false)
|
||||
|
|
@ -113,29 +102,29 @@ internal partial class CustomizationDrawer
|
|||
=> Draw(customize, CharacterEquip.Null, Array.Empty<Actor>(), locked);
|
||||
|
||||
// Set state for drawing of current customization.
|
||||
private CustomizationId _currentId;
|
||||
private CustomizationByteValue _currentByte = CustomizationByteValue.Zero;
|
||||
private int _currentCount;
|
||||
private string _currentOption = string.Empty;
|
||||
private CustomizeIndex _currentIndex;
|
||||
private CustomizeValue _currentByte = CustomizeValue.Zero;
|
||||
private int _currentCount;
|
||||
private string _currentOption = string.Empty;
|
||||
|
||||
// Prepare a new customization option.
|
||||
private ImRaii.Id SetId(CustomizationId id)
|
||||
private ImRaii.Id SetId(CustomizeIndex index)
|
||||
{
|
||||
_currentId = id;
|
||||
_currentByte = _customize[id];
|
||||
_currentCount = _set.Count(id, _customize.Face);
|
||||
_currentOption = _set.Option(id);
|
||||
return ImRaii.PushId((int)id);
|
||||
_currentIndex = index;
|
||||
_currentByte = _customize[index];
|
||||
_currentCount = _set.Count(index, _customize.Face);
|
||||
_currentOption = _set.Option(index);
|
||||
return ImRaii.PushId((int)index);
|
||||
}
|
||||
|
||||
// Update the current id with a value,
|
||||
// also update actors if any.
|
||||
private void UpdateValue(CustomizationByteValue value)
|
||||
private void UpdateValue(CustomizeValue value)
|
||||
{
|
||||
if (_customize[_currentId] == value)
|
||||
if (_customize[_currentIndex] == value)
|
||||
return;
|
||||
|
||||
_customize[_currentId] = value;
|
||||
_customize[_currentIndex] = value;
|
||||
UpdateActors();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
@ -12,9 +11,7 @@ internal partial class CustomizationDrawer
|
|||
// Only used for facial features, so fixed ID.
|
||||
private void DrawMultiIconSelector()
|
||||
{
|
||||
using var _ = SetId(CustomizationId.FacialFeaturesTattoos);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
DrawMultiIcons();
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
|
|
@ -23,28 +20,30 @@ internal partial class CustomizationDrawer
|
|||
_currentCount = 256;
|
||||
PercentageInputInt();
|
||||
|
||||
ImGui.TextUnformatted(_set.Option(CustomizationId.FacialFeaturesTattoos));
|
||||
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
|
||||
}
|
||||
|
||||
private void DrawMultiIcons()
|
||||
{
|
||||
using var _ = ImRaii.Group();
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
||||
using var _ = ImRaii.Group();
|
||||
foreach (var (featureIdx, idx) in options.WithIndex())
|
||||
{
|
||||
var enabled = _customize.FacialFeatures[i];
|
||||
var feature = _set.FacialFeature(_customize.Face, i);
|
||||
var icon = i == _currentCount - 1
|
||||
using var id = SetId(featureIdx);
|
||||
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
||||
var feature = _set.Data(featureIdx, 0, _customize.Face);
|
||||
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
||||
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||
: Glamourer.Customization.GetIcon(feature.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||
Vector4.Zero, enabled ? Vector4.One : RedTint))
|
||||
{
|
||||
_customize.FacialFeatures.Set(i, !enabled);
|
||||
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
|
||||
UpdateActors();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
if (i % 4 != 3)
|
||||
if (idx % 4 != 3)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
|
|
@ -10,9 +10,9 @@ namespace Glamourer.Gui.Customization;
|
|||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
private void DrawListSelector(CustomizationId id)
|
||||
private void DrawListSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
using var _ = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
ListCombo();
|
||||
|
|
@ -33,22 +33,22 @@ internal partial class CustomizationDrawer
|
|||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
||||
UpdateValue((CustomizationByteValue)i);
|
||||
UpdateValue((CustomizeValue)i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ListInputInt()
|
||||
{
|
||||
var tmp = _currentByte.Value + 1;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
||||
UpdateValue((CustomizationByteValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
||||
UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
|
||||
private void PercentageSelector(CustomizationId id)
|
||||
private void PercentageSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
using var _ = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
DrawPercentageSlider();
|
||||
|
|
@ -63,7 +63,7 @@ internal partial class CustomizationDrawer
|
|||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
||||
UpdateValue((CustomizationByteValue)tmp);
|
||||
UpdateValue((CustomizeValue)tmp);
|
||||
}
|
||||
|
||||
private void PercentageInputInt()
|
||||
|
|
@ -71,7 +71,7 @@ internal partial class CustomizationDrawer
|
|||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||
UpdateValue((CustomizationByteValue)Math.Clamp(tmp, 0, _currentCount - 1));
|
||||
UpdateValue((CustomizeValue)Math.Clamp(tmp, 0, _currentCount - 1));
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +87,14 @@ internal partial class CustomizationDrawer
|
|||
}
|
||||
}
|
||||
|
||||
// Draw a customize checkbox.
|
||||
private void DrawCheckbox(CustomizeIndex idx)
|
||||
{
|
||||
using var id = SetId(idx);
|
||||
Checkbox(_set.Option(idx), _customize.Get(idx) != CustomizeValue.Zero,
|
||||
b => _customize.Set(idx, b ? CustomizeValue.Max : CustomizeValue.Zero));
|
||||
}
|
||||
|
||||
// Integral input for an icon- or color based item.
|
||||
private void DataInputInt(int currentIndex)
|
||||
{
|
||||
|
|
@ -95,7 +103,7 @@ internal partial class CustomizationDrawer
|
|||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||
{
|
||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||
var data = _set.Data(_currentId, currentIndex, _customize.Face);
|
||||
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||
UpdateValue(data.Value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Lumina.Excel.GeneratedSheets;
|
|||
using Lumina.Text;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -141,6 +142,12 @@ public partial class EquipmentDrawer
|
|||
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
|
||||
}
|
||||
|
||||
//protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||
//{
|
||||
// using var _ = ImRaii.Group();
|
||||
//
|
||||
//}
|
||||
|
||||
protected override bool IsVisible(int globalIndex, LowerString filter)
|
||||
{
|
||||
var item = Items[globalIndex];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
|
|
@ -10,6 +12,8 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using ImGui = ImGuiNET.ImGui;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
|
|
@ -59,6 +63,7 @@ internal partial class Interface
|
|||
if (_currentData.Valid)
|
||||
_currentSave.Update(_currentData.Objects[0]);
|
||||
|
||||
RevertButton();
|
||||
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
|
||||
_identifier is Actor.SpecialIdentifier);
|
||||
|
||||
|
|
@ -68,6 +73,52 @@ internal partial class Interface
|
|||
private const uint RedHeaderColor = 0xFF1818C0;
|
||||
private const uint GreenHeaderColor = 0xFF18C018;
|
||||
|
||||
private void RevertButton()
|
||||
{
|
||||
if (ImGui.Button("Revert"))
|
||||
{
|
||||
_manipulations.DeleteSave(_identifier);
|
||||
|
||||
foreach (var actor in _currentData.Objects)
|
||||
_currentSave!.ApplyToActor(actor);
|
||||
|
||||
if (_currentData.Objects.Count > 0)
|
||||
_currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
|
||||
|
||||
_currentSave!.Reset();
|
||||
}
|
||||
|
||||
VisorBox();
|
||||
}
|
||||
|
||||
private unsafe void VisorBox()
|
||||
{
|
||||
var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch
|
||||
{
|
||||
ApplicationFlags.SetVisor => (0u, 3u),
|
||||
ApplicationFlags.Visor => (1u, 3u),
|
||||
ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u),
|
||||
_ => (2u, 3u),
|
||||
};
|
||||
var tmp = flags;
|
||||
if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask))
|
||||
{
|
||||
_currentSave.Data.Flags = flags switch
|
||||
{
|
||||
0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor,
|
||||
1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
|
||||
2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
|
||||
_ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor),
|
||||
};
|
||||
if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor))
|
||||
{
|
||||
var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor);
|
||||
foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject))
|
||||
RedrawManager.SetVisor(actor.DrawObject.Pointer, on);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPanelHeader()
|
||||
{
|
||||
var color = _currentData.Valid ? GreenHeaderColor : RedHeaderColor;
|
||||
|
|
|
|||
51
Glamourer/Gui/Interface.DebugDataTab.cs
Normal file
51
Glamourer/Gui/Interface.DebugDataTab.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Util;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private class DebugDataTab
|
||||
{
|
||||
private readonly ICustomizationManager _mg;
|
||||
|
||||
public DebugDataTab(ICustomizationManager manager)
|
||||
=> _mg = manager;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Debug");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
foreach (var clan in _mg.Clans)
|
||||
{
|
||||
foreach (var gender in _mg.Genders)
|
||||
DrawCustomizationInfo(_mg.GetList(clan, gender));
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawCustomizationInfo(CustomizationSet set)
|
||||
{
|
||||
if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(set.Clan, set.Gender)} {set.Gender}"))
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("data", 5);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(index.ToString());
|
||||
ImGuiUtil.DrawTableColumn(set.Option(index));
|
||||
ImGuiUtil.DrawTableColumn(set.IsAvailable(index) ? "Available" : "Unavailable");
|
||||
ImGuiUtil.DrawTableColumn(set.Type(index).ToString());
|
||||
ImGuiUtil.DrawTableColumn(set.Count(index).ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ internal partial class Interface : Window, IDisposable
|
|||
|
||||
private readonly ActorTab _actorTab;
|
||||
private readonly DebugStateTab _debugStateTab;
|
||||
private readonly DebugDataTab _debugDataTab;
|
||||
|
||||
public Interface(Glamourer plugin)
|
||||
: base(GetLabel())
|
||||
|
|
@ -29,6 +30,7 @@ internal partial class Interface : Window, IDisposable
|
|||
};
|
||||
_actorTab = new ActorTab(_plugin.CurrentManipulations);
|
||||
_debugStateTab = new DebugStateTab(_plugin.CurrentManipulations);
|
||||
_debugDataTab = new DebugDataTab(Glamourer.Customization);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
|
|
@ -44,6 +46,7 @@ internal partial class Interface : Window, IDisposable
|
|||
_actorTab.Draw();
|
||||
DrawSettingsTab();
|
||||
_debugStateTab.Draw();
|
||||
_debugDataTab.Draw();
|
||||
// DrawSaves();
|
||||
// DrawFixedDesignsTab();
|
||||
// DrawRevertablesTab();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
|
@ -187,13 +188,26 @@ public unsafe partial struct Actor
|
|||
public bool IsValid
|
||||
=> true;
|
||||
|
||||
public OwnedIdentifier(Utf8String name, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
|
||||
public OwnedIdentifier(Utf8String actorName, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
|
||||
{
|
||||
Name = name;
|
||||
OwnerName = ownerName;
|
||||
OwnerHomeWorld = ownerHomeWorld;
|
||||
DataId = dataId;
|
||||
Kind = kind;
|
||||
Name = actorName;
|
||||
switch (Kind)
|
||||
{
|
||||
case ObjectKind.MountType:
|
||||
var mount = Dalamud.GameData.GetExcelSheet<Mount>()!.GetRow(dataId);
|
||||
if (mount != null)
|
||||
Name = Utf8String.FromSpanUnsafe(mount.Singular.RawData, false).AsciiToMixed();
|
||||
break;
|
||||
case ObjectKind.Companion:
|
||||
var companion = Dalamud.GameData.GetExcelSheet<Companion>()!.GetRow(dataId);
|
||||
if (companion != null)
|
||||
Name = Utf8String.FromSpanUnsafe(companion.Singular.RawData, false).AsciiToMixed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(IIdentifier? other)
|
||||
|
|
@ -341,8 +355,19 @@ public unsafe partial struct Actor
|
|||
if (!owner)
|
||||
return new InvalidIdentifier();
|
||||
|
||||
return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld,
|
||||
actor.Pointer->GameObject.DataID, actor.ObjectKind);
|
||||
var dataId = actor.ObjectKind switch
|
||||
{
|
||||
ObjectKind.MountType => owner.UsedMountId,
|
||||
ObjectKind.Companion => actor.CompanionId,
|
||||
_ => actor.Pointer->GameObject.DataID,
|
||||
};
|
||||
|
||||
var name = actor.Utf8Name;
|
||||
if (name.IsEmpty && dataId == 0)
|
||||
return new InvalidIdentifier();
|
||||
|
||||
return new OwnedIdentifier(name, owner.Utf8Name, owner.Pointer->HomeWorld,
|
||||
dataId, actor.ObjectKind);
|
||||
}
|
||||
default: return new InvalidIdentifier();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,115 +1,13 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public interface IDesignable
|
||||
{
|
||||
public bool Valid { get; }
|
||||
public uint ModelId { get; }
|
||||
public Customize Customize { get; }
|
||||
public CharacterEquip Equip { get; }
|
||||
public CharacterWeapon MainHand { get; }
|
||||
public CharacterWeapon OffHand { get; }
|
||||
public bool VisorEnabled { get; }
|
||||
public bool WeaponEnabled { get; }
|
||||
}
|
||||
|
||||
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
|
||||
{
|
||||
public Human* Pointer;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)Pointer;
|
||||
|
||||
public static implicit operator DrawObject(IntPtr? pointer)
|
||||
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
|
||||
|
||||
public static implicit operator IntPtr(DrawObject drawObject)
|
||||
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
|
||||
|
||||
public bool Valid
|
||||
=> Pointer != null;
|
||||
|
||||
public uint ModelId
|
||||
=> 0;
|
||||
|
||||
public uint Type
|
||||
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
||||
|
||||
public Customize Customize
|
||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||
|
||||
public CharacterEquip Equip
|
||||
=> new((CharacterArmor*)Pointer->EquipSlotData);
|
||||
|
||||
public CharacterWeapon MainHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(child + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterWeapon OffHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
var sibling = (byte*) child->NextSiblingObject;
|
||||
if (sibling == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(child + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe bool VisorEnabled
|
||||
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
|
||||
|
||||
public unsafe bool WeaponEnabled
|
||||
=> false;
|
||||
|
||||
public static implicit operator bool(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator true(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator false(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public static bool operator !(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public bool Equals(DrawObject other)
|
||||
=> Pointer == other.Pointer;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is DrawObject other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> unchecked((int)(long)Pointer);
|
||||
|
||||
public static bool operator ==(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer == rhs.Pointer;
|
||||
|
||||
public static bool operator !=(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer != rhs.Pointer;
|
||||
}
|
||||
|
||||
public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
||||
{
|
||||
public static readonly Actor Null = new() { Pointer = null };
|
||||
|
|
@ -173,6 +71,12 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
|||
set => Pointer->ModelCharaId = (int)value;
|
||||
}
|
||||
|
||||
public ushort UsedMountId
|
||||
=> !IsHuman ? (ushort)0 : *(ushort*)((byte*)Pointer + 0x668);
|
||||
|
||||
public ushort CompanionId
|
||||
=> ObjectKind == ObjectKind.Companion ? *(ushort*)((byte*)Pointer + 0x1AAC) : (ushort)0;
|
||||
|
||||
public Customize Customize
|
||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||
|
||||
|
|
|
|||
97
Glamourer/Interop/DrawObject.cs
Normal file
97
Glamourer/Interop/DrawObject.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
|
||||
{
|
||||
public Human* Pointer;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)Pointer;
|
||||
|
||||
public static implicit operator DrawObject(IntPtr? pointer)
|
||||
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
|
||||
|
||||
public static implicit operator IntPtr(DrawObject drawObject)
|
||||
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
|
||||
|
||||
public bool Valid
|
||||
=> Pointer != null;
|
||||
|
||||
public uint ModelId
|
||||
=> 0;
|
||||
|
||||
public uint Type
|
||||
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
||||
|
||||
public Customize Customize
|
||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||
|
||||
public CharacterEquip Equip
|
||||
=> new((CharacterArmor*)Pointer->EquipSlotData);
|
||||
|
||||
public CharacterWeapon MainHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(child + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterWeapon OffHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
var sibling = (byte*)child->NextSiblingObject;
|
||||
if (sibling == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(sibling + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe bool VisorEnabled
|
||||
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
|
||||
|
||||
public unsafe bool WeaponEnabled
|
||||
=> false;
|
||||
|
||||
public static implicit operator bool(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator true(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator false(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public static bool operator !(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public bool Equals(DrawObject other)
|
||||
=> Pointer == other.Pointer;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is DrawObject other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> unchecked((int)(long)Pointer);
|
||||
|
||||
public static bool operator ==(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer == rhs.Pointer;
|
||||
|
||||
public static bool operator !=(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer != rhs.Pointer;
|
||||
}
|
||||
16
Glamourer/Interop/IDesignable.cs
Normal file
16
Glamourer/Interop/IDesignable.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public interface IDesignable
|
||||
{
|
||||
public bool Valid { get; }
|
||||
public uint ModelId { get; }
|
||||
public Customize Customize { get; }
|
||||
public CharacterEquip Equip { get; }
|
||||
public CharacterWeapon MainHand { get; }
|
||||
public CharacterWeapon OffHand { get; }
|
||||
public bool VisorEnabled { get; }
|
||||
public bool WeaponEnabled { get; }
|
||||
}
|
||||
52
Glamourer/Interop/RedrawManager.Customize.cs
Normal file
52
Glamourer/Interop/RedrawManager.Customize.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
// Update
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
||||
{
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
var d = actor.DrawObject;
|
||||
if (NeedsRedraw(d.Customize, customize))
|
||||
{
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
|
||||
}
|
||||
|
||||
public static bool NeedsRedraw(Customize lhs, Customize rhs)
|
||||
=> lhs.Race != rhs.Race
|
||||
|| lhs.Gender != rhs.Gender
|
||||
|| lhs.BodyType != rhs.BodyType
|
||||
|| lhs.Face != rhs.Face
|
||||
|| lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
||||
|
||||
|
||||
public static void SetVisor(Human* data, bool on)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & 0x40) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
||||
}
|
||||
}
|
||||
68
Glamourer/Interop/RedrawManager.Equipment.cs
Normal file
68
Glamourer/Interop/RedrawManager.Equipment.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
try
|
||||
{
|
||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
var identifier = actor.GetIdentifier();
|
||||
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
}
|
||||
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
save2.Data.Equipment[slot] = *data;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
}
|
||||
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
||||
{
|
||||
if (!drawObject)
|
||||
return false;
|
||||
|
||||
if (slotIdx > 9)
|
||||
return false;
|
||||
|
||||
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
||||
}
|
||||
|
||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
||||
}
|
||||
102
Glamourer/Interop/RedrawManager.Weapons.cs
Normal file
102
Glamourer/Interop/RedrawManager.Weapons.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
byte unk4);
|
||||
|
||||
// Weapons for a specific character are reloaded with this function.
|
||||
// The first argument is a pointer to the game object but shifted a bit inside.
|
||||
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
|
||||
// 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.
|
||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
byte unk4)
|
||||
{
|
||||
var oldWeapon = weapon;
|
||||
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||
try
|
||||
{
|
||||
var identifier = character.GetIdentifier();
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
weapon = slot switch
|
||||
{
|
||||
0 => save.MainHand.Value,
|
||||
1 => save.OffHand.Value,
|
||||
_ => weapon,
|
||||
};
|
||||
}
|
||||
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
switch (slot)
|
||||
{
|
||||
case 0:
|
||||
save2.Data.MainHand = new CharacterWeapon(weapon);
|
||||
break;
|
||||
case 1:
|
||||
save2.Data.OffHand = new CharacterWeapon(weapon);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||
}
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||
if (oldWeapon != weapon)
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||
else if (slot != 1 && character.OffHand.Value == 0)
|
||||
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific Main- and Offhand weapons.
|
||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||
{
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
using System;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.State;
|
||||
|
|
@ -12,7 +9,6 @@ using Glamourer.Structs;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -32,157 +28,6 @@ public unsafe partial class RedrawManager
|
|||
public event Action<Actor, Job>? JobChanged;
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
try
|
||||
{
|
||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
var identifier = actor.GetIdentifier();
|
||||
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
}
|
||||
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
save2.Data.Equipment[slot] = *data;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
}
|
||||
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
||||
{
|
||||
if (!drawObject)
|
||||
return false;
|
||||
|
||||
if (slotIdx > 9)
|
||||
return false;
|
||||
|
||||
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
||||
}
|
||||
|
||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public static readonly int CharacterWeaponOffset = (int) Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
byte unk4);
|
||||
|
||||
// Weapons for a specific character are reloaded with this function.
|
||||
// The first argument is a pointer to the game object but shifted a bit inside.
|
||||
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
|
||||
// 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.
|
||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
byte unk4)
|
||||
{
|
||||
var oldWeapon = weapon;
|
||||
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||
try
|
||||
{
|
||||
var identifier = character.GetIdentifier();
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
weapon = slot switch
|
||||
{
|
||||
0 => save.MainHand.Value,
|
||||
1 => save.OffHand.Value,
|
||||
_ => weapon,
|
||||
};
|
||||
}
|
||||
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
switch (slot)
|
||||
{
|
||||
//case 0:
|
||||
// save2.Data.MainHand = new CharacterWeapon(weapon);
|
||||
// break;
|
||||
//case 1:
|
||||
// save.OffHand = new CharacterWeapon(weapon);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||
}
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||
if (oldWeapon != weapon)
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||
else if (slot != 1 && character.OffHand.Value == 0)
|
||||
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific Main- and Offhand weapons.
|
||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||
{
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 0, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager : IDisposable
|
||||
{
|
||||
private readonly FixedDesigns _fixedDesigns;
|
||||
|
|
@ -191,8 +36,8 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
|
||||
Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished;
|
||||
_fixedDesigns = fixedDesigns;
|
||||
_currentManipulations = currentManipulations;
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
|
|
@ -205,8 +50,8 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
_flagSlotForUpdateHook.Dispose();
|
||||
_loadWeaponHook.Dispose();
|
||||
_changeJobHook.Dispose();
|
||||
Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase -= OnCharacterRedrawFinished;
|
||||
Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished;
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||
|
|
@ -225,23 +70,23 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
// Apply customization if they correspond and there is customization to apply.
|
||||
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(save.Data.Customize);
|
||||
customize.Load(save!.Data.Customize);
|
||||
|
||||
// Compare game object equip data against draw object equip data for transformations.
|
||||
// Apply each piece of equip that should be applied if they correspond.
|
||||
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
|
||||
if (gameObjectEquip.Equals(equip))
|
||||
{
|
||||
var saveEquip = save.Data.Equipment;
|
||||
var saveEquip = save!.Data.Equipment;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
(var _, equip[slot]) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(true ? equip[slot] : saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||
(_, equip[slot]) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(IntPtr gameObject, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
private void OnCharacterRedraw(IntPtr gameObject, string collection, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -254,7 +99,7 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private static void OnCharacterRedrawFinished(IntPtr gameObject, IntPtr drawObject)
|
||||
private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject)
|
||||
{
|
||||
//SetVisor((Human*)drawObject, true);
|
||||
if (Glamourer.Models.FromCharacterBase((CharacterBase*)drawObject, out var data))
|
||||
|
|
@ -262,42 +107,4 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
else
|
||||
PluginLog.Information($"Key: {Glamourer.Models.KeyFromCharacterBase((CharacterBase*)drawObject):X16}");
|
||||
}
|
||||
|
||||
// Update
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
||||
{
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
var d = actor.DrawObject;
|
||||
if (NeedsRedraw(d.Customize, customize))
|
||||
{
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
|
||||
}
|
||||
|
||||
public static bool NeedsRedraw(Customize lhs, Customize rhs)
|
||||
=> lhs.Race != rhs.Race || lhs.Gender != rhs.Gender || lhs.Face != rhs.Face || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
||||
|
||||
|
||||
public static void SetVisor(Human* data, bool on)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & 0x40) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
||||
}
|
||||
}
|
||||
}
|
||||
212
Glamourer/Saves/Design.cs
Normal file
212
Glamourer/Saves/Design.cs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Saves;
|
||||
|
||||
public class EquipmentDesign
|
||||
{
|
||||
private Data _data = default;
|
||||
|
||||
// @formatter:off
|
||||
//public Slot Head => new(ref _data, 0);
|
||||
//public Slot Body => new(ref _data, 1);
|
||||
//public Slot Hands => new(ref _data, 2);
|
||||
//public Slot Legs => new(ref _data, 3);
|
||||
//public Slot Feet => new(ref _data, 4);
|
||||
//public Slot Ears => new(ref _data, 5);
|
||||
//public Slot Neck => new(ref _data, 6);
|
||||
//public Slot Wrist => new(ref _data, 7);
|
||||
//public Slot RFinger => new(ref _data, 8);
|
||||
//public Slot LFinger => new(ref _data, 9);
|
||||
//public Slot MainHand => new(ref _data, 10);
|
||||
//public Slot OffHand => new(ref _data, 11);
|
||||
//// @formatter:on
|
||||
//
|
||||
//public Slot this[EquipSlot slot]
|
||||
// => new(ref _data, (int)slot.ToIndex());
|
||||
//
|
||||
//public Slot this[int idx]
|
||||
// => idx is >= 0 and < Data.NumEquipment ? new Slot(ref _data, idx) : throw new IndexOutOfRangeException();
|
||||
|
||||
public unsafe struct Data
|
||||
{
|
||||
public const int NumEquipment = 12;
|
||||
|
||||
public fixed uint Ids[NumEquipment];
|
||||
public fixed byte Stains[NumEquipment];
|
||||
public ushort Flags;
|
||||
public ushort StainFlags;
|
||||
}
|
||||
|
||||
//public ref struct Slot
|
||||
//{
|
||||
// private readonly ref Data _data;
|
||||
// private readonly int _index;
|
||||
// private readonly ushort _flag;
|
||||
//
|
||||
// public Slot(ref Data data, int idx)
|
||||
// {
|
||||
// _data = data;
|
||||
// _index = idx;
|
||||
// _flag = (ushort)(1 << idx);
|
||||
// }
|
||||
//
|
||||
// public unsafe uint ItemId
|
||||
// {
|
||||
// get => _data.Ids[_index];
|
||||
// set => _data.Ids[_index] = value;
|
||||
// }
|
||||
//
|
||||
// public unsafe StainId StainId
|
||||
// {
|
||||
// get => _data.Stains[_index];
|
||||
// set => _data.Stains[_index] = value.Value;
|
||||
// }
|
||||
//
|
||||
// public bool ApplyItem
|
||||
// {
|
||||
// get => (_data.Flags & _flag) != 0;
|
||||
// set => _data.Flags = (ushort)(value ? _data.Flags | _flag : _data.Flags & ~_flag);
|
||||
// }
|
||||
//
|
||||
// public bool ApplyStain
|
||||
// {
|
||||
// get => (_data.StainFlags & _flag) != 0;
|
||||
// set => _data.StainFlags = (ushort)(value ? _data.StainFlags | _flag : _data.StainFlags & ~_flag);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public class HumanDesign
|
||||
{
|
||||
public unsafe struct Data
|
||||
{
|
||||
public CustomizeData Values;
|
||||
public CustomizeFlag Flag;
|
||||
}
|
||||
|
||||
//public ref struct Choice<T> where T : unmanaged
|
||||
//{
|
||||
// private readonly ref Data _data;
|
||||
// private readonly CustomizeFlag _flag;
|
||||
//
|
||||
// public Choice(ref Data data, CustomizeFlag flag)
|
||||
// {
|
||||
// _data = data;
|
||||
// _flag = flag;
|
||||
// }
|
||||
//
|
||||
// public bool ApplyChoice
|
||||
// {
|
||||
// get => _data.Flag.HasFlag(_flag);
|
||||
// set => _data.Flag = value ? _data.Flag | _flag : _data.Flag & ~_flag;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public class Design
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
|
||||
public DateTimeOffset CreationDate { get; }
|
||||
public DateTimeOffset LastUpdateDate { get; private set; }
|
||||
|
||||
public bool ReadOnly { get; private set; }
|
||||
|
||||
public EquipmentDesign? Equipment;
|
||||
public HumanDesign? Customize;
|
||||
|
||||
public string WriteJson()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public struct DesignSaveV1
|
||||
{
|
||||
public string Name;
|
||||
public string Description;
|
||||
|
||||
public ulong CreationDate;
|
||||
public ulong LastUpdateDate;
|
||||
|
||||
public EquipmentPiece Head;
|
||||
public EquipmentPiece Body;
|
||||
public EquipmentPiece Hands;
|
||||
public EquipmentPiece Legs;
|
||||
public EquipmentPiece Feet;
|
||||
public EquipmentPiece Ears;
|
||||
public EquipmentPiece Neck;
|
||||
public EquipmentPiece Wrists;
|
||||
public EquipmentPiece LFinger;
|
||||
public EquipmentPiece RFinger;
|
||||
|
||||
public EquipmentPiece MainHand;
|
||||
public EquipmentPiece OffHand;
|
||||
|
||||
public CustomizationChoice<uint> ModelId;
|
||||
public CustomizationChoice<Race> Race;
|
||||
public CustomizationChoice<Gender> Gender;
|
||||
public CustomizationChoice BodyType;
|
||||
public CustomizationChoice Height;
|
||||
public CustomizationChoice<SubRace> Clan;
|
||||
public CustomizationChoice Face;
|
||||
public CustomizationChoice Hairstyle;
|
||||
public CustomizationChoice<bool> Highlights;
|
||||
public CustomizationChoice SkinColor;
|
||||
public CustomizationChoice EyeColorRight;
|
||||
public CustomizationChoice HairColor;
|
||||
public CustomizationChoice HighlightsColor;
|
||||
public CustomizationChoice<bool> FacialFeature1;
|
||||
public CustomizationChoice<bool> FacialFeature2;
|
||||
public CustomizationChoice<bool> FacialFeature3;
|
||||
public CustomizationChoice<bool> FacialFeature4;
|
||||
public CustomizationChoice<bool> FacialFeature5;
|
||||
public CustomizationChoice<bool> FacialFeature6;
|
||||
public CustomizationChoice<bool> FacialFeature7;
|
||||
public CustomizationChoice<bool> LegacyTattoo;
|
||||
public CustomizationChoice TattooColor;
|
||||
public CustomizationChoice Eyebrows;
|
||||
public CustomizationChoice EyeColorLeft;
|
||||
public CustomizationChoice EyeShape;
|
||||
public CustomizationChoice<bool> SmallIris;
|
||||
public CustomizationChoice Nose;
|
||||
public CustomizationChoice Jaw;
|
||||
public CustomizationChoice Mouth;
|
||||
public CustomizationChoice<bool> Lipstick;
|
||||
public CustomizationChoice MuscleMass;
|
||||
public CustomizationChoice TailShape;
|
||||
public CustomizationChoice BustSize;
|
||||
public CustomizationChoice FacePaint;
|
||||
public CustomizationChoice<bool> FacePaintReversed;
|
||||
public CustomizationChoice FacePaintColor;
|
||||
|
||||
public bool ReadOnly;
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
||||
public struct EquipmentPiece
|
||||
{
|
||||
public uint Item;
|
||||
public bool ApplyItem;
|
||||
public StainId Stain;
|
||||
public bool ApplyStain;
|
||||
}
|
||||
|
||||
public struct CustomizationChoice
|
||||
{
|
||||
public CustomizeValue Value;
|
||||
public bool Apply;
|
||||
}
|
||||
|
||||
public struct CustomizationChoice<T> where T : struct
|
||||
{
|
||||
public T Value;
|
||||
public bool Apply;
|
||||
}
|
||||
75
Glamourer/State/ApplicationFlags.cs
Normal file
75
Glamourer/State/ApplicationFlags.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using System;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
[Flags]
|
||||
public enum ApplicationFlags : uint
|
||||
{
|
||||
Customizations = 0x000001,
|
||||
MainHand = 0x000002,
|
||||
OffHand = 0x000004,
|
||||
Head = 0x000008,
|
||||
Body = 0x000010,
|
||||
Hands = 0x000020,
|
||||
Legs = 0x000040,
|
||||
Feet = 0x000080,
|
||||
Ears = 0x000100,
|
||||
Neck = 0x000200,
|
||||
Wrist = 0x000400,
|
||||
RFinger = 0x000800,
|
||||
LFinger = 0x001000,
|
||||
SetVisor = 0x002000,
|
||||
Visor = 0x004000,
|
||||
SetWeapon = 0x008000,
|
||||
Weapon = 0x010000,
|
||||
SetWet = 0x020000,
|
||||
Wet = 0x040000,
|
||||
}
|
||||
|
||||
public static class ApplicationFlagExtensions
|
||||
{
|
||||
public static ApplicationFlags ToApplicationFlag(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => ApplicationFlags.MainHand,
|
||||
EquipSlot.OffHand => ApplicationFlags.OffHand,
|
||||
EquipSlot.Head => ApplicationFlags.Head,
|
||||
EquipSlot.Body => ApplicationFlags.Body,
|
||||
EquipSlot.Hands => ApplicationFlags.Hands,
|
||||
EquipSlot.Legs => ApplicationFlags.Legs,
|
||||
EquipSlot.Feet => ApplicationFlags.Feet,
|
||||
EquipSlot.Ears => ApplicationFlags.Ears,
|
||||
EquipSlot.Neck => ApplicationFlags.Neck,
|
||||
EquipSlot.Wrists => ApplicationFlags.Wrist,
|
||||
EquipSlot.RFinger => ApplicationFlags.RFinger,
|
||||
EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand,
|
||||
EquipSlot.LFinger => ApplicationFlags.LFinger,
|
||||
EquipSlot.HeadBody => ApplicationFlags.Body,
|
||||
EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body,
|
||||
EquipSlot.LegsFeet => ApplicationFlags.Legs,
|
||||
EquipSlot.FullBody => ApplicationFlags.Body,
|
||||
EquipSlot.BodyHands => ApplicationFlags.Body,
|
||||
EquipSlot.BodyLegsFeet => ApplicationFlags.Body,
|
||||
EquipSlot.ChestHands => ApplicationFlags.Body,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static EquipSlot ToSlot(this ApplicationFlags flags)
|
||||
=> flags switch
|
||||
{
|
||||
ApplicationFlags.MainHand => EquipSlot.MainHand,
|
||||
ApplicationFlags.OffHand => EquipSlot.OffHand,
|
||||
ApplicationFlags.Head => EquipSlot.Head,
|
||||
ApplicationFlags.Body => EquipSlot.Body,
|
||||
ApplicationFlags.Hands => EquipSlot.Hands,
|
||||
ApplicationFlags.Legs => EquipSlot.Legs,
|
||||
ApplicationFlags.Feet => EquipSlot.Feet,
|
||||
ApplicationFlags.Ears => EquipSlot.Ears,
|
||||
ApplicationFlags.Neck => EquipSlot.Neck,
|
||||
ApplicationFlags.Wrist => EquipSlot.Wrists,
|
||||
ApplicationFlags.RFinger => EquipSlot.RFinger,
|
||||
ApplicationFlags.LFinger => EquipSlot.LFinger,
|
||||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,17 +1,12 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using DrawObject = Glamourer.Interop.DrawObject;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
using Functions = Penumbra.GameData.Util.Functions;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
|
@ -33,76 +28,7 @@ public class CharacterSaveConverter : JsonConverter<CharacterSave>
|
|||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ApplicationFlags : uint
|
||||
{
|
||||
Customizations = 0x000001,
|
||||
MainHand = 0x000002,
|
||||
OffHand = 0x000004,
|
||||
Head = 0x000008,
|
||||
Body = 0x000010,
|
||||
Hands = 0x000020,
|
||||
Legs = 0x000040,
|
||||
Feet = 0x000080,
|
||||
Ears = 0x000100,
|
||||
Neck = 0x000200,
|
||||
Wrist = 0x000400,
|
||||
RFinger = 0x000800,
|
||||
LFinger = 0x001000,
|
||||
SetVisor = 0x002000,
|
||||
Visor = 0x004000,
|
||||
SetWeapon = 0x008000,
|
||||
Weapon = 0x010000,
|
||||
SetWet = 0x020000,
|
||||
Wet = 0x040000,
|
||||
}
|
||||
|
||||
public static class ApplicationFlagExtensions
|
||||
{
|
||||
public static ApplicationFlags ToApplicationFlag(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => ApplicationFlags.MainHand,
|
||||
EquipSlot.OffHand => ApplicationFlags.OffHand,
|
||||
EquipSlot.Head => ApplicationFlags.Head,
|
||||
EquipSlot.Body => ApplicationFlags.Body,
|
||||
EquipSlot.Hands => ApplicationFlags.Hands,
|
||||
EquipSlot.Legs => ApplicationFlags.Legs,
|
||||
EquipSlot.Feet => ApplicationFlags.Feet,
|
||||
EquipSlot.Ears => ApplicationFlags.Ears,
|
||||
EquipSlot.Neck => ApplicationFlags.Neck,
|
||||
EquipSlot.Wrists => ApplicationFlags.Wrist,
|
||||
EquipSlot.RFinger => ApplicationFlags.RFinger,
|
||||
EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand,
|
||||
EquipSlot.LFinger => ApplicationFlags.LFinger,
|
||||
EquipSlot.HeadBody => ApplicationFlags.Body,
|
||||
EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body,
|
||||
EquipSlot.LegsFeet => ApplicationFlags.Legs,
|
||||
EquipSlot.FullBody => ApplicationFlags.Body,
|
||||
EquipSlot.BodyHands => ApplicationFlags.Body,
|
||||
EquipSlot.BodyLegsFeet => ApplicationFlags.Body,
|
||||
EquipSlot.ChestHands => ApplicationFlags.Body,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static EquipSlot ToSlot(this ApplicationFlags flags)
|
||||
=> flags switch
|
||||
{
|
||||
ApplicationFlags.MainHand => EquipSlot.MainHand,
|
||||
ApplicationFlags.OffHand => EquipSlot.OffHand,
|
||||
ApplicationFlags.Head => EquipSlot.Head,
|
||||
ApplicationFlags.Body => EquipSlot.Body,
|
||||
ApplicationFlags.Hands => EquipSlot.Hands,
|
||||
ApplicationFlags.Legs => EquipSlot.Legs,
|
||||
ApplicationFlags.Feet => EquipSlot.Feet,
|
||||
ApplicationFlags.Ears => EquipSlot.Ears,
|
||||
ApplicationFlags.Neck => EquipSlot.Neck,
|
||||
ApplicationFlags.Wrist => EquipSlot.Wrists,
|
||||
ApplicationFlags.RFinger => EquipSlot.RFinger,
|
||||
ApplicationFlags.LFinger => EquipSlot.LFinger,
|
||||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterData
|
||||
|
|
@ -206,19 +132,6 @@ public struct CharacterData
|
|||
}
|
||||
}
|
||||
|
||||
public interface ICharacterData
|
||||
{
|
||||
public ref CharacterData Data { get; }
|
||||
|
||||
//public bool ApplyModel();
|
||||
//public bool ApplyCustomize(Customize target);
|
||||
//public bool ApplyWeapon(ref CharacterWeapon weapon, bool mainHand, bool offHand);
|
||||
//public bool ApplyGear(ref CharacterArmor armor, EquipSlot slot);
|
||||
//public unsafe bool ApplyWetness(CharacterBase* drawObject);
|
||||
//public unsafe bool ApplyVisorState(CharacterBase* drawObject);
|
||||
//public unsafe bool ApplyWeaponState(CharacterBase* drawObject);
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(CharacterSaveConverter))]
|
||||
public class CharacterSave
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public unsafe class CurrentDesign : ICharacterData
|
||||
public unsafe class CurrentDesign : IDesign
|
||||
{
|
||||
public ref CharacterData Data
|
||||
=> ref _drawData;
|
||||
|
|
@ -28,6 +26,39 @@ public unsafe class CurrentDesign : ICharacterData
|
|||
_drawData = _initialData.Clone();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
=> _drawData = _initialData;
|
||||
|
||||
public void ApplyToActor(Actor actor)
|
||||
{
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
void Redraw()
|
||||
=> Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
|
||||
if (_drawData.ModelId != actor.ModelId)
|
||||
{
|
||||
Redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
var customize1 = _drawData.Customize;
|
||||
var customize2 = actor.Customize;
|
||||
if (RedrawManager.NeedsRedraw(customize1, customize2))
|
||||
{
|
||||
Redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor, customize2);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
Glamourer.RedrawManager.ChangeEquip(actor, slot, actor.Equip[slot]);
|
||||
Glamourer.RedrawManager.LoadWeapon(actor, actor.MainHand, actor.OffHand);
|
||||
if (actor.IsHuman && actor.DrawObject)
|
||||
RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled);
|
||||
}
|
||||
|
||||
public void Update(Actor actor)
|
||||
{
|
||||
if (!actor)
|
||||
|
|
|
|||
10
Glamourer/State/IDesign.cs
Normal file
10
Glamourer/State/IDesign.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using Glamourer.Interop;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public interface IDesign
|
||||
{
|
||||
public ref CharacterData Data { get; }
|
||||
|
||||
public void ApplyToActor(Actor a);
|
||||
}
|
||||
|
|
@ -90,18 +90,18 @@ public static unsafe class CustomizeExtensions
|
|||
customize.Load(newCustomize);
|
||||
}
|
||||
|
||||
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizationId id, CustomizationByteValue value)
|
||||
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizeIndex index, CustomizeValue value)
|
||||
{
|
||||
switch (id)
|
||||
switch (index)
|
||||
{
|
||||
case CustomizationId.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
|
||||
case CustomizationId.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
|
||||
case CustomizeIndex.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
|
||||
case CustomizeIndex.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
|
||||
}
|
||||
|
||||
if (customize[id] == value)
|
||||
if (customize[index] == value)
|
||||
return false;
|
||||
|
||||
customize[id] = value;
|
||||
customize[index] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -109,21 +109,20 @@ public static unsafe class CustomizeExtensions
|
|||
private static void FixUpAttributes(Customize customize)
|
||||
{
|
||||
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
|
||||
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race: break;
|
||||
case CustomizationId.Clan: break;
|
||||
case CustomizationId.BodyType: break;
|
||||
case CustomizationId.Gender: break;
|
||||
case CustomizationId.FacialFeaturesTattoos: break;
|
||||
case CustomizationId.HighlightsOnFlag: break;
|
||||
case CustomizationId.Face: break;
|
||||
case CustomizeIndex.Race: break;
|
||||
case CustomizeIndex.Clan: break;
|
||||
case CustomizeIndex.BodyType: break;
|
||||
case CustomizeIndex.Gender: break;
|
||||
case CustomizeIndex.Highlights: break;
|
||||
case CustomizeIndex.Face: break;
|
||||
default:
|
||||
var count = set.Count(id);
|
||||
if (set.DataByValue(id, customize[id], out _) < 0)
|
||||
customize[id] = count == 0 ? CustomizationByteValue.Zero : set.Data(id, 0).Value;
|
||||
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
|
||||
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue