Rework API/IPC and use new Penumbra IPC.

This commit is contained in:
Ottermandias 2024-04-14 15:07:31 +02:00
parent 9f276c7674
commit 0268546f63
46 changed files with 2270 additions and 1233 deletions

View file

@ -0,0 +1,8 @@
namespace Glamourer.Api.Api;
public interface IGlamourerApi : IGlamourerApiBase
{
public IGlamourerApiDesigns Designs { get; }
public IGlamourerApiItems Items { get; }
public IGlamourerApiState State { get; }
}

View file

@ -0,0 +1,6 @@
namespace Glamourer.Api.Api;
public interface IGlamourerApiBase
{
public (int Major, int Minor) ApiVersion { get; }
}

View file

@ -0,0 +1,12 @@
using Glamourer.Api.Enums;
namespace Glamourer.Api.Api;
public interface IGlamourerApiDesigns
{
public Dictionary<Guid, string> GetDesignList();
public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags);
public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags);
}

View file

@ -0,0 +1,9 @@
using Glamourer.Api.Enums;
namespace Glamourer.Api.Api;
public interface IGlamourerApiItems
{
public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot apiSlot, ulong itemId, byte stain, uint key, ApplyFlag flags);
public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags);
}

View file

@ -0,0 +1,28 @@
using Glamourer.Api.Enums;
using Newtonsoft.Json.Linq;
namespace Glamourer.Api.Api;
public interface IGlamourerApiState
{
public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key);
public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key);
public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags);
public GlamourerApiEc ApplyStateName(object state, string objectName, uint key, ApplyFlag flags);
public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags);
public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags);
public GlamourerApiEc UnlockState(int objectIndex, uint key);
public GlamourerApiEc UnlockStateName(string objectName, uint key);
public int UnlockAll(uint key);
public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags);
public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags);
public event Action<nint>? StateChanged;
public event Action<bool>? GPoseChanged;
}

124
Glamourer/Api/ApiHelpers.cs Normal file
View file

@ -0,0 +1,124 @@
using Glamourer.Api.Enums;
using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.State;
using OtterGui;
using OtterGui.Log;
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.String;
using ObjectManager = Glamourer.Interop.ObjectManager;
namespace Glamourer.Api;
public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal IEnumerable<ActorState> FindExistingStates(string actorName)
{
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
yield break;
foreach (var state in stateManager.Values.Where(state
=> state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString))
yield return state;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal GlamourerApiEc FindExistingState(int objectIndex, out ActorState? state)
{
var actor = objects[objectIndex];
var identifier = actor.GetIdentifier(actors);
if (!identifier.IsValid)
{
state = null;
return GlamourerApiEc.ActorNotFound;
}
stateManager.TryGetValue(identifier, out state);
return GlamourerApiEc.Success;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal ActorState? FindState(int objectIndex)
{
var actor = objects[objectIndex];
var identifier = actor.GetIdentifier(actors);
if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state))
return state;
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags)
=> (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch
{
ApplyFlag.Equipment => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, 0, CrestExtensions.All, 0),
ApplyFlag.Customization => design.TemporarilyRestrictApplication(0, CustomizeFlagExtensions.All, 0,
CustomizeParameterExtensions.All),
ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(EquipFlagExtensions.All,
CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All),
_ => design.TemporarilyRestrictApplication(0, 0, 0, 0),
};
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static void Lock(ActorState state, uint key, ApplyFlag flags)
{
if ((flags & ApplyFlag.Lock) != 0 && key != 0)
state.Lock(key);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal IEnumerable<ActorState> FindStates(string objectName)
{
if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString))
return [];
objects.Update();
return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)
.Concat(objects.Identifiers
.Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString)
.SelectWhere(kvp =>
{
if (stateManager.ContainsKey(kvp.Key))
return (false, null);
var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state);
return (ret, state);
}));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static GlamourerApiEc Return(GlamourerApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown")
{
if (ec is GlamourerApiEc.Success or GlamourerApiEc.NothingDone)
Glamourer.Log.Verbose($"[{name}] Called with {args}, returned {ec}.");
else
Glamourer.Log.Debug($"[{name}] Called with {args}, returned {ec}.");
return ec;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static LazyString Args(params object[] arguments)
{
if (arguments.Length == 0)
return new LazyString(() => "no arguments");
return new LazyString(() =>
{
var sb = new StringBuilder();
for (var i = 0; i < arguments.Length / 2; ++i)
{
sb.Append(arguments[2 * i]);
sb.Append(" = ");
sb.Append(arguments[2 * i + 1]);
sb.Append(", ");
}
return sb.ToString(0, sb.Length - 2);
});
}
}

View file

@ -0,0 +1,69 @@
using Glamourer.Api.Api;
using Glamourer.Api.Enums;
using Glamourer.Designs;
using Glamourer.State;
using OtterGui.Services;
namespace Glamourer.Api;
public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager) : IGlamourerApiDesigns, IApiService
{
public Dictionary<Guid, string> GetDesignList()
=> designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text);
public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags);
var design = designs.Designs.ByIdentifier(designId);
if (design == null)
return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args);
if (helpers.FindState(objectIndex) is not { } state)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (!state.CanUnlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
ApplyDesign(state, design, key, flags);
ApiHelpers.Lock(state, key, flags);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
private void ApplyDesign(ActorState state, Design design, uint key, ApplyFlag flags)
{
var once = (flags & ApplyFlag.Once) != 0;
var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true,
ResetMaterials: !once && key != 0);
using var restrict = ApiHelpers.Restrict(design, flags);
stateManager.ApplyDesign(state, design, settings);
}
public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Design", designId, "Name", objectName, "Key", key, "Flags", flags);
var design = designs.Designs.ByIdentifier(designId);
if (design == null)
return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args);
var any = false;
var anyUnlocked = false;
foreach (var state in helpers.FindStates(objectName))
{
any = true;
if (!state.CanUnlock(key))
continue;
anyUnlocked = true;
ApplyDesign(state, design, key, flags);
ApiHelpers.Lock(state, key, flags);
}
if (!any)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (!anyUnlocked)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
}

View file

@ -0,0 +1,34 @@
namespace Glamourer.Api.Enums;
[Flags]
public enum ApplyFlag : ulong
{
Once = 0x01,
Equipment = 0x02,
Customization = 0x04,
Lock = 0x08,
}
public static class ApplyFlagEx
{
public const ApplyFlag DesignDefault = ApplyFlag.Once | ApplyFlag.Equipment | ApplyFlag.Customization;
public const ApplyFlag StateDefault = ApplyFlag.Equipment | ApplyFlag.Customization | ApplyFlag.Lock;
public const ApplyFlag RevertDefault = ApplyFlag.Equipment | ApplyFlag.Customization;
}
public enum ApiEquipSlot : byte
{
Unknown = 0,
MainHand = 1,
OffHand = 2,
Head = 3,
Body = 4,
Hands = 5,
Legs = 7,
Feet = 8,
Ears = 9,
Neck = 10,
Wrists = 11,
RFinger = 12,
LFinger = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game.
}

View file

@ -0,0 +1,13 @@
namespace Glamourer.Api.Enums;
public enum GlamourerApiEc
{
Success,
ActorNotFound,
ActorNotHuman,
DesignNotFound,
ItemInvalid,
InvalidKey,
InvalidState,
NothingDone,
}

View file

@ -0,0 +1,22 @@
using Glamourer.Api.Api;
using OtterGui.Services;
namespace Glamourer.Api;
public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
{
public const int CurrentApiVersionMajor = 1;
public const int CurrentApiVersionMinor = 0;
public (int Major, int Minor) ApiVersion
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
public IGlamourerApiDesigns Designs
=> designs;
public IGlamourerApiItems Items
=> items;
public IGlamourerApiState State
=> state;
}

View file

@ -1,25 +0,0 @@
using Dalamud.Plugin;
using Penumbra.Api.Helpers;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public const string LabelApiVersion = "Glamourer.ApiVersion";
public const string LabelApiVersions = "Glamourer.ApiVersions";
private readonly FuncProvider<int> _apiVersionProvider;
private readonly FuncProvider<(int Major, int Minor)> _apiVersionsProvider;
public static FuncSubscriber<int> ApiVersionSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApiVersion);
public static FuncSubscriber<(int Major, int Minor)> ApiVersionsSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApiVersions);
public int ApiVersion()
=> CurrentApiVersionMajor;
public (int Major, int Minor) ApiVersions()
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
}

View file

@ -1,180 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public const string LabelApplyAll = "Glamourer.ApplyAll";
public const string LabelApplyAllOnce = "Glamourer.ApplyAllOnce";
public const string LabelApplyAllToCharacter = "Glamourer.ApplyAllToCharacter";
public const string LabelApplyAllOnceToCharacter = "Glamourer.ApplyAllOnceToCharacter";
public const string LabelApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment";
public const string LabelApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter";
public const string LabelApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization";
public const string LabelApplyOnlyCustomizationToCharacter = "Glamourer.ApplyOnlyCustomizationToCharacter";
public const string LabelApplyAllLock = "Glamourer.ApplyAllLock";
public const string LabelApplyAllToCharacterLock = "Glamourer.ApplyAllToCharacterLock";
public const string LabelApplyOnlyEquipmentLock = "Glamourer.ApplyOnlyEquipmentLock";
public const string LabelApplyOnlyEquipmentToCharacterLock = "Glamourer.ApplyOnlyEquipmentToCharacterLock";
public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock";
public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock";
public const string LabelApplyByGuid = "Glamourer.ApplyByGuid";
public const string LabelApplyByGuidOnce = "Glamourer.ApplyByGuidOnce";
public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter";
public const string LabelApplyByGuidOnceToCharacter = "Glamourer.ApplyByGuidOnceToCharacter";
private readonly ActionProvider<string, string> _applyAllProvider;
private readonly ActionProvider<string, string> _applyAllOnceProvider;
private readonly ActionProvider<string, Character?> _applyAllToCharacterProvider;
private readonly ActionProvider<string, Character?> _applyAllOnceToCharacterProvider;
private readonly ActionProvider<string, string> _applyOnlyEquipmentProvider;
private readonly ActionProvider<string, Character?> _applyOnlyEquipmentToCharacterProvider;
private readonly ActionProvider<string, string> _applyOnlyCustomizationProvider;
private readonly ActionProvider<string, Character?> _applyOnlyCustomizationToCharacterProvider;
private readonly ActionProvider<string, string, uint> _applyAllProviderLock;
private readonly ActionProvider<string, Character?, uint> _applyAllToCharacterProviderLock;
private readonly ActionProvider<string, string, uint> _applyOnlyEquipmentProviderLock;
private readonly ActionProvider<string, Character?, uint> _applyOnlyEquipmentToCharacterProviderLock;
private readonly ActionProvider<string, string, uint> _applyOnlyCustomizationProviderLock;
private readonly ActionProvider<string, Character?, uint> _applyOnlyCustomizationToCharacterProviderLock;
private readonly ActionProvider<Guid, string> _applyByGuidProvider;
private readonly ActionProvider<Guid, string> _applyByGuidOnceProvider;
private readonly ActionProvider<Guid, Character?> _applyByGuidToCharacterProvider;
private readonly ActionProvider<Guid, Character?> _applyByGuidOnceToCharacterProvider;
public static ActionSubscriber<string, string> ApplyAllSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyAll);
public static ActionSubscriber<string, string> ApplyAllOnceSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyAllOnce);
public static ActionSubscriber<string, Character?> ApplyAllToCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyAllToCharacter);
public static ActionSubscriber<string, Character?> ApplyAllOnceToCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyAllOnceToCharacter);
public static ActionSubscriber<string, string> ApplyOnlyEquipmentSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyOnlyEquipment);
public static ActionSubscriber<string, Character?> ApplyOnlyEquipmentToCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyOnlyEquipmentToCharacter);
public static ActionSubscriber<string, string> ApplyOnlyCustomizationSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyOnlyCustomization);
public static ActionSubscriber<string, Character?> ApplyOnlyCustomizationToCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyOnlyCustomizationToCharacter);
public static ActionSubscriber<Guid, string> ApplyByGuidSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyByGuid);
public static ActionSubscriber<Guid, string> ApplyByGuidOnceSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyByGuidOnce);
public static ActionSubscriber<Guid, Character?> ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyByGuidToCharacter);
public static ActionSubscriber<Guid, Character?> ApplyByGuidOnceToCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyByGuidOnceToCharacter);
public static ActionSubscriber<string, string, uint> ApplyAllLockSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyAllLock);
public static ActionSubscriber<string, Character?, uint> ApplyAllToCharacterLockSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelApplyAllToCharacterLock);
public void ApplyAll(string base64, string characterName)
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0);
public void ApplyAllOnce(string base64, string characterName)
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0, true);
public void ApplyAllToCharacter(string base64, Character? character)
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0);
public void ApplyAllOnceToCharacter(string base64, Character? character)
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0, true);
public void ApplyOnlyEquipment(string base64, string characterName)
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, 0);
public void ApplyOnlyEquipmentToCharacter(string base64, Character? character)
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, 0);
public void ApplyOnlyCustomization(string base64, string characterName)
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, 0);
public void ApplyOnlyCustomizationToCharacter(string base64, Character? character)
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, 0);
public void ApplyAllLock(string base64, string characterName, uint lockCode)
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, lockCode);
public void ApplyAllToCharacterLock(string base64, Character? character, uint lockCode)
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, lockCode);
public void ApplyOnlyEquipmentLock(string base64, string characterName, uint lockCode)
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, lockCode);
public void ApplyOnlyEquipmentToCharacterLock(string base64, Character? character, uint lockCode)
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, lockCode);
public void ApplyOnlyCustomizationLock(string base64, string characterName, uint lockCode)
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, lockCode);
public void ApplyOnlyCustomizationToCharacterLock(string base64, Character? character, uint lockCode)
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, lockCode);
public void ApplyByGuid(Guid identifier, string characterName)
=> ApplyDesignByGuid(identifier, FindActors(characterName), 0, false);
public void ApplyByGuidOnce(Guid identifier, string characterName)
=> ApplyDesignByGuid(identifier, FindActors(characterName), 0, true);
public void ApplyByGuidToCharacter(Guid identifier, Character? character)
=> ApplyDesignByGuid(identifier, FindActors(character), 0, false);
public void ApplyByGuidOnceToCharacter(Guid identifier, Character? character)
=> ApplyDesignByGuid(identifier, FindActors(character), 0, true);
private void ApplyDesign(DesignBase? design, IEnumerable<ActorIdentifier> actors, byte version, uint lockCode, bool once = false)
{
if (design == null)
return;
var hasModelId = version >= 3;
_objects.Update();
foreach (var id in actors)
{
if (!_stateManager.TryGetValue(id, out var state))
{
var data = _objects.TryGetValue(id, out var d) ? d : ActorData.Invalid;
if (!data.Valid || !_stateManager.GetOrCreate(id, data.Objects[0], out state))
continue;
}
if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode))
{
_stateManager.ApplyDesign(state, design,
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode, MergeLinks: true, ResetMaterials: !once && lockCode != 0));
state.Lock(lockCode);
}
}
}
private void ApplyDesignByGuid(Guid identifier, IEnumerable<ActorIdentifier> actors, uint lockCode, bool once)
=> ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode, once);
}

View file

@ -1,17 +0,0 @@
using Dalamud.Plugin;
using Penumbra.Api.Helpers;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public const string LabelGetDesignList = "Glamourer.GetDesignList";
private readonly FuncProvider<(string Name, Guid Identifier)[]> _getDesignListProvider;
public static FuncSubscriber<(string Name, Guid Identifier)[]> GetDesignListSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelGetDesignList);
public (string Name, Guid Identifier)[] GetDesignList()
=> _designManager.Designs.Select(x => (x.Name.Text, x.Identifier)).ToArray();
}

View file

@ -1,27 +0,0 @@
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Penumbra.Api.Helpers;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public const string LabelStateChanged = "Glamourer.StateChanged";
public const string LabelGPoseChanged = "Glamourer.GPoseChanged";
private readonly GPoseService _gPose;
private readonly StateChanged _stateChangedEvent;
private readonly EventProvider<StateChanged.Type, nint, Lazy<string>> _stateChangedProvider;
private readonly EventProvider<bool> _gPoseChangedProvider;
private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null)
{
foreach (var actor in actors.Objects)
_stateChangedProvider.Invoke(type, actor.Address, new Lazy<string>(() => _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state))));
}
private void OnGPoseChanged(bool value)
=> _gPoseChangedProvider.Invoke(value);
}

View file

@ -1,64 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Designs;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public const string LabelGetAllCustomization = "Glamourer.GetAllCustomization";
public const string LabelGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter";
public const string LabelGetAllCustomizationLocked = "Glamourer.GetAllCustomizationLocked";
public const string LabelGetAllCustomizationFromLockedCharacter = "Glamourer.GetAllCustomizationFromLockedCharacter";
private readonly FuncProvider<string, string?> _getAllCustomizationProvider;
private readonly FuncProvider<string, uint, string?> _getAllCustomizationLockedProvider;
private readonly FuncProvider<Character?, string?> _getAllCustomizationFromCharacterProvider;
private readonly FuncProvider<Character?, uint, string?> _getAllCustomizationFromLockedCharacterProvider;
public static FuncSubscriber<string, string?> GetAllCustomizationSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelGetAllCustomization);
public static FuncSubscriber<Character?, string?> GetAllCustomizationFromCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelGetAllCustomizationFromCharacter);
public static FuncSubscriber<string, uint, string?> GetAllCustomizationLockedSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelGetAllCustomizationLocked);
public static FuncSubscriber<Character?, uint, string?> GetAllCustomizationFromLockedCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelGetAllCustomizationFromLockedCharacter);
public string? GetAllCustomization(string characterName)
=> GetCustomization(FindActors(characterName), 0);
public string? GetAllCustomization(string characterName, uint lockCode)
=> GetCustomization(FindActors(characterName), lockCode);
public string? GetAllCustomizationFromCharacter(Character? character)
=> GetCustomization(FindActors(character), 0);
public string? GetAllCustomizationFromCharacter(Character? character, uint lockCode)
=> GetCustomization(FindActors(character), lockCode);
private string? GetCustomization(IEnumerable<ActorIdentifier> actors, uint lockCode)
{
var actor = actors.FirstOrDefault(ActorIdentifier.Invalid);
if (!actor.IsValid)
return null;
if (!_stateManager.TryGetValue(actor, out var state))
{
_objects.Update();
if (!_objects.TryGetValue(actor, out var data) || !data.Valid)
return null;
if (!_stateManager.GetOrCreate(actor, data.Objects[0], out state))
return null;
}
if (!state.CanUnlock(lockCode))
return null;
return _designConverter.ShareBase64(state, ApplicationRules.AllWithConfig(_config));
}
}

View file

@ -1,135 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public const string LabelRevert = "Glamourer.Revert";
public const string LabelRevertCharacter = "Glamourer.RevertCharacter";
public const string LabelRevertLock = "Glamourer.RevertLock";
public const string LabelRevertCharacterLock = "Glamourer.RevertCharacterLock";
public const string LabelRevertToAutomation = "Glamourer.RevertToAutomation";
public const string LabelRevertToAutomationCharacter = "Glamourer.RevertToAutomationCharacter";
public const string LabelUnlock = "Glamourer.Unlock";
public const string LabelUnlockName = "Glamourer.UnlockName";
public const string LabelUnlockAll = "Glamourer.UnlockAll";
private readonly ActionProvider<string> _revertProvider;
private readonly ActionProvider<Character?> _revertCharacterProvider;
private readonly ActionProvider<string, uint> _revertProviderLock;
private readonly ActionProvider<Character?, uint> _revertCharacterProviderLock;
private readonly FuncProvider<string, uint, bool> _revertToAutomationProvider;
private readonly FuncProvider<Character?, uint, bool> _revertToAutomationCharacterProvider;
private readonly FuncProvider<string, uint, bool> _unlockNameProvider;
private readonly FuncProvider<Character?, uint, bool> _unlockProvider;
private readonly FuncProvider<uint, int> _unlockAllProvider;
public static ActionSubscriber<string> RevertSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelRevert);
public static ActionSubscriber<Character?> RevertCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelRevertCharacter);
public static ActionSubscriber<string> RevertLockSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelRevertLock);
public static ActionSubscriber<Character?> RevertCharacterLockSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelRevertCharacterLock);
public static FuncSubscriber<string, uint, bool> UnlockNameSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelUnlockName);
public static FuncSubscriber<Character?, uint, bool> UnlockSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelUnlock);
public static FuncSubscriber<uint, int> UnlockAllSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelUnlockAll);
public static FuncSubscriber<string, uint, bool> RevertToAutomationSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelRevertToAutomation);
public static FuncSubscriber<Character?, uint, bool> RevertToAutomationCharacterSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelRevertToAutomationCharacter);
public void Revert(string characterName)
=> Revert(FindActorsRevert(characterName), 0);
public void RevertCharacter(Character? character)
=> Revert(FindActors(character), 0);
public void RevertLock(string characterName, uint lockCode)
=> Revert(FindActorsRevert(characterName), lockCode);
public void RevertCharacterLock(Character? character, uint lockCode)
=> Revert(FindActors(character), lockCode);
public bool Unlock(string characterName, uint lockCode)
=> Unlock(FindActorsRevert(characterName), lockCode);
public bool Unlock(Character? character, uint lockCode)
=> Unlock(FindActors(character), lockCode);
public int UnlockAll(uint lockCode)
{
var count = 0;
foreach (var state in _stateManager.Values)
if (state.Unlock(lockCode))
++count;
return count;
}
public bool RevertToAutomation(string characterName, uint lockCode)
=> RevertToAutomation(FindActorsRevert(characterName), lockCode);
public bool RevertToAutomation(Character? character, uint lockCode)
=> RevertToAutomation(FindActors(character), lockCode);
private void Revert(IEnumerable<ActorIdentifier> actors, uint lockCode)
{
foreach (var id in actors)
{
if (_stateManager.TryGetValue(id, out var state))
_stateManager.ResetState(state, StateSource.IpcFixed, lockCode);
}
}
private bool Unlock(IEnumerable<ActorIdentifier> actors, uint lockCode)
{
var ret = false;
foreach (var id in actors)
{
if (_stateManager.TryGetValue(id, out var state))
ret |= state.Unlock(lockCode);
}
return ret;
}
private bool RevertToAutomation(IEnumerable<ActorIdentifier> actors, uint lockCode)
{
var ret = false;
foreach (var id in actors)
{
if (_stateManager.TryGetValue(id, out var state))
{
ret |= state.Unlock(lockCode);
if (_objects.TryGetValue(id, out var data))
foreach (var obj in data.Objects)
{
_autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state, true);
_stateManager.ReapplyState(obj, StateSource.IpcManual);
}
}
}
return ret;
}
}

View file

@ -1,105 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public enum GlamourerErrorCode
{
Success,
ActorNotFound,
ActorNotHuman,
ItemInvalid,
}
public const string LabelSetItem = "Glamourer.SetItem";
public const string LabelSetItemOnce = "Glamourer.SetItemOnce";
public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName";
public const string LabelSetItemOnceByActorName = "Glamourer.SetItemOnceByActorName";
private readonly FuncProvider<Character?, byte, ulong, byte, uint, int> _setItemProvider;
private readonly FuncProvider<Character?, byte, ulong, byte, uint, int> _setItemOnceProvider;
private readonly FuncProvider<string, byte, ulong, byte, uint, int> _setItemByActorNameProvider;
private readonly FuncProvider<string, byte, ulong, byte, uint, int> _setItemOnceByActorNameProvider;
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelSetItem);
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemOnceSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelSetItemOnce);
public static FuncSubscriber<string, byte, ulong, byte, uint, int> SetItemByActorNameSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelSetItemByActorName);
public static FuncSubscriber<string, byte, ulong, byte, uint, int> SetItemOnceByActorNameSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelSetItemOnceByActorName);
private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once)
{
if (itemId.Id == 0)
itemId = ItemManager.NothingId(slot);
var item = _items.Resolve(slot, itemId);
if (!item.Valid)
return GlamourerErrorCode.ItemInvalid;
var identifier = _actors.FromObject(character, false, false, false);
if (!identifier.IsValid)
return GlamourerErrorCode.ActorNotFound;
if (!_stateManager.TryGetValue(identifier, out var state))
{
_objects.Update();
var data = _objects[identifier];
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
return GlamourerErrorCode.ActorNotFound;
}
if (!state.ModelData.IsHuman)
return GlamourerErrorCode.ActorNotHuman;
_stateManager.ChangeEquip(state, slot, item, stainId,
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key));
return GlamourerErrorCode.Success;
}
private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once)
{
if (itemId.Id == 0)
itemId = ItemManager.NothingId(slot);
var item = _items.Resolve(slot, itemId);
if (!item.Valid)
return GlamourerErrorCode.ItemInvalid;
var found = false;
_objects.Update();
foreach (var identifier in FindActorsRevert(name).Distinct())
{
if (!_stateManager.TryGetValue(identifier, out var state))
{
var data = _objects[identifier];
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
continue;
}
if (!state.ModelData.IsHuman)
return GlamourerErrorCode.ActorNotHuman;
_stateManager.ChangeEquip(state, slot, item, stainId,
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key));
found = true;
}
return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound;
}
}

View file

@ -1,196 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.String;
namespace Glamourer.Api;
public sealed partial class GlamourerIpc : IDisposable
{
public const int CurrentApiVersionMajor = 0;
public const int CurrentApiVersionMinor = 5;
private readonly StateManager _stateManager;
private readonly ObjectManager _objects;
private readonly ActorManager _actors;
private readonly DesignConverter _designConverter;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly DesignManager _designManager;
private readonly ItemManager _items;
private readonly Configuration _config;
public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors,
DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier,
DesignManager designManager, ItemManager items, Configuration config)
{
_stateManager = stateManager;
_objects = objects;
_actors = actors;
_designConverter = designConverter;
_autoDesignApplier = autoDesignApplier;
_items = items;
_config = config;
_gPose = gPose;
_stateChangedEvent = stateChangedEvent;
_designManager = designManager;
_apiVersionProvider = new FuncProvider<int>(pi, LabelApiVersion, ApiVersion);
_apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions);
_getAllCustomizationProvider = new FuncProvider<string, string?>(pi, LabelGetAllCustomization, GetAllCustomization);
_getAllCustomizationFromCharacterProvider =
new FuncProvider<Character?, string?>(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter);
_getAllCustomizationLockedProvider = new FuncProvider<string, uint, string?>(pi, LabelGetAllCustomizationLocked, GetAllCustomization);
_getAllCustomizationFromLockedCharacterProvider =
new FuncProvider<Character?, uint, string?>(pi, LabelGetAllCustomizationFromLockedCharacter, GetAllCustomizationFromCharacter);
_applyAllProvider = new ActionProvider<string, string>(pi, LabelApplyAll, ApplyAll);
_applyAllOnceProvider = new ActionProvider<string, string>(pi, LabelApplyAllOnce, ApplyAllOnce);
_applyAllToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllToCharacter, ApplyAllToCharacter);
_applyAllOnceToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllOnceToCharacter, ApplyAllOnceToCharacter);
_applyOnlyEquipmentProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment);
_applyOnlyEquipmentToCharacterProvider =
new ActionProvider<string, Character?>(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter);
_applyOnlyCustomizationProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization);
_applyOnlyCustomizationToCharacterProvider =
new ActionProvider<string, Character?>(pi, LabelApplyOnlyCustomizationToCharacter, ApplyOnlyCustomizationToCharacter);
_applyAllProviderLock = new ActionProvider<string, string, uint>(pi, LabelApplyAllLock, ApplyAllLock);
_applyAllToCharacterProviderLock =
new ActionProvider<string, Character?, uint>(pi, LabelApplyAllToCharacterLock, ApplyAllToCharacterLock);
_applyOnlyEquipmentProviderLock = new ActionProvider<string, string, uint>(pi, LabelApplyOnlyEquipmentLock, ApplyOnlyEquipmentLock);
_applyOnlyEquipmentToCharacterProviderLock =
new ActionProvider<string, Character?, uint>(pi, LabelApplyOnlyEquipmentToCharacterLock, ApplyOnlyEquipmentToCharacterLock);
_applyOnlyCustomizationProviderLock =
new ActionProvider<string, string, uint>(pi, LabelApplyOnlyCustomizationLock, ApplyOnlyCustomizationLock);
_applyOnlyCustomizationToCharacterProviderLock =
new ActionProvider<string, Character?, uint>(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock);
_applyByGuidProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuid, ApplyByGuid);
_applyByGuidOnceProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuidOnce, ApplyByGuidOnce);
_applyByGuidToCharacterProvider = new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter);
_applyByGuidOnceToCharacterProvider =
new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidOnceToCharacter, ApplyByGuidOnceToCharacter);
_revertProvider = new ActionProvider<string>(pi, LabelRevert, Revert);
_revertCharacterProvider = new ActionProvider<Character?>(pi, LabelRevertCharacter, RevertCharacter);
_revertProviderLock = new ActionProvider<string, uint>(pi, LabelRevertLock, RevertLock);
_revertCharacterProviderLock = new ActionProvider<Character?, uint>(pi, LabelRevertCharacterLock, RevertCharacterLock);
_unlockNameProvider = new FuncProvider<string, uint, bool>(pi, LabelUnlockName, Unlock);
_unlockProvider = new FuncProvider<Character?, uint, bool>(pi, LabelUnlock, Unlock);
_unlockAllProvider = new FuncProvider<uint, int>(pi, LabelUnlockAll, UnlockAll);
_revertToAutomationProvider = new FuncProvider<string, uint, bool>(pi, LabelRevertToAutomation, RevertToAutomation);
_revertToAutomationCharacterProvider =
new FuncProvider<Character?, uint, bool>(pi, LabelRevertToAutomationCharacter, RevertToAutomation);
_stateChangedProvider = new EventProvider<StateChanged.Type, nint, Lazy<string>>(pi, LabelStateChanged);
_gPoseChangedProvider = new EventProvider<bool>(pi, LabelGPoseChanged);
_setItemProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItem,
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false));
_setItemOnceProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItemOnce,
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true));
_setItemByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemByActorName,
(name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, false));
_setItemOnceByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemOnceByActorName,
(name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, true));
_stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc);
_gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc);
_getDesignListProvider = new FuncProvider<(string Name, Guid Identifier)[]>(pi, LabelGetDesignList, GetDesignList);
}
public void Dispose()
{
_apiVersionProvider.Dispose();
_apiVersionsProvider.Dispose();
_getAllCustomizationProvider.Dispose();
_getAllCustomizationLockedProvider.Dispose();
_getAllCustomizationFromCharacterProvider.Dispose();
_getAllCustomizationFromLockedCharacterProvider.Dispose();
_applyAllProvider.Dispose();
_applyAllOnceProvider.Dispose();
_applyAllToCharacterProvider.Dispose();
_applyAllOnceToCharacterProvider.Dispose();
_applyOnlyEquipmentProvider.Dispose();
_applyOnlyEquipmentToCharacterProvider.Dispose();
_applyOnlyCustomizationProvider.Dispose();
_applyOnlyCustomizationToCharacterProvider.Dispose();
_applyAllProviderLock.Dispose();
_applyAllToCharacterProviderLock.Dispose();
_applyOnlyEquipmentProviderLock.Dispose();
_applyOnlyEquipmentToCharacterProviderLock.Dispose();
_applyOnlyCustomizationProviderLock.Dispose();
_applyOnlyCustomizationToCharacterProviderLock.Dispose();
_applyByGuidProvider.Dispose();
_applyByGuidOnceProvider.Dispose();
_applyByGuidToCharacterProvider.Dispose();
_applyByGuidOnceToCharacterProvider.Dispose();
_revertProvider.Dispose();
_revertCharacterProvider.Dispose();
_revertProviderLock.Dispose();
_revertCharacterProviderLock.Dispose();
_unlockNameProvider.Dispose();
_unlockProvider.Dispose();
_unlockAllProvider.Dispose();
_revertToAutomationProvider.Dispose();
_revertToAutomationCharacterProvider.Dispose();
_stateChangedEvent.Unsubscribe(OnStateChanged);
_stateChangedProvider.Dispose();
_gPose.Unsubscribe(OnGPoseChanged);
_gPoseChangedProvider.Dispose();
_getDesignListProvider.Dispose();
_setItemProvider.Dispose();
_setItemOnceProvider.Dispose();
_setItemByActorNameProvider.Dispose();
_setItemOnceByActorNameProvider.Dispose();
}
private IEnumerable<ActorIdentifier> FindActors(string actorName)
{
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
return [];
_objects.Update();
return _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString);
}
private IEnumerable<ActorIdentifier> FindActorsRevert(string actorName)
{
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
yield break;
_objects.Update();
foreach (var id in _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString)
.Select(i => i))
yield return id;
foreach (var id in _stateManager.Keys.Where(s => s.Type is IdentifierType.Player && s.PlayerName == byteString))
yield return id;
}
private IEnumerable<ActorIdentifier> FindActors(Character? character)
{
var id = _actors.FromObject(character, true, true, false);
if (!id.IsValid)
yield break;
yield return id;
}
}

View file

@ -0,0 +1,56 @@
using Dalamud.Plugin;
using Glamourer.Api.Api;
using OtterGui.Services;
using Penumbra.Api.Helpers;
namespace Glamourer.Api;
public sealed class IpcProviders : IDisposable, IApiService
{
private readonly List<IDisposable> _providers;
private readonly EventProvider _disposedProvider;
private readonly EventProvider _initializedProvider;
public IpcProviders(DalamudPluginInterface pi, IGlamourerApi api)
{
_disposedProvider = IpcSubscribers.Disposed.Provider(pi);
_initializedProvider = IpcSubscribers.Initialized.Provider(pi);
_providers =
[
IpcSubscribers.ApiVersion.Provider(pi, api),
IpcSubscribers.GetDesignList.Provider(pi, api.Designs),
IpcSubscribers.ApplyDesign.Provider(pi, api.Designs),
IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs),
IpcSubscribers.SetItem.Provider(pi, api.Items),
IpcSubscribers.SetItemName.Provider(pi, api.Items),
IpcSubscribers.GetState.Provider(pi, api.State),
IpcSubscribers.GetStateName.Provider(pi, api.State),
IpcSubscribers.ApplyState.Provider(pi, api.State),
IpcSubscribers.ApplyStateName.Provider(pi, api.State),
IpcSubscribers.RevertState.Provider(pi, api.State),
IpcSubscribers.RevertStateName.Provider(pi, api.State),
IpcSubscribers.UnlockState.Provider(pi, api.State),
IpcSubscribers.UnlockStateName.Provider(pi, api.State),
IpcSubscribers.UnlockAll.Provider(pi, api.State),
IpcSubscribers.RevertToAutomation.Provider(pi, api.State),
IpcSubscribers.RevertToAutomationName.Provider(pi, api.State),
IpcSubscribers.StateChanged.Provider(pi, api.State),
IpcSubscribers.GPoseChanged.Provider(pi, api.State),
];
_initializedProvider.Invoke();
}
public void Dispose()
{
foreach (var provider in _providers)
provider.Dispose();
_providers.Clear();
_initializedProvider.Dispose();
_disposedProvider.Invoke();
_disposedProvider.Dispose();
}
}

View file

@ -0,0 +1,52 @@
using Dalamud.Plugin;
using Glamourer.Api.Api;
using Glamourer.Api.Enums;
using Penumbra.Api.Helpers;
namespace Glamourer.Api.IpcSubscribers;
/// <inheritdoc cref="IGlamourerApiDesigns.GetDesignList"/>
public sealed class GetDesignList(DalamudPluginInterface pi)
: FuncSubscriber<Dictionary<Guid, string>>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(GetDesignList)}";
/// <inheritdoc cref="IGlamourerApiDesigns.GetDesignList"/>
public new Dictionary<Guid, string> Invoke()
=> base.Invoke();
/// <summary> Create a provider. </summary>
public static FuncProvider<Dictionary<Guid, string>> Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api)
=> new(pi, Label, api.GetDesignList);
}
/// <inheritdoc cref="IGlamourerApiDesigns.ApplyDesign"/>
public sealed class ApplyDesign(DalamudPluginInterface pi) : FuncSubscriber<Guid, int, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(ApplyDesign)}";
/// <inheritdoc cref="IGlamourerApiDesigns.ApplyDesign"/>
public GlamourerApiEc Invoke(Guid designId, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault)
=> (GlamourerApiEc)Invoke(designId, objectIndex, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<Guid, int, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api)
=> new(pi, Label, (a, b, c, d) => (int)api.ApplyDesign(a, b, c, (ApplyFlag)d));
}
/// <inheritdoc cref="IGlamourerApiDesigns.ApplyDesignName"/>
public sealed class ApplyDesignName(DalamudPluginInterface pi) : FuncSubscriber<Guid, string, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(ApplyDesignName)}";
/// <inheritdoc cref="IGlamourerApiDesigns.ApplyDesignName"/>
public GlamourerApiEc Invoke(Guid designId, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault)
=> (GlamourerApiEc)Invoke(designId, objectName, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<Guid, string, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api)
=> new(pi, Label, (a, b, c, d) => (int)api.ApplyDesignName(a, b, c, (ApplyFlag)d));
}

View file

@ -0,0 +1,39 @@
using Dalamud.Plugin;
using Glamourer.Api.Api;
using Glamourer.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Enums;
namespace Glamourer.Api.IpcSubscribers;
/// <inheritdoc cref="IGlamourerApiItems.SetItem"/>
public sealed class SetItem(DalamudPluginInterface pi)
: FuncSubscriber<int, byte, ulong, byte, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(SetItem)}";
/// <inheritdoc cref="IGlamourerApiItems.SetItem"/>
public GlamourerApiEc Invoke(int objectIndex, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once)
=> (GlamourerApiEc)Invoke(objectIndex, (byte)slot, itemId, stain, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<int, byte, ulong, byte, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiItems api)
=> new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItem(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f));
}
/// <inheritdoc cref="IGlamourerApiItems.SetItemName"/>
public sealed class SetItemName(DalamudPluginInterface pi)
: FuncSubscriber<string, byte, ulong, byte, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(SetItemName)}";
/// <inheritdoc cref="IGlamourerApiItems.SetItem"/>
public GlamourerApiEc Invoke(string objectName, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once)
=> (GlamourerApiEc)Invoke(objectName, (byte)slot, itemId, stain, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<string, byte, ulong, byte, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiItems api)
=> new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItemName(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f));
}

View file

@ -0,0 +1,51 @@
using Dalamud.Plugin;
using Glamourer.Api.Api;
using Penumbra.Api.Helpers;
namespace Glamourer.Api.IpcSubscribers;
/// <inheritdoc cref="IGlamourerApiBase.ApiVersion"/>
public sealed class ApiVersion(DalamudPluginInterface pi)
: FuncSubscriber<(int, int)>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(ApiVersion)}";
/// <inheritdoc cref="IGlamourerApiBase.ApiVersion"/>
public new (int Major, int Minor) Invoke()
=> base.Invoke();
/// <summary> Create a provider. </summary>
public static FuncProvider<(int, int)> Provider(DalamudPluginInterface pi, IGlamourerApiBase api)
=> new(pi, Label, () => api.ApiVersion);
}
/// <summary> Triggered when the Glamourer API is initialized and ready. </summary>
public static class Initialized
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(Initialized)}";
/// <summary> Create a new event subscriber. </summary>
public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions)
=> new(pi, Label, actions);
/// <summary> Create a provider. </summary>
public static EventProvider Provider(DalamudPluginInterface pi)
=> new(pi, Label);
}
/// <summary> Triggered when the Glamourer API is fully disposed and unavailable. </summary>
public static class Disposed
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(Disposed)}";
/// <summary> Create a new event subscriber. </summary>
public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions)
=> new(pi, Label, actions);
/// <summary> Create a provider. </summary>
public static EventProvider Provider(DalamudPluginInterface pi)
=> new(pi, Label);
}

View file

@ -0,0 +1,235 @@
using Dalamud.Plugin;
using Glamourer.Api.Api;
using Glamourer.Api.Enums;
using Newtonsoft.Json.Linq;
using Penumbra.Api.Helpers;
namespace Glamourer.Api.IpcSubscribers;
/// <inheritdoc cref="IGlamourerApiState.GetState"/>
public sealed class GetState(DalamudPluginInterface pi)
: FuncSubscriber<int, uint, (int, JObject?)>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(GetState)}";
/// <inheritdoc cref="IGlamourerApiState.GetState"/>
public new (GlamourerApiEc, JObject?) Invoke(int objectIndex, uint key = 0)
{
var (ec, data) = base.Invoke(objectIndex, key);
return ((GlamourerApiEc)ec, data);
}
/// <summary> Create a provider. </summary>
public static FuncProvider<int, uint, (int, JObject?)> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b) =>
{
var (ec, data) = api.GetState(a, b);
return ((int)ec, data);
});
}
/// <inheritdoc cref="IGlamourerApiState.GetStateName"/>
public sealed class GetStateName(DalamudPluginInterface pi)
: FuncSubscriber<string, uint, (int, JObject?)>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(GetStateName)}";
/// <inheritdoc cref="IGlamourerApiState.GetStateName"/>
public new (GlamourerApiEc, JObject?) Invoke(string objectName, uint key = 0)
{
var (ec, data) = base.Invoke(objectName, key);
return ((GlamourerApiEc)ec, data);
}
/// <summary> Create a provider. </summary>
public static FuncProvider<string, uint, (int, JObject?)> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (i, k) =>
{
var (ec, data) = api.GetStateName(i, k);
return ((int)ec, data);
});
}
/// <inheritdoc cref="IGlamourerApiState.ApplyState"/>
public sealed class ApplyState(DalamudPluginInterface pi)
: FuncSubscriber<object, int, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(ApplyState)}";
/// <inheritdoc cref="IGlamourerApiState.ApplyState"/>
public GlamourerApiEc Invoke(JObject state, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault)
=> (GlamourerApiEc)Invoke(state, objectIndex, key, (ulong)flags);
/// <inheritdoc cref="IGlamourerApiState.ApplyState"/>
public GlamourerApiEc Invoke(string base64State, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault)
=> (GlamourerApiEc)Invoke(base64State, objectIndex, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<object, int, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b, c, d) => (int)api.ApplyState(a, b, c, (ApplyFlag)d));
}
/// <inheritdoc cref="IGlamourerApiState.ApplyStateName"/>
public sealed class ApplyStateName(DalamudPluginInterface pi)
: FuncSubscriber<object, string, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(ApplyStateName)}";
/// <inheritdoc cref="IGlamourerApiState.ApplyState"/>
public GlamourerApiEc Invoke(JObject state, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault)
=> (GlamourerApiEc)Invoke(state, objectName, key, (ulong)flags);
/// <inheritdoc cref="IGlamourerApiState.ApplyState"/>
public GlamourerApiEc Invoke(string base64State, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault)
=> (GlamourerApiEc)Invoke(base64State, objectName, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<object, string, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b, c, d) => (int)api.ApplyStateName(a, b, c, (ApplyFlag)d));
}
/// <inheritdoc cref="IGlamourerApiState.RevertState"/>
public sealed class RevertState(DalamudPluginInterface pi)
: FuncSubscriber<int, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(RevertState)}";
/// <inheritdoc cref="IGlamourerApiState.RevertState"/>
public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault)
=> (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<int, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b, c) => (int)api.RevertState(a, b, (ApplyFlag)c));
}
/// <inheritdoc cref="IGlamourerApiState.RevertStateName"/>
public sealed class RevertStateName(DalamudPluginInterface pi)
: FuncSubscriber<string, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(RevertStateName)}";
/// <inheritdoc cref="IGlamourerApiState.RevertStateName"/>
public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault)
=> (GlamourerApiEc)Invoke(objectName, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<string, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b, c) => (int)api.RevertStateName(a, b, (ApplyFlag)c));
}
/// <inheritdoc cref="IGlamourerApiState.UnlockState"/>
public sealed class UnlockState(DalamudPluginInterface pi)
: FuncSubscriber<int, uint, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(UnlockState)}";
/// <inheritdoc cref="IGlamourerApiState.UnlockState"/>
public new GlamourerApiEc Invoke(int objectIndex, uint key = 0)
=> (GlamourerApiEc)base.Invoke(objectIndex, key);
/// <summary> Create a provider. </summary>
public static FuncProvider<int, uint, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b) => (int)api.UnlockState(a, b));
}
/// <inheritdoc cref="IGlamourerApiState.UnlockStateName"/>
public sealed class UnlockStateName(DalamudPluginInterface pi)
: FuncSubscriber<string, uint, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(UnlockStateName)}";
/// <inheritdoc cref="IGlamourerApiState.UnlockStateName"/>
public new GlamourerApiEc Invoke(string objectName, uint key = 0)
=> (GlamourerApiEc)base.Invoke(objectName, key);
/// <summary> Create a provider. </summary>
public static FuncProvider<string, uint, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b) => (int)api.UnlockStateName(a, b));
}
/// <inheritdoc cref="IGlamourerApiState.UnlockAll"/>
public sealed class UnlockAll(DalamudPluginInterface pi)
: FuncSubscriber<uint, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(UnlockAll)}";
/// <inheritdoc cref="IGlamourerApiState.UnlockAll"/>
public new int Invoke(uint key)
=> base.Invoke(key);
/// <summary> Create a provider. </summary>
public static FuncProvider<uint, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, api.UnlockAll);
}
/// <inheritdoc cref="IGlamourerApiState.RevertToAutomation"/>
public sealed class RevertToAutomation(DalamudPluginInterface pi)
: FuncSubscriber<int, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(RevertToAutomation)}";
/// <inheritdoc cref="IGlamourerApiState.RevertToAutomation"/>
public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault)
=> (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<int, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b, c) => (int)api.RevertToAutomation(a, b, (ApplyFlag)c));
}
/// <inheritdoc cref="IGlamourerApiState.RevertToAutomationName"/>
public sealed class RevertToAutomationName(DalamudPluginInterface pi)
: FuncSubscriber<string, uint, ulong, int>(pi, Label)
{
/// <summary> The label. </summary>
public const string Label = $"Glamourer.{nameof(RevertToAutomationName)}";
/// <inheritdoc cref="IGlamourerApiState.RevertToAutomationName"/>
public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault)
=> (GlamourerApiEc)Invoke(objectName, key, (ulong)flags);
/// <summary> Create a provider. </summary>
public static FuncProvider<string, uint, ulong, int> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (a, b, c) => (int)api.RevertToAutomationName(a, b, (ApplyFlag)c));
}
/// <inheritdoc cref="IGlamourerApiState.StateChanged" />
public static class StateChanged
{
/// <summary> The label. </summary>
public const string Label = $"Penumbra.{nameof(StateChanged)}";
/// <summary> Create a new event subscriber. </summary>
public static EventSubscriber<nint> Subscriber(DalamudPluginInterface pi, params Action<nint>[] actions)
=> new(pi, Label, actions);
/// <summary> Create a provider. </summary>
public static EventProvider<nint> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (t => api.StateChanged += t, t => api.StateChanged -= t));
}
/// <inheritdoc cref="IGlamourerApiState.GPoseChanged" />
public static class GPoseChanged
{
/// <summary> The label. </summary>
public const string Label = $"Penumbra.{nameof(GPoseChanged)}";
/// <summary> Create a new event subscriber. </summary>
public static EventSubscriber<bool> Subscriber(DalamudPluginInterface pi, params Action<bool>[] actions)
=> new(pi, Label, actions);
/// <summary> Create a provider. </summary>
public static EventProvider<bool> Provider(DalamudPluginInterface pi, IGlamourerApiState api)
=> new(pi, Label, (t => api.GPoseChanged += t, t => api.GPoseChanged -= t));
}

82
Glamourer/Api/ItemsApi.cs Normal file
View file

@ -0,0 +1,82 @@
using Glamourer.Api.Api;
using Glamourer.Api.Enums;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Api;
public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager stateManager) : IGlamourerApiItems, IApiService
{
public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags);
if (!ResolveItem(slot, itemId, out var item))
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
if (helpers.FindState(objectIndex) is not { } state)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (!state.ModelData.IsHuman)
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
if (!state.CanUnlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key);
stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings);
ApiHelpers.Lock(state, key, flags);
return GlamourerApiEc.Success;
}
public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Name", objectName, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags);
if (!ResolveItem(slot, itemId, out var item))
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key);
var anyHuman = false;
var anyFound = false;
var anyUnlocked = false;
foreach (var state in helpers.FindStates(objectName))
{
anyFound = true;
if (!state.ModelData.IsHuman)
continue;
anyHuman = true;
if (!state.CanUnlock(key))
continue;
anyUnlocked = true;
stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings);
ApiHelpers.Lock(state, key, flags);
}
if (!anyFound)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (!anyHuman)
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
if (!anyUnlocked)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item)
{
var id = (CustomItemId)itemId;
var slot = (EquipSlot)apiSlot;
if (id.Id == 0)
id = ItemManager.NothingId(slot);
item = itemManager.Resolve(slot, id);
return item.Valid;
}
}

328
Glamourer/Api/StateApi.cs Normal file
View file

@ -0,0 +1,328 @@
using Glamourer.Api.Api;
using Glamourer.Api.Enums;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Newtonsoft.Json.Linq;
using OtterGui.Services;
using Penumbra.GameData.Interop;
using ObjectManager = Glamourer.Interop.ObjectManager;
using StateChanged = Glamourer.Events.StateChanged;
namespace Glamourer.Api;
public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
{
private readonly ApiHelpers _helpers;
private readonly StateManager _stateManager;
private readonly DesignConverter _converter;
private readonly Configuration _config;
private readonly AutoDesignApplier _autoDesigns;
private readonly ObjectManager _objects;
private readonly StateChanged _stateChanged;
private readonly GPoseService _gPose;
public StateApi(ApiHelpers helpers,
StateManager stateManager,
DesignConverter converter,
Configuration config,
AutoDesignApplier autoDesigns,
ObjectManager objects,
StateChanged stateChanged,
GPoseService gPose)
{
_helpers = helpers;
_stateManager = stateManager;
_converter = converter;
_config = config;
_autoDesigns = autoDesigns;
_objects = objects;
_stateChanged = stateChanged;
_gPose = gPose;
_stateChanged.Subscribe(OnStateChange, Events.StateChanged.Priority.GlamourerIpc);
_gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc);
}
public void Dispose()
{
_stateChanged.Unsubscribe(OnStateChange);
_gPose.Unsubscribe(OnGPoseChange);
}
public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key)
=> Convert(_helpers.FindState(objectIndex), key);
public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key)
=> Convert(_helpers.FindStates(objectName).FirstOrDefault(), key);
public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
if (Convert(applyState, flags, out var version) is not { } design)
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args);
if (_helpers.FindState(objectIndex) is not { } state)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (!state.CanUnlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
if (version < 3 && state.ModelData.ModelId != 0)
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
ApplyDesign(state, design, key, flags);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc ApplyStateName(object applyState, string objectName, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags);
if (Convert(applyState, flags, out var version) is not { } design)
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args);
var states = _helpers.FindExistingStates(objectName);
var any = false;
var anyUnlocked = false;
var anyHuman = false;
foreach (var state in states)
{
any = true;
if (!state.CanUnlock(key))
continue;
anyUnlocked = true;
if (version < 3 && state.ModelData.ModelId != 0)
continue;
anyHuman = true;
ApplyDesign(state, design, key, flags);
}
if (any)
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!anyUnlocked)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
if (!anyHuman)
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (state == null)
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!state.CanUnlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
Revert(state, key, flags);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags);
var states = _helpers.FindExistingStates(objectName);
var any = false;
var anyUnlocked = false;
foreach (var state in states)
{
any = true;
if (!state.CanUnlock(key))
continue;
anyUnlocked = true;
Revert(state, key, flags);
}
if (any)
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!anyUnlocked)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc UnlockState(int objectIndex, uint key)
{
var args = ApiHelpers.Args("Index", objectIndex, "Key", key);
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (state == null)
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!state.Unlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc UnlockStateName(string objectName, uint key)
{
var args = ApiHelpers.Args("Name", objectName, "Key", key);
var states = _helpers.FindExistingStates(objectName);
var any = false;
var anyUnlocked = false;
foreach (var state in states)
{
any = true;
anyUnlocked |= state.Unlock(key);
}
if (any)
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!anyUnlocked)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public int UnlockAll(uint key)
=> _stateManager.Values.Count(state => state.Unlock(key));
public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (state == null)
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!state.CanUnlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
RevertToAutomation(_objects[objectIndex], state, key, flags);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags);
var states = _helpers.FindExistingStates(objectName);
var any = false;
var anyUnlocked = false;
var anyReverted = false;
foreach (var state in states)
{
any = true;
if (!state.CanUnlock(key))
continue;
anyUnlocked = true;
anyReverted |= RevertToAutomation(state, key, flags) is GlamourerApiEc.Success;
}
if (any)
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!anyReverted)
ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (!anyUnlocked)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public event Action<IntPtr>? StateChanged;
public event Action<bool>? GPoseChanged;
private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags)
{
var once = (flags & ApplyFlag.Once) != 0;
var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true,
ResetMaterials: !once && key != 0);
_stateManager.ApplyDesign(state, design, settings);
ApiHelpers.Lock(state, key, flags);
}
private void Revert(ActorState state, uint key, ApplyFlag flags)
{
var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed;
switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization))
{
case ApplyFlag.Equipment:
_stateManager.ResetEquip(state, source, key);
break;
case ApplyFlag.Customization:
_stateManager.ResetCustomize(state, source, key);
break;
case ApplyFlag.Equipment | ApplyFlag.Customization:
_stateManager.ResetState(state, source, key);
break;
}
ApiHelpers.Lock(state, key, flags);
}
private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags)
{
_objects.Update();
if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid)
return GlamourerApiEc.ActorNotFound;
foreach (var actor in actors.Objects)
RevertToAutomation(actor, state, key, flags);
return GlamourerApiEc.Success;
}
private void RevertToAutomation(Actor actor, ActorState state, uint key, ApplyFlag flags)
{
var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed;
_autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true);
_stateManager.ReapplyState(actor, state, source);
ApiHelpers.Lock(state, key, flags);
}
private (GlamourerApiEc, JObject?) Convert(ActorState? state, uint key)
{
if (state == null)
return (GlamourerApiEc.ActorNotFound, null);
if (!state.CanUnlock(key))
return (GlamourerApiEc.InvalidKey, null);
return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config)));
}
private DesignBase? Convert(object? state, ApplyFlag flags, out byte version)
{
version = DesignConverter.Version;
return state switch
{
string s => _converter.FromBase64(s, (flags & ApplyFlag.Equipment) != 0, (flags & ApplyFlag.Customization) != 0, out version),
JObject j => _converter.FromJObject(j, (flags & ApplyFlag.Equipment) != 0, (flags & ApplyFlag.Customization) != 0),
_ => null,
};
}
private void OnGPoseChange(bool gPose)
=> GPoseChanged?.Invoke(gPose);
private void OnStateChange(StateChanged.Type _1, StateSource _2, ActorState _3, ActorData actors, object? _5)
{
if (StateChanged == null)
return;
foreach (var actor in actors.Objects)
StateChanged.Invoke(actor.Address);
}
}

View file

@ -199,8 +199,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
continue; continue;
} }
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, string[]>>() ?? new Dictionary<string, string[]>(); var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, List<string>>>() ?? [];
var settings = new SortedList<string, IList<string>>(settingsDict.Count); var settings = new Dictionary<string, List<string>>(settingsDict.Count);
foreach (var (key, value) in settingsDict) foreach (var (key, value) in settingsDict)
settings.Add(key, value); settings.Add(key, value);
var priority = tok["Priority"]?.ToObject<int>() ?? 0; var priority = tok["Priority"]?.ToObject<int>() ?? 0;

View file

@ -34,10 +34,10 @@ public class DesignConverter(
} }
public string ShareBase64(Design design) public string ShareBase64(Design design)
=> ShareBase64(ShareJObject(design)); => ToBase64(ShareJObject(design));
public string ShareBase64(DesignBase design) public string ShareBase64(DesignBase design)
=> ShareBase64(ShareJObject(design)); => ToBase64(ShareJObject(design));
public string ShareBase64(ActorState state, in ApplicationRules rules) public string ShareBase64(ActorState state, in ApplicationRules rules)
=> ShareBase64(state.ModelData, state.Materials, rules); => ShareBase64(state.ModelData, state.Materials, rules);
@ -45,7 +45,7 @@ public class DesignConverter(
public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
{ {
var design = Convert(data, materials, rules); var design = Convert(data, materials, rules);
return ShareBase64(ShareJObject(design)); return ToBase64(ShareJObject(design));
} }
public DesignBase Convert(ActorState state, in ApplicationRules rules) public DesignBase Convert(ActorState state, in ApplicationRules rules)
@ -61,6 +61,37 @@ public class DesignConverter(
return design; return design;
} }
public DesignBase? FromJObject(JObject? jObject, bool customize, bool equip)
{
if (jObject == null)
return null;
try
{
var ret = jObject["Identifier"] != null
? Design.LoadDesign(_customize, _items, _linkLoader, jObject)
: DesignBase.LoadDesignBase(_customize, _items, jObject);
ret.SetApplyMeta(MetaIndex.Wetness, customize);
if (!customize)
ret.ApplyCustomize = 0;
if (!equip)
{
ret.ApplyEquip = 0;
ret.ApplyCrest = 0;
ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
}
return ret;
}
catch (Exception ex)
{
Glamourer.Log.Warning($"Failure to parse JObject to design:\n{ex}");
return null;
}
}
public DesignBase? FromBase64(string base64, bool customize, bool equip, out byte version) public DesignBase? FromBase64(string base64, bool customize, bool equip, out byte version)
{ {
DesignBase ret; DesignBase ret;
@ -138,7 +169,7 @@ public class DesignConverter(
return ret; return ret;
} }
private static string ShareBase64(JToken jObject) public static string ToBase64(JToken jObject)
{ {
var json = jObject.ToString(Formatting.None); var json = jObject.ToString(Formatting.None);
var compressed = json.Compress(Version); var compressed = json.Compress(Version);

View file

@ -40,7 +40,7 @@ public class Glamourer : IDalamudPlugin
_services.GetService<StateListener>(); // Initialize State Listener. _services.GetService<StateListener>(); // Initialize State Listener.
_services.GetService<GlamourerWindowSystem>(); // initialize ui. _services.GetService<GlamourerWindowSystem>(); // initialize ui.
_services.GetService<CommandService>(); // initialize commands. _services.GetService<CommandService>(); // initialize commands.
_services.GetService<GlamourerIpc>(); // initialize IPC. _services.GetService<IpcProviders>(); // initialize IPC.
Log.Information($"Glamourer v{Version} loaded successfully."); Log.Information($"Glamourer v{Version} loaded successfully.");
} }
catch catch

View file

@ -1,4 +1,5 @@
using ImGuiNET; using Glamourer.Gui.Tabs.DebugTab.IpcTester;
using ImGuiNET;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Gui.Debug;

View file

@ -0,0 +1,78 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Glamourer.Api.Enums;
using Glamourer.Api.IpcSubscribers;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class DesignIpcTester(DalamudPluginInterface pluginInterface) : IUiService
{
private Dictionary<Guid, string> _designs = [];
private int _gameObjectIndex;
private string _gameObjectName = string.Empty;
private uint _key;
private ApplyFlag _flags = ApplyFlagEx.DesignDefault;
private Guid? _design;
private string _designText = string.Empty;
private GlamourerApiEc _lastError;
public void Draw()
{
using var tree = ImRaii.TreeNode("Designs");
if (!tree)
return;
IpcTesterHelpers.IndexInput(ref _gameObjectIndex);
IpcTesterHelpers.KeyInput(ref _key);
IpcTesterHelpers.NameInput(ref _gameObjectName);
ImGuiUtil.GuidInput("##identifier", "Design Identifier...", string.Empty, ref _design, ref _designText,
ImGui.GetContentRegionAvail().X);
IpcTesterHelpers.DrawFlagInput(ref _flags);
using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit);
IpcTesterHelpers.DrawIntro("Last Error");
ImGui.TextUnformatted(_lastError.ToString());
IpcTesterHelpers.DrawIntro(GetDesignList.Label);
DrawDesignsPopup();
if (ImGui.Button("Get##Designs"))
{
_designs = new GetDesignList(pluginInterface).Invoke();
ImGui.OpenPopup("Designs");
}
IpcTesterHelpers.DrawIntro(ApplyDesign.Label);
if (ImGuiUtil.DrawDisabledButton("Apply##Idx", Vector2.Zero, string.Empty, !_design.HasValue))
_lastError = new ApplyDesign(pluginInterface).Invoke(_design!.Value, _gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(ApplyDesignName.Label);
if (ImGuiUtil.DrawDisabledButton("Apply##Name", Vector2.Zero, string.Empty, !_design.HasValue))
_lastError = new ApplyDesignName(pluginInterface).Invoke(_design!.Value, _gameObjectName, _key, _flags);
}
private void DrawDesignsPopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var p = ImRaii.Popup("Designs");
if (!p)
return;
using var table = ImRaii.Table("Designs", 2, ImGuiTableFlags.SizingFixedFit);
foreach (var (guid, name) in _designs)
{
ImGuiUtil.DrawTableColumn(name);
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TableNextColumn();
ImGuiUtil.CopyOnClickSelectable(guid.ToString());
}
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
}

View file

@ -0,0 +1,60 @@
using Glamourer.Api.Enums;
using Glamourer.Designs;
using ImGuiNET;
using OtterGui;
using static Penumbra.GameData.Files.ShpkFile;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public static class IpcTesterHelpers
{
public static void DrawFlagInput(ref ApplyFlag flags)
{
var value = (flags & ApplyFlag.Once) != 0;
if (ImGui.Checkbox("Apply Once", ref value))
flags = value ? flags | ApplyFlag.Once : flags & ~ApplyFlag.Once;
ImGui.SameLine();
value = (flags & ApplyFlag.Equipment) != 0;
if (ImGui.Checkbox("Apply Equipment", ref value))
flags = value ? flags | ApplyFlag.Equipment : flags & ~ApplyFlag.Equipment;
ImGui.SameLine();
value = (flags & ApplyFlag.Customization) != 0;
if (ImGui.Checkbox("Apply Customization", ref value))
flags = value ? flags | ApplyFlag.Customization : flags & ~ApplyFlag.Customization;
ImGui.SameLine();
value = (flags & ApplyFlag.Lock) != 0;
if (ImGui.Checkbox("Lock Actor", ref value))
flags = value ? flags | ApplyFlag.Lock : flags & ~ApplyFlag.Lock;
}
public static void IndexInput(ref int index)
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2);
ImGui.InputInt("Game Object Index", ref index, 0, 0);
}
public static void KeyInput(ref uint key)
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2);
var keyI = (int)key;
if (ImGui.InputInt("Key", ref keyI, 0, 0))
key = (uint)keyI;
}
public static void NameInput(ref string name)
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.InputTextWithHint("##gameObject", "Character Name...", ref name, 64);
}
public static void DrawIntro(string intro)
{
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(intro);
ImGui.TableNextColumn();
}
}

View file

@ -0,0 +1,271 @@
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Api;
using Glamourer.Api.Enums;
using Glamourer.Api.IpcSubscribers;
using Glamourer.Interop;
using ImGuiNET;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class IpcTesterPanel(
DalamudPluginInterface pluginInterface,
DesignIpcTester designs,
ItemsIpcTester items,
StateIpcTester state,
IFramework framework) : IGameDataDrawer
{
public string Label
=> "IPC Tester";
public bool Disabled
=> false;
private DateTime _lastUpdate;
private bool _subscribed = false;
public void Draw()
{
try
{
_lastUpdate = framework.LastUpdateUTC.AddSeconds(1);
Subscribe();
ImGui.TextUnformatted(ApiVersion.Label);
var (major, minor) = new ApiVersion(pluginInterface).Invoke();
ImGui.SameLine();
ImGui.TextUnformatted($"({major}.{minor:D4})");
designs.Draw();
items.Draw();
state.Draw();
}
catch (Exception e)
{
Glamourer.Log.Error($"Error during IPC Tests:\n{e}");
}
//ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0);
//ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64);
//ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047);
//ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36);
//DrawItemInput();
//using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
//if (!table)
// return;
//
//ImGuiUtil.DrawTableColumn();
//ImGui.TableNextColumn();
//var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName);
//if (base64 != null)
// ImGuiUtil.CopyOnClickSelectable(base64);
//else
// ImGui.TextUnformatted("Error");
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter);
//ImGui.TableNextColumn();
//base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface)
// .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex));
//if (base64 != null)
// ImGuiUtil.CopyOnClickSelectable(base64);
//else
// ImGui.TextUnformatted("Error");
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter);
//ImGui.TableNextColumn();
//var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface)
// .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
//if (base64Locked != null)
// ImGuiUtil.CopyOnClickSelectable(base64Locked);
//else
// ImGui.TextUnformatted("Error");
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert);
//ImGui.TableNextColumn();
//if (ImGui.Button("Revert##Name"))
// GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Revert##Character"))
// GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##AllName"))
// GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply Once##AllName"))
// GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##AllCharacter"))
// GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface)
// .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply Once##AllCharacter"))
// GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface)
// .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##EquipName"))
// GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##EquipCharacter"))
// GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface)
// .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##CustomizeName"))
// GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##CustomizeCharacter"))
// GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface)
// .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1))
// GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once))
// GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2))
// GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface)
// .Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once))
// GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface)
// .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock);
//ImGui.TableNextColumn();
//if (ImGui.Button("Apply With Lock##CustomizeCharacter"))
// GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface)
// .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock);
//ImGui.TableNextColumn();
//if (ImGui.Button("Unlock##CustomizeCharacter"))
// GlamourerIpc.UnlockSubscriber(_pluginInterface)
// .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll);
//ImGui.TableNextColumn();
//if (ImGui.Button("Unlock All##CustomizeCharacter"))
// GlamourerIpc.UnlockAllSubscriber(_pluginInterface)
// .Invoke(1337);
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation);
//ImGui.TableNextColumn();
//if (ImGui.Button("Revert##CustomizeCharacter"))
// GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface)
// .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
//
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList);
//ImGui.TableNextColumn();
//var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface)
// .Invoke();
//if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList"))
// ImGui.SetClipboardText(string.Join("\n", designList));
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem);
//ImGui.TableNextColumn();
//if (ImGui.Button("Set##SetItem"))
// _setItemEc = (GlamourerApiEc)GlamourerIpc.SetItemSubscriber(_pluginInterface)
// .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
//if (_setItemEc != GlamourerApiEc.Success)
//{
// ImGui.SameLine();
// ImGui.TextUnformatted(_setItemEc.ToString());
//}
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce);
//ImGui.TableNextColumn();
//if (ImGui.Button("Set Once##SetItem"))
// _setItemOnceEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface)
// .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
//if (_setItemOnceEc != GlamourerApiEc.Success)
//{
// ImGui.SameLine();
// ImGui.TextUnformatted(_setItemOnceEc.ToString());
//}
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName);
//ImGui.TableNextColumn();
//if (ImGui.Button("Set##SetItemByActorName"))
// _setItemByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface)
// .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
//if (_setItemByActorNameEc != GlamourerApiEc.Success)
//{
// ImGui.SameLine();
// ImGui.TextUnformatted(_setItemByActorNameEc.ToString());
//}
//
//ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName);
//ImGui.TableNextColumn();
//if (ImGui.Button("Set Once##SetItemByActorName"))
// _setItemOnceByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface)
// .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
//if (_setItemOnceByActorNameEc != GlamourerApiEc.Success)
//{
// ImGui.SameLine();
// ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString());
//}
}
private void Subscribe()
{
if (_subscribed)
return;
Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester.");
state.GPoseChanged.Enable();
state.StateChanged.Enable();
framework.Update += CheckUnsubscribe;
_subscribed = true;
}
private void CheckUnsubscribe(IFramework framework1)
{
if (_lastUpdate > framework.LastUpdateUTC)
return;
Unsubscribe();
framework.Update -= CheckUnsubscribe;
}
private void Unsubscribe()
{
if (!_subscribed)
return;
Glamourer.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester.");
_subscribed = false;
state.GPoseChanged.Disable();
state.StateChanged.Disable();
}
}

View file

@ -0,0 +1,66 @@
using Dalamud.Plugin;
using Glamourer.Api.Enums;
using Glamourer.Api.IpcSubscribers;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService
{
private int _gameObjectIndex;
private string _gameObjectName = string.Empty;
private uint _key;
private ApplyFlag _flags = ApplyFlagEx.DesignDefault;
private CustomItemId _customItemId;
private StainId _stainId;
private EquipSlot _slot = EquipSlot.Head;
private GlamourerApiEc _lastError;
public void Draw()
{
using var tree = ImRaii.TreeNode("Items");
if (!tree)
return;
IpcTesterHelpers.IndexInput(ref _gameObjectIndex);
IpcTesterHelpers.KeyInput(ref _key);
DrawItemInput();
IpcTesterHelpers.NameInput(ref _gameObjectName);
IpcTesterHelpers.DrawFlagInput(ref _flags);
using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit);
IpcTesterHelpers.DrawIntro("Last Error");
ImGui.TextUnformatted(_lastError.ToString());
IpcTesterHelpers.DrawIntro(SetItem.Label);
if (ImGui.Button("Set##Idx"))
_lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, _slot, _customItemId.Id, _stainId.Id, _key, _flags);
IpcTesterHelpers.DrawIntro(SetItemName.Label);
if (ImGui.Button("Set##Name"))
_lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, _slot, _customItemId.Id, _stainId.Id, _key, _flags);
}
private void DrawItemInput()
{
var tmp = _customItemId.Id;
var width = ImGui.GetContentRegionAvail().X / 2;
ImGui.SetNextItemWidth(width);
if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp))
_customItemId = tmp;
EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot, width);
var value = (int)_stainId.Id;
ImGui.SetNextItemWidth(width);
if (ImGui.InputInt("Stain ID", ref value, 1, 3))
{
value = Math.Clamp(value, 0, byte.MaxValue);
_stainId = (StainId)value;
}
}
}

View file

@ -0,0 +1,184 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Glamourer.Api.Enums;
using Glamourer.Api.IpcSubscribers;
using Glamourer.Designs;
using ImGuiNET;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Interop;
using Penumbra.String;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
public class StateIpcTester : IUiService, IDisposable
{
private readonly DalamudPluginInterface _pluginInterface;
private int _gameObjectIndex;
private string _gameObjectName = string.Empty;
private uint _key;
private ApplyFlag _flags = ApplyFlagEx.DesignDefault;
private GlamourerApiEc _lastError;
private JObject? _state;
private string? _stateString;
private string _base64State = string.Empty;
public readonly EventSubscriber<nint> StateChanged;
private nint _lastStateChangeActor;
private ByteString _lastStateChangeName = ByteString.Empty;
private DateTime _lastStateChangeTime;
public readonly EventSubscriber<bool> GPoseChanged;
private bool _lastGPoseChangeValue;
private DateTime _lastGPoseChangeTime;
private int _numUnlocked;
public StateIpcTester(DalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
StateChanged = Api.IpcSubscribers.StateChanged.Subscriber(_pluginInterface, OnStateChanged);
GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange);
}
public void Dispose()
{
StateChanged.Dispose();
GPoseChanged.Dispose();
}
public void Draw()
{
using var tree = ImRaii.TreeNode("State");
if (!tree)
return;
IpcTesterHelpers.IndexInput(ref _gameObjectIndex);
IpcTesterHelpers.KeyInput(ref _key);
IpcTesterHelpers.NameInput(ref _gameObjectName);
IpcTesterHelpers.DrawFlagInput(ref _flags);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.InputTextWithHint("##base64", "Base 64 State...", ref _base64State, 2000);
using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit);
IpcTesterHelpers.DrawIntro("Last Error");
ImGui.TextUnformatted(_lastError.ToString());
IpcTesterHelpers.DrawIntro("Last State Change");
PrintName();
IpcTesterHelpers.DrawIntro("Last GPose Change");
ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}");
IpcTesterHelpers.DrawIntro(GetState.Label);
DrawStatePopup();
if (ImGui.Button("Get##Idx"))
{
(_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key);
_stateString = _state?.ToString(Formatting.Indented) ?? "No State Available";
ImGui.OpenPopup("State");
}
IpcTesterHelpers.DrawIntro(GetStateName.Label);
if (ImGui.Button("Get##Name"))
{
(_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key);
_stateString = _state?.ToString(Formatting.Indented) ?? "No State Available";
ImGui.OpenPopup("State");
}
IpcTesterHelpers.DrawIntro(ApplyState.Label);
if (ImGuiUtil.DrawDisabledButton("Apply Last##Idx", Vector2.Zero, string.Empty, _state == null))
_lastError = new ApplyState(_pluginInterface).Invoke(_state!, _gameObjectIndex, _key, _flags);
ImGui.SameLine();
if (ImGui.Button("Apply Base64##Idx"))
_lastError = new ApplyState(_pluginInterface).Invoke(_base64State, _gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(ApplyStateName.Label);
if (ImGuiUtil.DrawDisabledButton("Apply Last##Name", Vector2.Zero, string.Empty, _state == null))
_lastError = new ApplyStateName(_pluginInterface).Invoke(_state!, _gameObjectName, _key, _flags);
ImGui.SameLine();
if (ImGui.Button("Apply Base64##Name"))
_lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertState.Label);
if (ImGui.Button("Revert##Idx"))
_lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertStateName.Label);
if (ImGui.Button("Revert##Name"))
_lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(UnlockState.Label);
if (ImGui.Button("Unlock##Idx"))
_lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key);
IpcTesterHelpers.DrawIntro(UnlockStateName.Label);
if (ImGui.Button("Unlock##Name"))
_lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key);
IpcTesterHelpers.DrawIntro(UnlockAll.Label);
if (ImGui.Button("Unlock##All"))
_numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key);
ImGui.SameLine();
ImGui.TextUnformatted($"Unlocked {_numUnlocked}");
IpcTesterHelpers.DrawIntro(RevertToAutomation.Label);
if (ImGui.Button("Revert##AutomationIdx"))
_lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label);
if (ImGui.Button("Revert##AutomationName"))
_lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags);
}
private void DrawStatePopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var p = ImRaii.Popup("State");
if (!p)
return;
if (ImGui.Button("Copy to Clipboard"))
ImGui.SetClipboardText(_stateString);
ImGui.SameLine();
if (ImGui.Button("Copy as Base64") && _state != null)
ImGui.SetClipboardText(DesignConverter.ToBase64(_state));
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TextUnformatted(_stateString);
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private unsafe void PrintName()
{
ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length);
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}");
}
ImGui.SameLine();
ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}");
}
private void OnStateChanged(nint actor)
{
_lastStateChangeActor = actor;
_lastStateChangeTime = DateTime.UtcNow;
_lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty;
}
private void OnGPoseChange(bool value)
{
_lastGPoseChangeValue = value;
_lastGPoseChangeTime = DateTime.UtcNow;
}
}

View file

@ -1,244 +0,0 @@
using Dalamud.Plugin;
using Glamourer.Api;
using Glamourer.Interop;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab;
public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer
{
public string Label
=> "IPC Tester";
public bool Disabled
=> false;
private int _gameObjectIndex;
private CustomItemId _customItemId;
private StainId _stainId;
private EquipSlot _slot = EquipSlot.Head;
private string _gameObjectName = string.Empty;
private string _base64Apply = string.Empty;
private string _designIdentifier = string.Empty;
private GlamourerIpc.GlamourerErrorCode _setItemEc;
private GlamourerIpc.GlamourerErrorCode _setItemOnceEc;
private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc;
private GlamourerIpc.GlamourerErrorCode _setItemOnceByActorNameEc;
public void Draw()
{
ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0);
ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64);
ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047);
ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36);
DrawItemInput();
using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table)
return;
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApiVersions);
var (major, minor) = GlamourerIpc.ApiVersionsSubscriber(_pluginInterface).Invoke();
ImGuiUtil.DrawTableColumn($"({major}, {minor})");
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomization);
ImGui.TableNextColumn();
var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName);
if (base64 != null)
ImGuiUtil.CopyOnClickSelectable(base64);
else
ImGui.TextUnformatted("Error");
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter);
ImGui.TableNextColumn();
base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface)
.Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex));
if (base64 != null)
ImGuiUtil.CopyOnClickSelectable(base64);
else
ImGui.TextUnformatted("Error");
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter);
ImGui.TableNextColumn();
var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
if (base64Locked != null)
ImGuiUtil.CopyOnClickSelectable(base64Locked);
else
ImGui.TextUnformatted("Error");
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert);
ImGui.TableNextColumn();
if (ImGui.Button("Revert##Name"))
GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Revert##Character"))
GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##AllName"))
GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce);
ImGui.TableNextColumn();
if (ImGui.Button("Apply Once##AllName"))
GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##AllCharacter"))
GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply Once##AllCharacter"))
GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##EquipName"))
GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##EquipCharacter"))
GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##CustomizeName"))
GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##CustomizeCharacter"))
GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1))
GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce);
ImGui.TableNextColumn();
if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once))
GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2))
GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface)
.Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once))
GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface)
.Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock);
ImGui.TableNextColumn();
if (ImGui.Button("Apply With Lock##CustomizeCharacter"))
GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock);
ImGui.TableNextColumn();
if (ImGui.Button("Unlock##CustomizeCharacter"))
GlamourerIpc.UnlockSubscriber(_pluginInterface)
.Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll);
ImGui.TableNextColumn();
if (ImGui.Button("Unlock All##CustomizeCharacter"))
GlamourerIpc.UnlockAllSubscriber(_pluginInterface)
.Invoke(1337);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation);
ImGui.TableNextColumn();
if (ImGui.Button("Revert##CustomizeCharacter"))
GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface)
.Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList);
ImGui.TableNextColumn();
var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface)
.Invoke();
if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList"))
ImGui.SetClipboardText(string.Join("\n", designList));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem);
ImGui.TableNextColumn();
if (ImGui.Button("Set##SetItem"))
_setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface)
.Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted(_setItemEc.ToString());
}
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce);
ImGui.TableNextColumn();
if (ImGui.Button("Set Once##SetItem"))
_setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface)
.Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
if (_setItemOnceEc != GlamourerIpc.GlamourerErrorCode.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted(_setItemOnceEc.ToString());
}
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName);
ImGui.TableNextColumn();
if (ImGui.Button("Set##SetItemByActorName"))
_setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface)
.Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted(_setItemByActorNameEc.ToString());
}
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName);
ImGui.TableNextColumn();
if (ImGui.Button("Set Once##SetItemByActorName"))
_setItemOnceByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface)
.Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
if (_setItemOnceByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString());
}
}
private void DrawItemInput()
{
var tmp = _customItemId.Id;
if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp))
_customItemId = tmp;
var width = ImGui.GetContentRegionAvail().X;
EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot);
var value = (int)_stainId.Id;
ImGui.SameLine();
width -= ImGui.GetContentRegionAvail().X;
ImGui.SetNextItemWidth(width);
if (ImGui.InputInt("Stain ID", ref value, 1, 3))
{
value = Math.Clamp(value, 0, byte.MaxValue);
_stainId = (StainId)value;
}
}
}

View file

@ -11,24 +11,13 @@ using OtterGui.Raii;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public class ModAssociationsTab public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager)
{ {
private readonly PenumbraService _penumbra; private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log);
private readonly DesignFileSystemSelector _selector;
private readonly DesignManager _manager;
private readonly ModCombo _modCombo;
public ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager)
{
_penumbra = penumbra;
_selector = selector;
_manager = manager;
_modCombo = new ModCombo(penumbra, Glamourer.Log);
}
public void Draw() public void Draw()
{ {
using var h = ImRaii.CollapsingHeader("Mod Associations"); using var h = ImRaii.CollapsingHeader("Mod Associations");
ImGuiUtil.HoverTooltip( ImGuiUtil.HoverTooltip(
"This tab can store information about specific mods associated with this design.\n\n" "This tab can store information about specific mods associated with this design.\n\n"
+ "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n" + "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n"
@ -43,25 +32,25 @@ public class ModAssociationsTab
private void DrawApplyAllButton() private void DrawApplyAllButton()
{ {
var current = _penumbra.CurrentCollection; var (id, name) = penumbra.CurrentCollection;
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {current}##applyAll", if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll",
new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, current is "<Unavailable>")) new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty))
ApplyAll(); ApplyAll();
} }
public void DrawApplyButton() public void DrawApplyButton()
{ {
var current = _penumbra.CurrentCollection; var (id, name) = penumbra.CurrentCollection;
if (ImGuiUtil.DrawDisabledButton("Apply Mod Associations", Vector2.Zero, if (ImGuiUtil.DrawDisabledButton("Apply Mod Associations", Vector2.Zero,
$"Try to apply all associated mod settings to Penumbras current collection {current}", $"Try to apply all associated mod settings to Penumbras current collection {name}",
_selector.Selected!.AssociatedMods.Count == 0 || current is "<Unavailable>")) selector.Selected!.AssociatedMods.Count == 0 || id == Guid.Empty))
ApplyAll(); ApplyAll();
} }
public void ApplyAll() public void ApplyAll()
{ {
foreach (var (mod, settings) in _selector.Selected!.AssociatedMods) foreach (var (mod, settings) in selector.Selected!.AssociatedMods)
_penumbra.SetMod(mod, settings); penumbra.SetMod(mod, settings);
} }
private void DrawTable() private void DrawTable()
@ -79,9 +68,9 @@ public class ModAssociationsTab
ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X); ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
Mod? removedMod = null; Mod? removedMod = null;
(Mod mod, ModSettings settings)? updatedMod = null; (Mod mod, ModSettings settings)? updatedMod = null;
foreach (var ((mod, settings), idx) in _selector.Selected!.AssociatedMods.WithIndex()) foreach (var ((mod, settings), idx) in selector.Selected!.AssociatedMods.WithIndex())
{ {
using var id = ImRaii.PushId(idx); using var id = ImRaii.PushId(idx);
DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp); DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp);
@ -94,10 +83,10 @@ public class ModAssociationsTab
DrawNewModRow(); DrawNewModRow();
if (removedMod.HasValue) if (removedMod.HasValue)
_manager.RemoveMod(_selector.Selected!, removedMod.Value); manager.RemoveMod(selector.Selected!, removedMod.Value);
if (updatedMod.HasValue) if (updatedMod.HasValue)
_manager.UpdateMod(_selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); manager.UpdateMod(selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings);
} }
private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod, out (Mod, ModSettings)? updatedMod) private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod, out (Mod, ModSettings)? updatedMod)
@ -112,15 +101,15 @@ public class ModAssociationsTab
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), new Vector2(ImGui.GetFrameHeight()), ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
"Update the settings of this mod association", false, true); "Update the settings of this mod association", false, true);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
var newSettings = _penumbra.GetModSettings(mod); var newSettings = penumbra.GetModSettings(mod);
if (ImGui.IsItemClicked()) if (ImGui.IsItemClicked())
updatedMod = (mod, newSettings); updatedMod = (mod, newSettings);
using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale);
using var tt = ImRaii.Tooltip(); using var tt = ImRaii.Tooltip();
ImGui.Separator(); ImGui.Separator();
var namesDifferent = mod.Name != mod.DirectoryName; var namesDifferent = mod.Name != mod.DirectoryName;
ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0));
@ -143,7 +132,7 @@ public class ModAssociationsTab
ModCombo.DrawSettingsRight(newSettings); ModCombo.DrawSettingsRight(newSettings);
} }
} }
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var selected = ImGui.Selectable($"{mod.Name}##name"); var selected = ImGui.Selectable($"{mod.Name}##name");
var hovered = ImGui.IsItemHovered(); var hovered = ImGui.IsItemHovered();
@ -151,7 +140,7 @@ public class ModAssociationsTab
selected |= ImGui.Selectable($"{mod.DirectoryName}##directory"); selected |= ImGui.Selectable($"{mod.DirectoryName}##directory");
hovered |= ImGui.IsItemHovered(); hovered |= ImGui.IsItemHovered();
if (selected) if (selected)
_penumbra.OpenModPage(mod); penumbra.OpenModPage(mod);
if (hovered) if (hovered)
ImGui.SetTooltip("Click to open mod page in Penumbra."); ImGui.SetTooltip("Click to open mod page in Penumbra.");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -164,9 +153,9 @@ public class ModAssociationsTab
ImGuiUtil.RightAlign(settings.Priority.ToString()); ImGuiUtil.RightAlign(settings.Priority.ToString());
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty,
!_penumbra.Available)) !penumbra.Available))
{ {
var text = _penumbra.SetMod(mod, settings); var text = penumbra.SetMod(mod, settings);
if (text.Length > 0) if (text.Length > 0)
Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false);
} }
@ -202,13 +191,13 @@ public class ModAssociationsTab
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var tt = currentName.IsNullOrEmpty() var tt = currentName.IsNullOrEmpty()
? "Please select a mod first." ? "Please select a mod first."
: _selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod)
? "The design already contains an association with the selected mod." ? "The design already contains an association with the selected mod."
: string.Empty; : string.Empty;
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, tt.Length > 0, if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, tt.Length > 0,
true)) true))
_manager.AddMod(_selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
_modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty, _modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty,
ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight()); ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight());

View file

@ -0,0 +1,36 @@
using Dalamud.Interface;
using Glamourer.Interop.Penumbra;
using ImGuiNET;
using OtterGui;
using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.SettingsTab;
public sealed class CollectionCombo(Configuration config, PenumbraService penumbra, Logger log)
: FilterComboCache<(Guid Id, string IdShort, string Name)>(
() => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(),
MouseWheelType.Control, log), IUiService
{
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var (_, idShort, name) = Items[globalIdx];
if (config.Ephemeral.IncognitoMode)
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
return ImGui.Selectable(idShort);
}
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
ImGuiUtil.RightAlign($"({idShort})");
}
return ret;
}
}

View file

@ -1,13 +1,12 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Components; using Glamourer.Interop.Penumbra;
using Dalamud.Interface.Style;
using Glamourer.Interop;
using Glamourer.Services; using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using ObjectManager = Glamourer.Interop.ObjectManager;
namespace Glamourer.Gui.Tabs.SettingsTab; namespace Glamourer.Gui.Tabs.SettingsTab;
@ -15,13 +14,14 @@ public class CollectionOverrideDrawer(
CollectionOverrideService collectionOverrides, CollectionOverrideService collectionOverrides,
Configuration config, Configuration config,
ObjectManager objects, ObjectManager objects,
ActorManager actors) : IService ActorManager actors,
PenumbraService penumbra,
CollectionCombo combo) : IService
{ {
private string _newIdentifier = string.Empty; private string _newIdentifier = string.Empty;
private ActorIdentifier[] _identifiers = []; private ActorIdentifier[] _identifiers = [];
private int _dragDropIndex = -1; private int _dragDropIndex = -1;
private Exception? _exception; private Exception? _exception;
private string _collection = string.Empty;
public void Draw() public void Draw()
{ {
@ -32,58 +32,113 @@ public class CollectionOverrideDrawer(
if (!header) if (!header)
return; return;
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg); using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg);
if (!table) if (!table)
return; return;
var buttonSize = new Vector2(ImGui.GetFrameHeight()); var buttonSize = new Vector2(ImGui.GetFrameHeight());
ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X);
ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.6f); ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.35f);
ImGui.TableSetupColumn("collections", ImGuiTableColumnFlags.WidthStretch, 0.4f); ImGui.TableSetupColumn("collections", ImGuiTableColumnFlags.WidthStretch, 0.4f);
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.25f);
for (var i = 0; i < collectionOverrides.Overrides.Count; ++i) for (var i = 0; i < collectionOverrides.Overrides.Count; ++i)
DrawCollectionRow(ref i, buttonSize);
DrawNewOverride(buttonSize);
}
private void DrawCollectionRow(ref int idx, Vector2 buttonSize)
{
using var id = ImRaii.PushId(idx);
var (exists, actor, collection, name) = collectionOverrides.Fetch(idx);
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true))
collectionOverrides.DeleteOverride(idx--);
ImGui.TableNextColumn();
DrawActorIdentifier(idx, actor);
ImGui.TableNextColumn();
if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight()))
{ {
var (identifier, collection) = collectionOverrides.Overrides[i]; var (guid, _, newName) = combo.CurrentSelection;
using var id = ImRaii.PushId(i); collectionOverrides.ChangeOverride(idx, guid, newName);
ImGui.TableNextColumn(); }
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true))
collectionOverrides.DeleteOverride(i--);
ImGui.TableNextColumn(); if (ImGui.IsItemHovered())
ImGui.Selectable(config.Ephemeral.IncognitoMode ? identifier.Incognito(null) : identifier.ToString()); {
using var tt = ImRaii.Tooltip();
using (var target = ImRaii.DragDropTarget()) using var font = ImRaii.PushFont(UiBuilder.MonoFont);
{ ImGui.TextUnformatted($" {collection}");
if (target.Success && ImGuiUtil.IsDropping("DraggingOverride"))
{
collectionOverrides.MoveOverride(_dragDropIndex, i);
_dragDropIndex = -1;
}
}
using (var source = ImRaii.DragDropSource())
{
if (source)
{
ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0);
ImGui.TextUnformatted($"Reordering Override #{i + 1}...");
_dragDropIndex = i;
}
}
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputText("##input", ref collection, 64) && collection.Length > 0)
collectionOverrides.ChangeOverride(i, collection);
} }
ImGui.TableNextColumn();
DrawCollectionName(exists, collection, name);
}
private void DrawCollectionName(bool exists, Guid collection, string name)
{
if (!exists)
{
ImGui.TextUnformatted("<Does not Exist>");
if (!ImGui.IsItemHovered())
return;
using var tt1 = ImRaii.Tooltip();
ImGui.TextUnformatted($"The design {name} with the GUID");
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.TextUnformatted($" {collection}");
}
ImGui.TextUnformatted("does not exist in Penumbra.");
return;
}
ImGui.TextUnformatted(config.Ephemeral.IncognitoMode ? collection.ToString()[..8] : name);
if (!ImGui.IsItemHovered())
return;
using var tt2 = ImRaii.Tooltip();
using var f = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TextUnformatted(collection.ToString());
}
private void DrawActorIdentifier(int idx, ActorIdentifier actor)
{
ImGui.Selectable(config.Ephemeral.IncognitoMode ? actor.Incognito(null) : actor.ToString());
using (var target = ImRaii.DragDropTarget())
{
if (target.Success && ImGuiUtil.IsDropping("DraggingOverride"))
{
collectionOverrides.MoveOverride(_dragDropIndex, idx);
_dragDropIndex = -1;
}
}
using (var source = ImRaii.DragDropSource())
{
if (source)
{
ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0);
ImGui.TextUnformatted($"Reordering Override #{idx + 1}...");
_dragDropIndex = idx;
}
}
}
private void DrawNewOverride(Vector2 buttonSize)
{
var (currentId, currentName) = penumbra.CurrentCollection;
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PersonCirclePlus.ToIconString(), buttonSize, "Add override for current player.", if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PersonCirclePlus.ToIconString(), buttonSize, "Add override for current player.",
!objects.Player.Valid, true)) !objects.Player.Valid && currentId != Guid.Empty, true))
collectionOverrides.AddOverride([objects.PlayerData.Identifier], _collection.Length > 0 ? _collection : "TempCollection"); collectionOverrides.AddOverride([objects.PlayerData.Identifier], currentId, currentName);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X - buttonSize.X * 2); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputTextWithHint("##newActor", "New Identifier...", ref _newIdentifier, 80)) if (ImGui.InputTextWithHint("##newActor", "New Identifier...", ref _newIdentifier, 80))
try try
{ {
@ -91,7 +146,7 @@ public class CollectionOverrideDrawer(
} }
catch (ActorIdentifierFactory.IdentifierParseError e) catch (ActorIdentifierFactory.IdentifierParseError e)
{ {
_exception = e; _exception = e;
_identifiers = []; _identifiers = [];
} }
@ -101,9 +156,9 @@ public class CollectionOverrideDrawer(
? "Please enter an identifier string first." ? "Please enter an identifier string first."
: $"The identifier string {_newIdentifier} does not result in a valid identifier{(_exception == null ? "." : $":\n\n{_exception?.Message}")}"; : $"The identifier string {_newIdentifier} does not result in a valid identifier{(_exception == null ? "." : $":\n\n{_exception?.Message}")}";
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is 'T', true)) if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is not 'A', true))
collectionOverrides.AddOverride(_identifiers, _collection.Length > 0 ? _collection : "TempCollection"); collectionOverrides.AddOverride(_identifiers, currentId, currentName);
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
using (ImRaii.PushFont(UiBuilder.IconFont)) using (ImRaii.PushFont(UiBuilder.IconFont))
@ -114,9 +169,5 @@ public class CollectionOverrideDrawer(
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ActorIdentifierFactory.WriteUserStringTooltip(false); ActorIdentifierFactory.WriteUserStringTooltip(false);
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.InputTextWithHint("##collection", "Enter Collection...", ref _collection, 80);
} }
} }

View file

@ -22,16 +22,13 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
return; return;
} }
var collections = new HashSet<string>(); var collections = new HashSet<Guid>();
foreach (var actor in data.Objects) foreach (var actor in data.Objects)
{ {
var (collection, overridden) = overrides.GetCollection(actor, state.Identifier); var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier);
if (collection.Length == 0) if (collection == Guid.Empty)
{
Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}.");
continue; continue;
}
if (!collections.Add(collection)) if (!collections.Add(collection))
continue; continue;
@ -48,11 +45,11 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
} }
} }
public (List<string> Messages, int Applied, string Collection, bool Overridden) ApplyModSettings(IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor) public (List<string> Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings(IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor)
{ {
var (collection, overridden) = overrides.GetCollection(actor); var (collection, name, overridden) = overrides.GetCollection(actor);
if (collection.Length <= 0) if (collection == Guid.Empty)
return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty, false); return ([$"{actor.Utf8Name} uses no mods."], 0, Guid.Empty, string.Empty, false);
var messages = new List<string>(); var messages = new List<string>();
var appliedMods = 0; var appliedMods = 0;
@ -65,6 +62,6 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
++appliedMods; ++appliedMods;
} }
return (messages, appliedMods, collection, overridden); return (messages, appliedMods, collection, name, overridden);
} }
} }

View file

@ -67,7 +67,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
} }
} }
private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) private void OnModSettingChange(ModSettingChange type, Guid collectionId, string mod, bool inherited)
{ {
if (type is ModSettingChange.TemporaryMod) if (type is ModSettingChange.TemporaryMod)
{ {
@ -79,8 +79,8 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) if (!_objects.TryGetValue(id, out var actors) || !actors.Valid)
continue; continue;
var collection = _penumbra.GetActorCollection(actors.Objects[0]); var collection = _penumbra.GetActorCollection(actors.Objects[0], out _);
if (collection != name) if (collection != collectionId)
continue; continue;
_actions.Enqueue((state, () => _actions.Enqueue((state, () =>
@ -96,7 +96,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
{ {
// Only update once per frame. // Only update once per frame.
var playerName = _penumbra.GetCurrentPlayerCollection(); var playerName = _penumbra.GetCurrentPlayerCollection();
if (playerName != name) if (playerName != collectionId)
return; return;
var currentFrame = _framework.LastUpdateUTC; var currentFrame = _framework.LastUpdateUTC;

View file

@ -2,7 +2,6 @@
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Events; using Glamourer.Events;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.Api;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Api.Helpers; using Penumbra.Api.Helpers;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
@ -10,8 +9,6 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Penumbra; namespace Glamourer.Interop.Penumbra;
using CurrentSettings = ValueTuple<PenumbraApiEc, (bool, int, IDictionary<string, IList<string>>, bool)?>;
public readonly record struct Mod(string Name, string DirectoryName) : IComparable<Mod> public readonly record struct Mod(string Name, string DirectoryName) : IComparable<Mod>
{ {
public int CompareTo(Mod other) public int CompareTo(Mod other)
@ -24,10 +21,10 @@ public readonly record struct Mod(string Name, string DirectoryName) : IComparab
} }
} }
public readonly record struct ModSettings(IDictionary<string, IList<string>> Settings, int Priority, bool Enabled) public readonly record struct ModSettings(Dictionary<string, List<string>> Settings, int Priority, bool Enabled)
{ {
public ModSettings() public ModSettings()
: this(new Dictionary<string, IList<string>>(), 0, false) : this(new Dictionary<string, List<string>>(), 0, false)
{ } { }
public static ModSettings Empty public static ModSettings Empty
@ -36,30 +33,33 @@ public readonly record struct ModSettings(IDictionary<string, IList<string>> Set
public unsafe class PenumbraService : IDisposable public unsafe class PenumbraService : IDisposable
{ {
public const int RequiredPenumbraBreakingVersion = 4; public const int RequiredPenumbraBreakingVersion = 5;
public const int RequiredPenumbraFeatureVersion = 15; public const int RequiredPenumbraFeatureVersion = 0;
private readonly DalamudPluginInterface _pluginInterface; private readonly DalamudPluginInterface _pluginInterface;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber; private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber; private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase; private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase;
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase; private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase;
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged; private readonly EventSubscriber<ModSettingChange, Guid, 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 FuncSubscriber<IList<(string, string)>> _getMods;
private FuncSubscriber<ApiCollectionType, string> _currentCollection;
private FuncSubscriber<string, string, string, bool, CurrentSettings> _getCurrentSettings;
private FuncSubscriber<string, string, string, bool, PenumbraApiEc> _setMod;
private FuncSubscriber<string, string, string, int, PenumbraApiEc> _setModPriority;
private FuncSubscriber<string, string, string, string, string, PenumbraApiEc> _setModSetting;
private FuncSubscriber<string, string, string, string, IReadOnlyList<string>, PenumbraApiEc> _setModSettings;
private FuncSubscriber<TabType, string, string, PenumbraApiEc> _openModPage;
private readonly EventSubscriber _initializedEvent; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier;
private readonly EventSubscriber _disposedEvent; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections;
private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw;
private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo;
private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent;
private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection;
private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods;
private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection;
private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings;
private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod;
private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority;
private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting;
private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings;
private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage;
private readonly IDisposable _initializedEvent;
private readonly IDisposable _disposedEvent;
private readonly PenumbraReloaded _penumbraReloaded; private readonly PenumbraReloaded _penumbraReloaded;
@ -69,13 +69,13 @@ public unsafe class PenumbraService : IDisposable
{ {
_pluginInterface = pi; _pluginInterface = pi;
_penumbraReloaded = penumbraReloaded; _penumbraReloaded = penumbraReloaded;
_initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach); _initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach);
_disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach); _disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach);
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi); _tooltipSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemTooltip.Subscriber(pi);
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi); _clickSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemClicked.Subscriber(pi);
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi); _createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi);
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi); _creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi);
_modSettingChanged = Ipc.ModSettingChanged.Subscriber(pi); _modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi);
Reattach(); Reattach();
} }
@ -92,24 +92,27 @@ public unsafe class PenumbraService : IDisposable
} }
public event Action<nint, string, nint, nint, nint> CreatingCharacterBase public event Action<nint, Guid, nint, nint, nint> CreatingCharacterBase
{ {
add => _creatingCharacterBase.Event += value; add => _creatingCharacterBase.Event += value;
remove => _creatingCharacterBase.Event -= value; remove => _creatingCharacterBase.Event -= value;
} }
public event Action<nint, string, nint> CreatedCharacterBase public event Action<nint, Guid, nint> CreatedCharacterBase
{ {
add => _createdCharacterBase.Event += value; add => _createdCharacterBase.Event += value;
remove => _createdCharacterBase.Event -= value; remove => _createdCharacterBase.Event -= value;
} }
public event Action<ModSettingChange, string, string, bool> ModSettingChanged public event Action<ModSettingChange, Guid, string, bool> ModSettingChanged
{ {
add => _modSettingChanged.Event += value; add => _modSettingChanged.Event += value;
remove => _modSettingChanged.Event -= value; remove => _modSettingChanged.Event -= value;
} }
public Dictionary<Guid, string> GetCollections()
=> Available ? _collections!.Invoke() : [];
public ModSettings GetModSettings(in Mod mod) public ModSettings GetModSettings(in Mod mod)
{ {
if (!Available) if (!Available)
@ -117,8 +120,8 @@ public unsafe class PenumbraService : IDisposable
try try
{ {
var collection = _currentCollection.Invoke(ApiCollectionType.Current); var collection = _currentCollection!.Invoke(ApiCollectionType.Current);
var (ec, tuple) = _getCurrentSettings.Invoke(collection, mod.DirectoryName, string.Empty, false); var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName);
if (ec is not PenumbraApiEc.Success) if (ec is not PenumbraApiEc.Success)
return ModSettings.Empty; return ModSettings.Empty;
@ -131,6 +134,18 @@ public unsafe class PenumbraService : IDisposable
} }
} }
public (Guid Id, string Name)? CollectionByIdentifier(string identifier)
{
if (!Available)
return null;
var ret = _collectionByIdentifier!.Invoke(identifier);
if (ret.Count == 0)
return null;
return ret[0];
}
public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods() public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods()
{ {
if (!Available) if (!Available)
@ -138,10 +153,10 @@ public unsafe class PenumbraService : IDisposable
try try
{ {
var allMods = _getMods.Invoke(); var allMods = _getMods!.Invoke();
var collection = _currentCollection.Invoke(ApiCollectionType.Current); var collection = _currentCollection!.Invoke(ApiCollectionType.Current);
return allMods return allMods
.Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, false))) .Select(m => (m.Key, m.Value, _getCurrentSettings!.Invoke(collection!.Value.Id, m.Key)))
.Where(t => t.Item3.Item1 is PenumbraApiEc.Success) .Where(t => t.Item3.Item1 is PenumbraApiEc.Success)
.Select(t => (new Mod(t.Item2, t.Item1), .Select(t => (new Mod(t.Item2, t.Item1),
!t.Item3.Item2.HasValue !t.Item3.Item2.HasValue
@ -162,19 +177,22 @@ public unsafe class PenumbraService : IDisposable
public void OpenModPage(Mod mod) public void OpenModPage(Mod mod)
{ {
if (_openModPage.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) if (!Available)
return;
if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName) == PenumbraApiEc.ModMissing)
Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.",
NotificationType.Info, false); NotificationType.Info, false);
} }
public string CurrentCollection public (Guid Id, string Name) CurrentCollection
=> Available ? _currentCollection.Invoke(ApiCollectionType.Current) : "<Unavailable>"; => Available ? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value : (Guid.Empty, "<Unavailable>");
/// <summary> /// <summary>
/// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// Try to set all mod settings as desired. Only sets when the mod should be enabled.
/// If it is disabled, ignore all other settings. /// If it is disabled, ignore all other settings.
/// </summary> /// </summary>
public string SetMod(Mod mod, ModSettings settings, string? collection = null) public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null)
{ {
if (!Available) if (!Available)
return "Penumbra is not available."; return "Penumbra is not available.";
@ -182,8 +200,8 @@ public unsafe class PenumbraService : IDisposable
var sb = new StringBuilder(); var sb = new StringBuilder();
try try
{ {
collection ??= _currentCollection.Invoke(ApiCollectionType.Current); var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id;
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); var ec = _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled);
switch (ec) switch (ec)
{ {
case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found.";
@ -193,14 +211,14 @@ public unsafe class PenumbraService : IDisposable
if (!settings.Enabled) if (!settings.Enabled)
return string.Empty; return string.Empty;
ec = _setModPriority.Invoke(collection, mod.DirectoryName, mod.Name, settings.Priority); ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority);
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail."); Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail.");
foreach (var (setting, list) in settings.Settings) foreach (var (setting, list) in settings.Settings)
{ {
ec = list.Count == 1 ec = list.Count == 1
? _setModSetting.Invoke(collection, mod.DirectoryName, mod.Name, setting, list[0]) ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0])
: _setModSettings.Invoke(collection, mod.DirectoryName, mod.Name, setting, (IReadOnlyList<string>)list); : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list);
switch (ec) switch (ec)
{ {
case PenumbraApiEc.OptionGroupMissing: case PenumbraApiEc.OptionGroupMissing:
@ -227,55 +245,54 @@ public unsafe class PenumbraService : IDisposable
} }
/// <summary> Obtain the name of the collection currently assigned to the player. </summary> /// <summary> Obtain the name of the collection currently assigned to the player. </summary>
public string GetCurrentPlayerCollection() public Guid GetCurrentPlayerCollection()
{ {
if (!Available) if (!Available)
return string.Empty; return Guid.Empty;
var (valid, _, name) = _objectCollection.Invoke(0); var (valid, _, (id, _)) = _objectCollection!.Invoke(0);
return valid ? name : string.Empty; return valid ? id : Guid.Empty;
} }
/// <summary> Obtain the name of the collection currently assigned to the given actor. </summary> /// <summary> Obtain the name of the collection currently assigned to the given actor. </summary>
public string GetActorCollection(Actor actor) public Guid GetActorCollection(Actor actor, out string name)
{ {
if (!Available) if (!Available)
return string.Empty; {
name = string.Empty;
return Guid.Empty;
}
var (valid, _, name) = _objectCollection.Invoke(actor.Index.Index); (var valid, _, (var id, name)) = _objectCollection!.Invoke(actor.Index.Index);
return valid ? name : string.Empty; return valid ? id : Guid.Empty;
} }
/// <summary> Obtain the game object corresponding to a draw object. </summary> /// <summary> Obtain the game object corresponding to a draw object. </summary>
public Actor GameObjectFromDrawObject(Model drawObject) public Actor GameObjectFromDrawObject(Model drawObject)
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null; => Available ? _drawObjectInfo!.Invoke(drawObject.Address).Item1 : Actor.Null;
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary> /// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
public short CutsceneParent(ushort idx) public short CutsceneParent(ushort idx)
=> (short)(Available ? _cutsceneParent.Invoke(idx) : -1); => (short)(Available ? _cutsceneParent!.Invoke(idx) : -1);
/// <summary> Try to redraw the given actor. </summary> /// <summary> Try to redraw the given actor. </summary>
public void RedrawObject(Actor actor, RedrawType settings) public void RedrawObject(Actor actor, RedrawType settings)
{ {
if (!actor || !Available) if (!actor)
return; return;
try RedrawObject(actor.Index, settings);
{
_redrawSubscriber.Invoke(actor.AsObject->ObjectIndex, settings);
}
catch (Exception e)
{
Glamourer.Log.Debug($"Failure redrawing object:\n{e}");
}
} }
/// <summary> Try to redraw the given actor. </summary> /// <summary> Try to redraw the given actor. </summary>
public void RedrawObject(ObjectIndex index, RedrawType settings) public void RedrawObject(ObjectIndex index, RedrawType settings)
{ {
if (!Available)
return;
try try
{ {
_redrawSubscriber.Invoke(index.Index, settings); _redraw!.Invoke(index.Index, settings);
} }
catch (Exception e) catch (Exception e)
{ {
@ -290,7 +307,7 @@ public unsafe class PenumbraService : IDisposable
{ {
Unattach(); Unattach();
var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke(); var (breaking, feature) = new global::Penumbra.Api.IpcSubscribers.ApiVersion(_pluginInterface).Invoke();
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion) if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
throw new Exception( throw new Exception(
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
@ -300,24 +317,27 @@ public unsafe class PenumbraService : IDisposable
_creatingCharacterBase.Enable(); _creatingCharacterBase.Enable();
_createdCharacterBase.Enable(); _createdCharacterBase.Enable();
_modSettingChanged.Enable(); _modSettingChanged.Enable();
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface); _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface);
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface); _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface);
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface); _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface);
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface); _drawObjectInfo = new global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo(_pluginInterface);
_getMods = Ipc.GetMods.Subscriber(_pluginInterface); _cutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex(_pluginInterface);
_currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface); _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface);
_getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface); _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface);
_setMod = Ipc.TrySetMod.Subscriber(_pluginInterface); _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface);
_setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface); _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface);
_setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface); _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface);
_setModSettings = Ipc.TrySetModSettings.Subscriber(_pluginInterface); _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface);
_openModPage = Ipc.OpenMainWindow.Subscriber(_pluginInterface); _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface);
Available = true; _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface);
_openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface);
Available = true;
_penumbraReloaded.Invoke(); _penumbraReloaded.Invoke();
Glamourer.Log.Debug("Glamourer attached to Penumbra."); Glamourer.Log.Debug("Glamourer attached to Penumbra.");
} }
catch (Exception e) catch (Exception e)
{ {
Unattach();
Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}"); Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}");
} }
} }
@ -332,7 +352,21 @@ public unsafe class PenumbraService : IDisposable
_modSettingChanged.Disable(); _modSettingChanged.Disable();
if (Available) if (Available)
{ {
Available = false; _collectionByIdentifier = null;
_collections = null;
_redraw = null;
_drawObjectInfo = null;
_cutsceneParent = null;
_objectCollection = null;
_getMods = null;
_currentCollection = null;
_getCurrentSettings = null;
_setMod = null;
_setModPriority = null;
_setModSetting = null;
_setModSettings = null;
_openModPage = null;
Available = false;
Glamourer.Log.Debug("Glamourer detached from Penumbra."); Glamourer.Log.Debug("Glamourer detached from Penumbra.");
} }
} }

View file

@ -1,7 +1,9 @@
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
@ -11,7 +13,7 @@ namespace Glamourer.Services;
public sealed class CollectionOverrideService : IService, ISavable public sealed class CollectionOverrideService : IService, ISavable
{ {
public const int Version = 1; public const int Version = 2;
private readonly SaveService _saveService; private readonly SaveService _saveService;
private readonly ActorManager _actors; private readonly ActorManager _actors;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
@ -24,48 +26,71 @@ public sealed class CollectionOverrideService : IService, ISavable
Load(); Load();
} }
public unsafe (string Collection, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default) public unsafe (Guid CollectionId, string Display, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default)
{ {
if (!identifier.IsValid) if (!identifier.IsValid)
identifier = _actors.FromObject(actor.AsObject, out _, true, true, true); identifier = _actors.FromObject(actor.AsObject, out _, true, true, true);
return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret) return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret)
? (ret.Collection, true) ? (ret.CollectionId, ret.DisplayName, true)
: (_penumbra.GetActorCollection(actor), false); : (_penumbra.GetActorCollection(actor, out var name), name, false);
} }
private readonly List<(ActorIdentifier Actor, string Collection)> _overrides = []; private readonly List<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> _overrides = [];
public IReadOnlyList<(ActorIdentifier Actor, string Collection)> Overrides public IReadOnlyList<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> Overrides
=> _overrides; => _overrides;
public string ToFilename(FilenameService fileNames) public string ToFilename(FilenameService fileNames)
=> fileNames.CollectionOverrideFile; => fileNames.CollectionOverrideFile;
public void AddOverride(IEnumerable<ActorIdentifier> identifiers, string collection) public void AddOverride(IEnumerable<ActorIdentifier> identifiers, Guid collectionId, string displayName)
{ {
if (collection.Length == 0) if (collectionId == Guid.Empty)
return; return;
foreach (var id in identifiers.Where(i => i.IsValid)) foreach (var id in identifiers.Where(i => i.IsValid))
{ {
_overrides.Add((id, collection)); _overrides.Add((id, collectionId, displayName));
Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collection}."); Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collectionId}.");
_saveService.QueueSave(this); _saveService.QueueSave(this);
} }
} }
public void ChangeOverride(int idx, string newCollection) public (bool Exists, ActorIdentifier Identifier, Guid CollectionId, string DisplayName) Fetch(int idx)
{ {
if (idx < 0 || idx >= _overrides.Count || newCollection.Length == 0) var (identifier, id, name) = _overrides[idx];
var collection = _penumbra.CollectionByIdentifier(id.ToString());
if (collection == null)
return (false, identifier, id, name);
if (collection.Value.Name == name)
return (true, identifier, id, name);
_overrides[idx] = (identifier, id, collection.Value.Name);
Glamourer.Log.Debug($"Updated display name of collection override {idx + 1} ({id}).");
_saveService.QueueSave(this);
return (true, identifier, id, collection.Value.Name);
}
public void ChangeOverride(int idx, Guid newCollectionId, string newDisplayName)
{
if (idx < 0 || idx >= _overrides.Count)
return;
if (newCollectionId == Guid.Empty || newDisplayName.Length == 0)
return; return;
var current = _overrides[idx]; var current = _overrides[idx];
if (current.Collection == newCollection) if (current.CollectionId == newCollectionId)
return; return;
_overrides[idx] = current with { Collection = newCollection }; _overrides[idx] = current with
Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.Collection} to {newCollection}."); {
CollectionId = newCollectionId,
DisplayName = newDisplayName,
};
Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.CollectionId} to {newCollectionId}.");
_saveService.QueueSave(this); _saveService.QueueSave(this);
} }
@ -102,6 +127,7 @@ public sealed class CollectionOverrideService : IService, ISavable
switch (version) switch (version)
{ {
case 1: case 1:
case 2:
if (jObj["Overrides"] is not JArray array) if (jObj["Overrides"] is not JArray array)
{ {
Glamourer.Log.Error($"Invalid format of collection override file, ignored."); Glamourer.Log.Error($"Invalid format of collection override file, ignored.");
@ -110,17 +136,50 @@ public sealed class CollectionOverrideService : IService, ISavable
foreach (var token in array.OfType<JObject>()) foreach (var token in array.OfType<JObject>())
{ {
var collection = token["Collection"]?.ToObject<string>() ?? string.Empty; var collectionIdentifier = token["Collection"]?.ToObject<string>() ?? string.Empty;
var identifier = _actors.FromJson(token); var identifier = _actors.FromJson(token);
var displayName = token["DisplayName"]?.ToObject<string>() ?? collectionIdentifier;
if (!identifier.IsValid) if (!identifier.IsValid)
Glamourer.Log.Warning($"Invalid identifier for collection override with collection [{collection}], skipped."); {
else if (collection.Length == 0) Glamourer.Log.Warning(
Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped."); $"Invalid identifier for collection override with collection [{token["Collection"]}], skipped.");
else continue;
_overrides.Add((identifier, collection)); }
if (!Guid.TryParse(collectionIdentifier, out var collectionId))
{
if (collectionIdentifier.Length == 0)
{
Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped.");
continue;
}
if (version >= 2)
{
Glamourer.Log.Warning(
$"Invalid collection override {collectionIdentifier} for identifier {identifier.Incognito(null)}, skipped.");
continue;
}
var collection = _penumbra.CollectionByIdentifier(collectionIdentifier);
if (collection == null)
{
Glamourer.Messager.AddMessage(new Notification(
$"The overridden collection for identifier {identifier.Incognito(null)} with name {collectionIdentifier} could not be found by Penumbra for migration.",
NotificationType.Warning));
continue;
}
Glamourer.Log.Information($"Migrated collection {collectionIdentifier} to {collection.Value.Id}.");
collectionId = collection.Value.Id;
displayName = collection.Value.Name;
}
_overrides.Add((identifier, collectionId, displayName));
} }
break; break;
default: default:
Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored."); Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored.");
return; return;
@ -147,10 +206,11 @@ public sealed class CollectionOverrideService : IService, ISavable
JArray SerializeOverrides() JArray SerializeOverrides()
{ {
var jArray = new JArray(); var jArray = new JArray();
foreach (var (actor, collection) in _overrides) foreach (var (actor, collection, displayName) in _overrides)
{ {
var obj = actor.ToJson(); var obj = actor.ToJson();
obj["Collection"] = collection; obj["Collection"] = collection;
obj["DisplayName"] = displayName;
jArray.Add(obj); jArray.Add(obj);
} }

View file

@ -10,6 +10,7 @@ using Glamourer.State;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
@ -18,7 +19,7 @@ using ObjectManager = Glamourer.Interop.ObjectManager;
namespace Glamourer.Services; namespace Glamourer.Services;
public class CommandService : IDisposable public class CommandService : IDisposable, IApiService
{ {
private const string RandomString = "random"; private const string RandomString = "random";
private const string MainCommandString = "/glamourer"; private const string MainCommandString = "/glamourer";
@ -118,7 +119,7 @@ public class CommandService : IDisposable
"apply" => Apply(argument), "apply" => Apply(argument),
"reapply" => ReapplyState(argument), "reapply" => ReapplyState(argument),
"revert" => Revert(argument), "revert" => Revert(argument),
"reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false),
"reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true), "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true),
"automation" => SetAutomation(argument), "automation" => SetAutomation(argument),
"copy" => CopyState(argument), "copy" => CopyState(argument),
@ -534,14 +535,14 @@ public class CommandService : IDisposable
if (!applyMods || design is not Design d) if (!applyMods || design is not Design d)
return; return;
var (messages, appliedMods, collection, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); var (messages, appliedMods, collection, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor);
foreach (var message in messages) foreach (var message in messages)
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
if (appliedMods > 0) if (appliedMods > 0)
Glamourer.Messager.Chat.Print( Glamourer.Messager.Chat.Print(
$"Applied {appliedMods} mod settings to {collection}{(overridden ? " (overridden by settings)" : string.Empty)}."); $"Applied {appliedMods} mod settings to {name}{(overridden ? " (overridden by settings)" : string.Empty)}.");
} }
private bool Delete(string argument) private bool Delete(string argument)

View file

@ -65,20 +65,26 @@ public class ItemManager
if (itemId == SmallclothesId(slot)) if (itemId == SmallclothesId(slot))
return SmallClothesItem(slot); return SmallClothesItem(slot);
if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item)) if (!itemId.IsItem)
{ {
item = EquipItem.FromId(itemId); var item = EquipItem.FromId(itemId);
item = slot is EquipSlot.MainHand or EquipSlot.OffHand item = slot is EquipSlot.MainHand or EquipSlot.OffHand
? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant) ? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant)
: Identify(slot, item.PrimaryId, item.Variant); : Identify(slot, item.PrimaryId, item.Variant);
return item; return item;
} }
else if (!ItemData.TryGetValue(itemId.Item, slot, out var item))
{
return EquipItem.FromId(itemId);
}
else
{
if (item.Type.ToSlot() != slot)
return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant,
0, 0, 0, 0);
if (item.Type.ToSlot() != slot) return item;
return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, }
0, 0, 0, 0);
return item;
} }
public EquipItem Resolve(FullEquipType type, ItemId itemId) public EquipItem Resolve(FullEquipType type, ItemId itemId)

View file

@ -1,5 +1,6 @@
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Api; using Glamourer.Api;
using Glamourer.Api.Api;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
@ -43,12 +44,12 @@ public static class StaticServiceManager
.AddData() .AddData()
.AddDesigns() .AddDesigns()
.AddState() .AddState()
.AddUi() .AddUi();
.AddApi();
DalamudServices.AddServices(services, pi); DalamudServices.AddServices(services, pi);
services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(EquipItem).Assembly);
services.AddIServices(typeof(Glamourer).Assembly); services.AddIServices(typeof(Glamourer).Assembly);
services.AddIServices(typeof(ImRaii).Assembly); services.AddIServices(typeof(ImRaii).Assembly);
services.AddSingleton<IGlamourerApi>(p => p.GetRequiredService<GlamourerApi>());
services.CreateProvider(); services.CreateProvider();
return services; return services;
} }
@ -164,8 +165,4 @@ public static class StaticServiceManager
.AddSingleton<DesignQuickBar>() .AddSingleton<DesignQuickBar>()
.AddSingleton<DesignColorUi>() .AddSingleton<DesignColorUi>()
.AddSingleton<NpcCombo>(); .AddSingleton<NpcCombo>();
private static ServiceManager AddApi(this ServiceManager services)
=> services.AddSingleton<CommandService>()
.AddSingleton<GlamourerIpc>();
} }

View file

@ -106,7 +106,7 @@ public class StateListener : IDisposable
/// Weapons and meta flags are updated independently. /// Weapons and meta flags are updated independently.
/// We also need to apply fixed designs here. /// We also need to apply fixed designs here.
/// </summary> /// </summary>
private unsafe void OnCreatingCharacterBase(nint actorPtr, string _, nint modelPtr, nint customizePtr, nint equipDataPtr) private unsafe void OnCreatingCharacterBase(nint actorPtr, Guid _, nint modelPtr, nint customizePtr, nint equipDataPtr)
{ {
var actor = (Actor)actorPtr; var actor = (Actor)actorPtr;
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
@ -725,7 +725,7 @@ public class StateListener : IDisposable
_changeCustomizeService.Unsubscribe(OnCustomizeChanged); _changeCustomizeService.Unsubscribe(OnCustomizeChanged);
} }
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) private void OnCreatedCharacterBase(nint gameObject, Guid _, nint drawObject)
{ {
if (_condition[ConditionFlag.CreatingCharacter]) if (_condition[ConditionFlag.CreatingCharacter])
return; return;