mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-01-02 05:43:42 +01:00
Merge branch 'main' of https://github.com/Ottermandias/Glamourer into Ottermandias-main
This commit is contained in:
commit
47d42446cc
85 changed files with 2754 additions and 1560 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -14,3 +14,7 @@
|
|||
path = Penumbra.Api
|
||||
url = https://github.com/Ottermandias/Penumbra.Api.git
|
||||
branch = main
|
||||
[submodule "Glamourer.Api"]
|
||||
path = Glamourer.Api
|
||||
url = git@github.com:Ottermandias/Glamourer.Api.git
|
||||
branch = main
|
||||
|
|
|
|||
1
Glamourer.Api
Submodule
1
Glamourer.Api
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 30cdd0a2386045b84f3cfdde483a1ffe60441f05
|
||||
|
|
@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{EF233CE2-F243-449E-BE05-72B9D110E419}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.Api", "Glamourer.Api\Glamourer.Api.csproj", "{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -45,6 +47,10 @@ Global
|
|||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
124
Glamourer/Api/ApiHelpers.cs
Normal file
124
Glamourer/Api/ApiHelpers.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
69
Glamourer/Api/DesignsApi.cs
Normal file
69
Glamourer/Api/DesignsApi.cs
Normal 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 playerName, uint key, ApplyFlag flags)
|
||||
{
|
||||
var args = ApiHelpers.Args("Design", designId, "Name", playerName, "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(playerName))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
22
Glamourer/Api/GlamourerApi.cs
Normal file
22
Glamourer/Api/GlamourerApi.cs
Normal 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 = 1;
|
||||
|
||||
public (int Major, int Minor) ApiVersion
|
||||
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
|
||||
|
||||
public IGlamourerApiDesigns Designs
|
||||
=> designs;
|
||||
|
||||
public IGlamourerApiItems Items
|
||||
=> items;
|
||||
|
||||
public IGlamourerApiState State
|
||||
=> state;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
60
Glamourer/Api/IpcProviders.cs
Normal file
60
Glamourer/Api/IpcProviders.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using Dalamud.Plugin;
|
||||
using Glamourer.Api.Api;
|
||||
using Glamourer.Api.Helpers;
|
||||
using OtterGui.Services;
|
||||
|
||||
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 =
|
||||
[
|
||||
new FuncProvider<(int Major, int Minor)>(pi, "Glamourer.ApiVersions", () => api.ApiVersion), // backward compatibility
|
||||
new FuncProvider<int>(pi, "Glamourer.ApiVersion", () => api.ApiVersion.Major), // backward compatibility
|
||||
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.GetStateBase64.Provider(pi, api.State),
|
||||
IpcSubscribers.GetStateBase64Name.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();
|
||||
}
|
||||
}
|
||||
82
Glamourer/Api/ItemsApi.cs
Normal file
82
Glamourer/Api/ItemsApi.cs
Normal 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 playerName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags)
|
||||
{
|
||||
var args = ApiHelpers.Args("Name", playerName, "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(playerName))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
340
Glamourer/Api/StateApi.cs
Normal file
340
Glamourer/Api/StateApi.cs
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
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 playerName, uint key)
|
||||
=> Convert(_helpers.FindStates(playerName).FirstOrDefault(), key);
|
||||
|
||||
public (GlamourerApiEc, string?) GetStateBase64(int objectIndex, uint key)
|
||||
=> ConvertBase64(_helpers.FindState(objectIndex), key);
|
||||
|
||||
public (GlamourerApiEc, string?) GetStateBase64Name(string objectName, uint key)
|
||||
=> ConvertBase64(_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 playerName, uint key, ApplyFlag flags)
|
||||
{
|
||||
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||
if (Convert(applyState, flags, out var version) is not { } design)
|
||||
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args);
|
||||
|
||||
var states = _helpers.FindExistingStates(playerName);
|
||||
|
||||
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 playerName, uint key, ApplyFlag flags)
|
||||
{
|
||||
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||
var states = _helpers.FindExistingStates(playerName);
|
||||
|
||||
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 playerName, uint key)
|
||||
{
|
||||
var args = ApiHelpers.Args("Name", playerName, "Key", key);
|
||||
var states = _helpers.FindExistingStates(playerName);
|
||||
|
||||
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 playerName, uint key, ApplyFlag flags)
|
||||
{
|
||||
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||
var states = _helpers.FindExistingStates(playerName);
|
||||
|
||||
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, out var forcedRedraw);
|
||||
_stateManager.ReapplyState(actor, state, forcedRedraw, 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 (GlamourerApiEc, string?) ConvertBase64(ActorState? state, uint key)
|
||||
{
|
||||
var (ec, jObj) = Convert(state, key);
|
||||
return (ec, jObj != null ? DesignConverter.ToBase64(jObj) : null);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -152,9 +152,9 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
{
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
{
|
||||
Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false);
|
||||
Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, out var forcedRedraw);
|
||||
foreach (var actor in data.Objects)
|
||||
_state.ReapplyState(actor, StateSource.Fixed);
|
||||
_state.ReapplyState(actor, forcedRedraw,StateSource.Fixed);
|
||||
}
|
||||
}
|
||||
else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data))
|
||||
|
|
@ -164,8 +164,8 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
var specificId = actor.GetIdentifier(_actors);
|
||||
if (_state.GetOrCreate(specificId, actor, out var state))
|
||||
{
|
||||
Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false);
|
||||
_state.ReapplyState(actor, StateSource.Fixed);
|
||||
Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, out var forcedRedraw);
|
||||
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -212,12 +212,13 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
|
||||
var respectManual = state.LastJob == newJob.Id;
|
||||
state.LastJob = actor.Job;
|
||||
Reduce(actor, state, set, respectManual, true);
|
||||
_state.ReapplyState(actor, StateSource.Fixed);
|
||||
Reduce(actor, state, set, respectManual, true, out var forcedRedraw);
|
||||
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
|
||||
}
|
||||
|
||||
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset)
|
||||
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, out bool forcedRedraw)
|
||||
{
|
||||
forcedRedraw = false;
|
||||
if (!_config.EnableAutoDesigns)
|
||||
return;
|
||||
|
||||
|
|
@ -226,7 +227,7 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
|
||||
if (reset)
|
||||
_state.ResetState(state, StateSource.Game);
|
||||
Reduce(actor, state, set, false, false);
|
||||
Reduce(actor, state, set, false, false, out forcedRedraw);
|
||||
}
|
||||
|
||||
public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state)
|
||||
|
|
@ -253,11 +254,11 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange;
|
||||
if (!respectManual)
|
||||
_state.ResetState(state, StateSource.Game);
|
||||
Reduce(actor, state, set, respectManual, false);
|
||||
Reduce(actor, state, set, respectManual, false, out _);
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange)
|
||||
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, out bool forcedRedraw)
|
||||
{
|
||||
if (set.BaseState is AutoDesignSet.Base.Game)
|
||||
{
|
||||
|
|
@ -275,6 +276,7 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
forcedRedraw = false;
|
||||
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
|
||||
return;
|
||||
|
||||
|
|
@ -282,6 +284,7 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))),
|
||||
state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods);
|
||||
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false));
|
||||
forcedRedraw = mergedDesign.ForcedRedraw;
|
||||
}
|
||||
|
||||
/// <summary> Get world-specific first and all-world afterward. </summary>
|
||||
|
|
@ -323,10 +326,10 @@ public sealed class AutoDesignApplier : IDisposable
|
|||
|
||||
var respectManual = prior == id;
|
||||
NewGearsetId = id;
|
||||
Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob);
|
||||
Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, out var forcedRedraw);
|
||||
NewGearsetId = -1;
|
||||
foreach (var actor in data.Objects)
|
||||
_state.ReapplyState(actor, StateSource.Fixed);
|
||||
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
|
||||
}
|
||||
|
||||
public static unsafe bool CheckGearset(short check)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Dalamud.Game.ClientState.Keys;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Gui.Tabs.DesignTab;
|
||||
using Glamourer.Services;
|
||||
using Newtonsoft.Json;
|
||||
using OtterGui;
|
||||
|
|
@ -13,42 +14,54 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
|||
|
||||
namespace Glamourer;
|
||||
|
||||
public enum HeightDisplayType
|
||||
{
|
||||
None,
|
||||
Centimetre,
|
||||
Metre,
|
||||
Wrong,
|
||||
WrongFoot,
|
||||
}
|
||||
|
||||
public class Configuration : IPluginConfiguration, ISavable
|
||||
{
|
||||
[JsonIgnore]
|
||||
public readonly EphemeralConfig Ephemeral;
|
||||
|
||||
public bool UseRestrictedGearProtection { get; set; } = false;
|
||||
public bool OpenFoldersByDefault { get; set; } = false;
|
||||
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
||||
public bool EnableAutoDesigns { get; set; } = true;
|
||||
public bool HideApplyCheckmarks { get; set; } = false;
|
||||
public bool SmallEquip { get; set; } = false;
|
||||
public bool UnlockedItemMode { get; set; } = false;
|
||||
public byte DisableFestivals { get; set; } = 1;
|
||||
public bool EnableGameContextMenu { get; set; } = true;
|
||||
public bool HideWindowInCutscene { get; set; } = false;
|
||||
public bool ShowAutomationSetEditing { get; set; } = true;
|
||||
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
|
||||
public bool ShowUnlockedItemWarnings { get; set; } = true;
|
||||
public bool RevertManualChangesOnZoneChange { get; set; } = false;
|
||||
public bool ShowQuickBarInTabs { get; set; } = true;
|
||||
public bool OpenWindowAtStart { get; set; } = false;
|
||||
public bool ShowWindowWhenUiHidden { get; set; } = false;
|
||||
public bool UseAdvancedParameters { get; set; } = true;
|
||||
public bool UseAdvancedDyes { get; set; } = true;
|
||||
public bool KeepAdvancedDyesAttached { get; set; } = true;
|
||||
public bool ShowPalettePlusImport { get; set; } = true;
|
||||
public bool UseFloatForColors { get; set; } = true;
|
||||
public bool UseRgbForColors { get; set; } = true;
|
||||
public bool ShowColorConfig { get; set; } = true;
|
||||
public bool ChangeEntireItem { get; set; } = false;
|
||||
public bool AlwaysApplyAssociatedMods { get; set; } = false;
|
||||
public bool AllowDoubleClickToApply { get; set; } = false;
|
||||
public bool RespectManualOnAutomationUpdate { get; set; } = false;
|
||||
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
||||
public bool UseRestrictedGearProtection { get; set; } = false;
|
||||
public bool OpenFoldersByDefault { get; set; } = false;
|
||||
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
||||
public bool EnableAutoDesigns { get; set; } = true;
|
||||
public bool HideApplyCheckmarks { get; set; } = false;
|
||||
public bool SmallEquip { get; set; } = false;
|
||||
public bool UnlockedItemMode { get; set; } = false;
|
||||
public byte DisableFestivals { get; set; } = 1;
|
||||
public bool EnableGameContextMenu { get; set; } = true;
|
||||
public bool HideWindowInCutscene { get; set; } = false;
|
||||
public bool ShowAutomationSetEditing { get; set; } = true;
|
||||
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
|
||||
public bool ShowUnlockedItemWarnings { get; set; } = true;
|
||||
public bool RevertManualChangesOnZoneChange { get; set; } = false;
|
||||
public bool ShowQuickBarInTabs { get; set; } = true;
|
||||
public bool OpenWindowAtStart { get; set; } = false;
|
||||
public bool ShowWindowWhenUiHidden { get; set; } = false;
|
||||
public bool UseAdvancedParameters { get; set; } = true;
|
||||
public bool UseAdvancedDyes { get; set; } = true;
|
||||
public bool KeepAdvancedDyesAttached { get; set; } = true;
|
||||
public bool ShowPalettePlusImport { get; set; } = true;
|
||||
public bool UseFloatForColors { get; set; } = true;
|
||||
public bool UseRgbForColors { get; set; } = true;
|
||||
public bool ShowColorConfig { get; set; } = true;
|
||||
public bool ChangeEntireItem { get; set; } = false;
|
||||
public bool AlwaysApplyAssociatedMods { get; set; } = false;
|
||||
public bool AllowDoubleClickToApply { get; set; } = false;
|
||||
public bool RespectManualOnAutomationUpdate { get; set; } = false;
|
||||
|
||||
public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre;
|
||||
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
|
||||
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
||||
|
||||
public QdbButtons QdbButtons { get; set; } =
|
||||
QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvanced;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
|||
public string Description { get; internal set; } = string.Empty;
|
||||
public string[] Tags { get; internal set; } = [];
|
||||
public int Index { get; internal set; }
|
||||
public bool ForcedRedraw { get; internal set; }
|
||||
public bool QuickDesign { get; internal set; } = true;
|
||||
public string Color { get; internal set; } = string.Empty;
|
||||
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
|
||||
|
|
@ -99,6 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
|||
["LastEdit"] = LastEdit,
|
||||
["Name"] = Name.Text,
|
||||
["Description"] = Description,
|
||||
["ForcedRedraw"] = ForcedRedraw,
|
||||
["Color"] = Color,
|
||||
["QuickDesign"] = QuickDesign,
|
||||
["Tags"] = JArray.FromObject(Tags),
|
||||
|
|
@ -173,7 +175,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
|||
LoadParameters(json["Parameters"], design, design.Name);
|
||||
LoadMaterials(json["Materials"], design, design.Name);
|
||||
LoadLinks(linkLoader, json["Links"], design);
|
||||
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
||||
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
||||
design.ForcedRedraw = json["ForcedRedraw"]?.ToObject<bool>() ?? false;
|
||||
return design;
|
||||
|
||||
static string[] ParseTags(JObject json)
|
||||
|
|
@ -199,8 +202,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
|||
continue;
|
||||
}
|
||||
|
||||
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, string[]>>() ?? new Dictionary<string, string[]>();
|
||||
var settings = new SortedList<string, IList<string>>(settingsDict.Count);
|
||||
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, List<string>>>() ?? [];
|
||||
var settings = new Dictionary<string, List<string>>(settingsDict.Count);
|
||||
foreach (var (key, value) in settingsDict)
|
||||
settings.Add(key, value);
|
||||
var priority = tok["Priority"]?.ToObject<int>() ?? 0;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Newtonsoft.Json;
|
|||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
|
@ -34,10 +34,10 @@ public class DesignConverter(
|
|||
}
|
||||
|
||||
public string ShareBase64(Design design)
|
||||
=> ShareBase64(ShareJObject(design));
|
||||
=> ToBase64(ShareJObject(design));
|
||||
|
||||
public string ShareBase64(DesignBase design)
|
||||
=> ShareBase64(ShareJObject(design));
|
||||
=> ToBase64(ShareJObject(design));
|
||||
|
||||
public string ShareBase64(ActorState state, in ApplicationRules 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)
|
||||
{
|
||||
var design = Convert(data, materials, rules);
|
||||
return ShareBase64(ShareJObject(design));
|
||||
return ToBase64(ShareJObject(design));
|
||||
}
|
||||
|
||||
public DesignBase Convert(ActorState state, in ApplicationRules rules)
|
||||
|
|
@ -61,6 +61,37 @@ public class DesignConverter(
|
|||
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)
|
||||
{
|
||||
DesignBase ret;
|
||||
|
|
@ -138,7 +169,7 @@ public class DesignConverter(
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static string ShareBase64(JToken jObject)
|
||||
public static string ToBase64(JToken jObject)
|
||||
{
|
||||
var json = jObject.ToString(Formatting.None);
|
||||
var compressed = json.Compress(Version);
|
||||
|
|
@ -193,7 +224,7 @@ public class DesignConverter(
|
|||
foreach (var (key, value) in materials.Values)
|
||||
{
|
||||
var idx = MaterialValueIndex.FromKey(key);
|
||||
if (idx.RowIndex >= MtrlFile.ColorTable.NumRows)
|
||||
if (idx.RowIndex >= LegacyColorTable.NumUsedRows)
|
||||
continue;
|
||||
if (idx.MaterialIndex >= MaterialService.MaterialsPerModel)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ public sealed class DesignManager : DesignEditor
|
|||
DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription);
|
||||
}
|
||||
|
||||
/// <summary> Change the associated color of a design. </summary>
|
||||
public void ChangeColor(Design design, string newColor)
|
||||
{
|
||||
var oldColor = design.Color;
|
||||
|
|
@ -273,13 +274,13 @@ public sealed class DesignManager : DesignEditor
|
|||
DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
|
||||
}
|
||||
|
||||
public void UpdateMod(Design design, Mod mod, ModSettings settings) {
|
||||
if (!design.AssociatedMods.ContainsKey(mod))
|
||||
return;
|
||||
/// <summary> Add or update an associated mod to a design. </summary>
|
||||
public void UpdateMod(Design design, Mod mod, ModSettings settings)
|
||||
{
|
||||
design.AssociatedMods[mod] = settings;
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||
Glamourer.Log.Debug($"Updated (or added) associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +303,8 @@ public sealed class DesignManager : DesignEditor
|
|||
|
||||
design.QuickDesign = value;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar.");
|
||||
Glamourer.Log.Debug(
|
||||
$"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, value);
|
||||
}
|
||||
|
||||
|
|
@ -310,6 +312,17 @@ public sealed class DesignManager : DesignEditor
|
|||
|
||||
#region Edit Application Rules
|
||||
|
||||
public void ChangeForcedRedraw(Design design, bool forcedRedraw)
|
||||
{
|
||||
if (design.ForcedRedraw == forcedRedraw)
|
||||
return;
|
||||
|
||||
design.ForcedRedraw = forcedRedraw;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? "not" : string.Empty)} force redraws.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null);
|
||||
}
|
||||
|
||||
/// <summary> Change whether to apply a specific customize value. </summary>
|
||||
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,4 +23,6 @@ public interface IDesignStandIn : IEquatable<IDesignStandIn>
|
|||
public void ParseData(JObject jObj);
|
||||
|
||||
public bool ChangeData(object data);
|
||||
|
||||
public bool ForcedRedraw { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ public class DesignMerger(
|
|||
{
|
||||
public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership,
|
||||
bool modAssociations)
|
||||
=> Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type, JobFlag.All)), currentCustomize, baseRef, respectOwnership, modAssociations);
|
||||
=> Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type, JobFlag.All)), currentCustomize, baseRef, respectOwnership,
|
||||
modAssociations);
|
||||
|
||||
public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType, JobFlag)> designs, in CustomizeArray currentCustomize, in DesignData baseRef,
|
||||
public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType, JobFlag)> designs, in CustomizeArray currentCustomize,
|
||||
in DesignData baseRef,
|
||||
bool respectOwnership, bool modAssociations)
|
||||
{
|
||||
var ret = new MergedDesign(designManager);
|
||||
|
|
@ -51,6 +53,8 @@ public class DesignMerger(
|
|||
ReduceMods(design as Design, ret, modAssociations);
|
||||
if (type.HasFlag(ApplicationType.GearCustomization))
|
||||
ReduceMaterials(design, ret);
|
||||
if (design.ForcedRedraw)
|
||||
ret.ForcedRedraw = true;
|
||||
}
|
||||
|
||||
ApplyFixFlags(ret, fixFlags);
|
||||
|
|
@ -189,7 +193,8 @@ public class DesignMerger(
|
|||
ret.Weapons.TryAdd(weapon.Type, weapon, source, allowedJobs);
|
||||
}
|
||||
|
||||
private void ReduceOffhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership)
|
||||
private void ReduceOffhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
||||
bool respectOwnership)
|
||||
{
|
||||
if (!equipFlags.HasFlag(EquipFlag.Offhand))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ public sealed class MergedDesign
|
|||
if (weapon.Valid)
|
||||
Weapons.TryAdd(weapon.Type, weapon, StateSource.Manual, JobFlag.All);
|
||||
}
|
||||
|
||||
ForcedRedraw = design is IDesignStandIn { ForcedRedraw: true };
|
||||
}
|
||||
|
||||
public MergedDesign(Design design)
|
||||
|
|
@ -101,4 +103,5 @@ public sealed class MergedDesign
|
|||
public readonly WeaponList Weapons = new();
|
||||
public readonly SortedList<Mod, ModSettings> AssociatedMods = [];
|
||||
public StateSources Sources = new();
|
||||
public bool ForcedRedraw;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,7 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ
|
|||
|
||||
public bool ChangeData(object data)
|
||||
=> false;
|
||||
|
||||
public bool ForcedRedraw
|
||||
=> combo.Design?.ForcedRedraw ?? false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,4 +78,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
|
|||
Predicates = predicates;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ForcedRedraw
|
||||
=> false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,4 +42,7 @@ public class RevertDesign : IDesignStandIn
|
|||
|
||||
public bool ChangeData(object data)
|
||||
=> false;
|
||||
|
||||
public bool ForcedRedraw
|
||||
=> false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ public sealed class DesignChanged()
|
|||
/// <summary> An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. </summary>
|
||||
MaterialRevert,
|
||||
|
||||
/// <summary> An existing design had changed whether it always forces a redraw or not. </summary>
|
||||
ForceRedraw,
|
||||
|
||||
/// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary>
|
||||
ApplyCustomize,
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class Glamourer : IDalamudPlugin
|
|||
_services.GetService<StateListener>(); // Initialize State Listener.
|
||||
_services.GetService<GlamourerWindowSystem>(); // initialize ui.
|
||||
_services.GetService<CommandService>(); // initialize commands.
|
||||
_services.GetService<GlamourerIpc>(); // initialize IPC.
|
||||
_services.GetService<IpcProviders>(); // initialize IPC.
|
||||
Log.Information($"Glamourer v{Version} loaded successfully.");
|
||||
}
|
||||
catch
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
|
||||
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
|
||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
||||
<ProjectReference Include="..\Penumbra.String\Penumbra.string.csproj" />
|
||||
|
|
|
|||
|
|
@ -29,6 +29,27 @@ public partial class CustomizationDrawer
|
|||
ImGui.SameLine();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
if (_currentIndex is CustomizeIndex.Height)
|
||||
DrawHeight();
|
||||
}
|
||||
|
||||
private void DrawHeight()
|
||||
{
|
||||
if (_config.HeightDisplayType is HeightDisplayType.None)
|
||||
return;
|
||||
|
||||
var height = _heightService.Height(_customize);
|
||||
ImGui.SameLine();
|
||||
|
||||
var heightString = _config.HeightDisplayType switch
|
||||
{
|
||||
HeightDisplayType.Centimetre => FormattableString.Invariant($"({height * 100:F1} cm)"),
|
||||
HeightDisplayType.Metre => FormattableString.Invariant($"({height:F2} m)"),
|
||||
HeightDisplayType.Wrong => FormattableString.Invariant($"({height * 100 / 2.539:F1} in)"),
|
||||
HeightDisplayType.WrongFoot => $"({(int)(height * 100 / 2.539 / 12)}'{(int)(height * 100 / 2.539) % 12}'')",
|
||||
_ => FormattableString.Invariant($"({height})"),
|
||||
};
|
||||
ImGui.TextUnformatted(heightString);
|
||||
}
|
||||
|
||||
private void DrawPercentageSlider()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites)
|
||||
public partial class CustomizationDrawer(
|
||||
DalamudPluginInterface pi,
|
||||
CustomizeService _service,
|
||||
CodeService _codes,
|
||||
Configuration _config,
|
||||
FavoriteManager _favorites,
|
||||
HeightService _heightService)
|
||||
: IDisposable
|
||||
{
|
||||
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
|
|
@ -20,8 +26,8 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
|||
|
||||
private Exception? _terminate;
|
||||
|
||||
private CustomizeArray _customize = CustomizeArray.Default;
|
||||
private CustomizeSet _set = null!;
|
||||
private CustomizeArray _customize = CustomizeArray.Default;
|
||||
private CustomizeSet _set = null!;
|
||||
|
||||
public CustomizeArray Customize
|
||||
=> _customize;
|
||||
|
|
@ -46,7 +52,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
|||
|
||||
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
|
||||
{
|
||||
_withApply = false;
|
||||
_withApply = false;
|
||||
Init(current, locked, lockedRedraw);
|
||||
|
||||
return DrawInternal();
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
|
||||
private void Draw(float width)
|
||||
{
|
||||
_objects.Update();
|
||||
using var group = ImRaii.Group();
|
||||
var spacing = ImGui.GetStyle().ItemInnerSpacing;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||
|
|
@ -113,7 +112,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
ImGui.SameLine();
|
||||
DrawApplyButton(buttonSize);
|
||||
}
|
||||
|
||||
|
||||
DrawRevertButton(buttonSize);
|
||||
DrawRevertEquipButton(buttonSize);
|
||||
DrawRevertCustomizeButton(buttonSize);
|
||||
|
|
@ -132,7 +131,6 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
|
||||
private void PrepareButtons()
|
||||
{
|
||||
_objects.Update();
|
||||
(_playerIdentifier, _playerData) = _objects.PlayerData;
|
||||
(_targetIdentifier, _targetData) = _objects.TargetData;
|
||||
_playerState = _stateManager.GetValueOrDefault(_playerIdentifier);
|
||||
|
|
@ -251,8 +249,8 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
|
||||
foreach (var actor in data.Objects)
|
||||
{
|
||||
_autoDesignApplier.ReapplyAutomation(actor, id, state!, true);
|
||||
_stateManager.ReapplyState(actor, StateSource.Manual);
|
||||
_autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw);
|
||||
_stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,8 +289,8 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
|
||||
foreach (var actor in data.Objects)
|
||||
{
|
||||
_autoDesignApplier.ReapplyAutomation(actor, id, state!, false);
|
||||
_stateManager.ReapplyState(actor, StateSource.Manual);
|
||||
_autoDesignApplier.ReapplyAutomation(actor, id, state!, false, out var forcedRedraw);
|
||||
_stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ public class EquipmentDrawer
|
|||
foreach (var type in Enum.GetValues<FullEquipType>())
|
||||
{
|
||||
if (type.ToSlot() is EquipSlot.MainHand)
|
||||
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log));
|
||||
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites));
|
||||
else if (type.ToSlot() is EquipSlot.OffHand)
|
||||
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log));
|
||||
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites));
|
||||
}
|
||||
|
||||
_weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log));
|
||||
_weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log, favorites));
|
||||
}
|
||||
|
||||
private Vector2 _iconSize;
|
||||
|
|
@ -452,8 +452,8 @@ public class EquipmentDrawer
|
|||
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
|
||||
data.SetStain(Stain.None.RowIndex);
|
||||
|
||||
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out _))
|
||||
data.SetStain(Stain.None.RowIndex);
|
||||
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var newStain))
|
||||
data.SetStain(newStain);
|
||||
}
|
||||
|
||||
private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open)
|
||||
|
|
@ -579,7 +579,7 @@ public class EquipmentDrawer
|
|||
|
||||
private static void DrawApplyStain(in EquipDrawData data)
|
||||
{
|
||||
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain,
|
||||
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this dye to the item when applying the Design.", data.CurrentApplyStain,
|
||||
out var enabled,
|
||||
data.Locked))
|
||||
data.SetApplyStain(enabled);
|
||||
|
|
|
|||
|
|
@ -105,14 +105,11 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot)
|
||||
private static List<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot)
|
||||
{
|
||||
var nothing = ItemManager.NothingItem(slot);
|
||||
if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list))
|
||||
return new[]
|
||||
{
|
||||
nothing,
|
||||
};
|
||||
return [nothing];
|
||||
|
||||
var enumerable = list.AsEnumerable();
|
||||
if (slot.IsEquipment())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Glamourer.Services;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -12,13 +13,15 @@ namespace Glamourer.Gui.Equipment;
|
|||
|
||||
public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
||||
{
|
||||
public readonly string Label;
|
||||
private ItemId _currentItemId;
|
||||
private float _innerWidth;
|
||||
private readonly FavoriteManager _favorites;
|
||||
public readonly string Label;
|
||||
private ItemId _currentItem;
|
||||
private float _innerWidth;
|
||||
|
||||
public WeaponCombo(ItemManager items, FullEquipType type, Logger log)
|
||||
: base(() => GetWeapons(items, type), MouseWheelType.Control, log)
|
||||
public WeaponCombo(ItemManager items, FullEquipType type, Logger log, FavoriteManager favorites)
|
||||
: base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log)
|
||||
{
|
||||
_favorites = favorites;
|
||||
Label = GetLabel(type);
|
||||
SearchByParts = true;
|
||||
}
|
||||
|
|
@ -32,29 +35,38 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
|||
|
||||
protected override int UpdateCurrentSelected(int currentSelected)
|
||||
{
|
||||
if (CurrentSelection.ItemId == _currentItemId)
|
||||
if (CurrentSelection.ItemId == _currentItem)
|
||||
return currentSelected;
|
||||
|
||||
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItemId);
|
||||
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
|
||||
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
|
||||
return base.UpdateCurrentSelected(CurrentSelectionIdx);
|
||||
}
|
||||
|
||||
public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth)
|
||||
{
|
||||
_innerWidth = innerWidth;
|
||||
_currentItem = previewIdx;
|
||||
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
protected override float GetFilterWidth()
|
||||
=> _innerWidth - 2 * ImGui.GetStyle().FramePadding.X;
|
||||
|
||||
public bool Draw(string previewName, ItemId previewId, float width, float innerWidth)
|
||||
{
|
||||
_currentItemId = previewId;
|
||||
_innerWidth = innerWidth;
|
||||
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||
{
|
||||
var obj = Items[globalIdx];
|
||||
var name = ToString(obj);
|
||||
var ret = ImGui.Selectable(name, selected);
|
||||
if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx)
|
||||
{
|
||||
CurrentSelectionIdx = -1;
|
||||
_currentItem = obj.ItemId;
|
||||
CurrentSelection = default;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var ret = ImGui.Selectable(name, selected);
|
||||
ImGui.SameLine();
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
||||
ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant})");
|
||||
|
|
@ -70,7 +82,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
|||
private static string GetLabel(FullEquipType type)
|
||||
=> type is FullEquipType.Unknown ? "Mainhand" : type.ToName();
|
||||
|
||||
private static IReadOnlyList<EquipItem> GetWeapons(ItemManager items, FullEquipType type)
|
||||
private static IReadOnlyList<EquipItem> GetWeapons(FavoriteManager favorites, ItemManager items, FullEquipType type)
|
||||
{
|
||||
if (type is FullEquipType.Unknown)
|
||||
{
|
||||
|
|
@ -81,15 +93,15 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
|||
enumerable = enumerable.Concat(l);
|
||||
}
|
||||
|
||||
return enumerable.OrderBy(e => e.Name).ToList();
|
||||
return [.. enumerable.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)];
|
||||
}
|
||||
|
||||
if (!items.ItemData.ByType.TryGetValue(type, out var list))
|
||||
return Array.Empty<EquipItem>();
|
||||
return [];
|
||||
|
||||
if (type.AllowsNothing())
|
||||
return list.OrderBy(e => e.Name).Prepend(ItemManager.NothingItem(type)).ToList();
|
||||
return [ItemManager.NothingItem(type), .. list.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)];
|
||||
|
||||
return list.OrderBy(e => e.Name).ToList();
|
||||
return [.. list.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ public class GlamourerChangelog
|
|||
AddDummy(Changelog);
|
||||
Add1_2_0_0(Changelog);
|
||||
Add1_2_1_0(Changelog);
|
||||
AddDummy(Changelog);
|
||||
Add1_2_3_0(Changelog);
|
||||
}
|
||||
|
||||
private (int, ChangeLogDisplayType) ConfigData()
|
||||
|
|
@ -52,13 +54,41 @@ public class GlamourerChangelog
|
|||
}
|
||||
}
|
||||
|
||||
private static void Add1_2_3_0(Changelog log)
|
||||
=> log.NextVersion("Version 1.2.3.0")
|
||||
.RegisterHighlight(
|
||||
"Added a field to rename designs directly from the mod selector context menu, instead of moving them in the filesystem.")
|
||||
.RegisterEntry("You can choose which rename field (none, either one or both) to display in the settings.", 1)
|
||||
.RegisterEntry("Automatically applied offhand weapons due to mainhand settings now also apply the mainhands dye.")
|
||||
.RegisterHighlight("Added a height display in real-world units next to the height-selector.")
|
||||
.RegisterEntry("This can be configured to use your favourite unit of measurement, even wrong ones, or not display at all.", 1)
|
||||
.RegisterHighlight(
|
||||
"Added a chat command '/glamour applycustomization' that can apply single customization values to actors. Use without arguments for help.")
|
||||
.RegisterHighlight(
|
||||
"Added an option for designs to always force a redraw when applied to a character, regardless of whether it is necessary or not.")
|
||||
.RegisterHighlight("Added a button to overwrite the selected design with the current player state.")
|
||||
.RegisterEntry("Added some copy/paste functionality for mod associations.")
|
||||
.RegisterEntry("Reworked the API and IPC structure heavily.")
|
||||
.RegisterEntry("Added warnings if Glamourer can not attach successfully to Penumbra or if Penumbras IPC version is not correct.")
|
||||
.RegisterEntry("Added hints for all of the available cheat codes and improved the cheat code display somewhat.")
|
||||
.RegisterEntry("Fixed weapon selectors not having a favourite star available.")
|
||||
.RegisterEntry("Fixed issues with items with custom names.")
|
||||
.RegisterEntry("Fixed the labels for eye colors.")
|
||||
.RegisterEntry("Fixed the tooltip for Apply Dye checkboxes.")
|
||||
.RegisterEntry("Fixed an issue when hovering over assigned mod settings.")
|
||||
.RegisterEntry("Made conformant to Dalamud guidelines by adding a button to open the main UI.")
|
||||
.RegisterEntry("Fixed an issue with visor states. (1.2.1.3)")
|
||||
.RegisterEntry("Fixed an issue with identical weapon types and multiple restricted designs. (1.2.1.3)");
|
||||
|
||||
private static void Add1_2_1_0(Changelog log)
|
||||
=> log.NextVersion("Version 1.2.1.0")
|
||||
.RegisterEntry("Updated for .net 8 and FFXIV 6.58, using some new framework options to improve performance and stability.")
|
||||
.RegisterEntry("Previewing changed items in Penumbra now works with all weapons in GPose. (1.2.0.8)")
|
||||
.RegisterEntry("Added a design type selectable for automation that applies the design currently selected in the quick design bar. (1.2.0.4)")
|
||||
.RegisterEntry(
|
||||
"Added a design type selectable for automation that applies the design currently selected in the quick design bar. (1.2.0.4)")
|
||||
.RegisterEntry("Added an option to respect manual changes when changing automation settings. (1.2.0.3)")
|
||||
.RegisterEntry("You can now apply designs to the player character with a double click on them (can be turned off in settings). (1.2.0.1)")
|
||||
.RegisterEntry(
|
||||
"You can now apply designs to the player character with a double click on them (can be turned off in settings). (1.2.0.1)")
|
||||
.RegisterEntry("The last selected design and tab are now stored and applied on startup. (1.2.0.1)")
|
||||
.RegisterEntry("Fixed behavior of revert to automation to actually revert and not just reapply. (1.2.0.8)")
|
||||
.RegisterEntry("Added Reapply Automation buttons and chat commands with prior behaviour.", 1)
|
||||
|
|
|
|||
|
|
@ -22,15 +22,17 @@ public class GlamourerWindowSystem : IDisposable
|
|||
_windowSystem.AddWindow(unlocksTab);
|
||||
_windowSystem.AddWindow(changelog.Changelog);
|
||||
_windowSystem.AddWindow(quick);
|
||||
_uiBuilder.OpenMainUi += _ui.Toggle;
|
||||
_uiBuilder.Draw += _windowSystem.Draw;
|
||||
_uiBuilder.OpenConfigUi += _ui.Toggle;
|
||||
_uiBuilder.OpenConfigUi += _ui.OpenSettings;
|
||||
_uiBuilder.DisableCutsceneUiHide = !config.HideWindowInCutscene;
|
||||
_uiBuilder.DisableUserUiHide = config.ShowWindowWhenUiHidden;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_uiBuilder.OpenMainUi -= _ui.Toggle;
|
||||
_uiBuilder.Draw -= _windowSystem.Draw;
|
||||
_uiBuilder.OpenConfigUi -= _ui.Toggle;
|
||||
_uiBuilder.OpenConfigUi -= _ui.OpenSettings;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
|
|
@ -11,9 +10,13 @@ using Glamourer.Gui.Tabs.DesignTab;
|
|||
using Glamourer.Gui.Tabs.NpcTab;
|
||||
using Glamourer.Gui.Tabs.SettingsTab;
|
||||
using Glamourer.Gui.Tabs.UnlocksTab;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Custom;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Widgets;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
|
@ -41,10 +44,12 @@ public class MainWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly DesignQuickBar _quickBar;
|
||||
private readonly TabSelected _event;
|
||||
private readonly MainWindowPosition _position;
|
||||
private readonly ITab[] _tabs;
|
||||
private bool _ignorePenumbra = false;
|
||||
|
||||
public readonly SettingsTab Settings;
|
||||
public readonly ActorTab Actors;
|
||||
|
|
@ -59,7 +64,7 @@ public class MainWindow : Window, IDisposable
|
|||
|
||||
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
|
||||
DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar,
|
||||
NpcTab npcs, MainWindowPosition position)
|
||||
NpcTab npcs, MainWindowPosition position, PenumbraService penumbra)
|
||||
: base("GlamourerMainWindow")
|
||||
{
|
||||
pi.UiBuilder.DisableGposeUiHide = true;
|
||||
|
|
@ -80,6 +85,7 @@ public class MainWindow : Window, IDisposable
|
|||
Npcs = npcs;
|
||||
_position = position;
|
||||
_config = config;
|
||||
_penumbra = penumbra;
|
||||
_tabs =
|
||||
[
|
||||
settings,
|
||||
|
|
@ -96,6 +102,12 @@ public class MainWindow : Window, IDisposable
|
|||
IsOpen = _config.OpenWindowAtStart;
|
||||
}
|
||||
|
||||
public void OpenSettings()
|
||||
{
|
||||
IsOpen = true;
|
||||
SelectTab = TabType.Settings;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
Flags = _config.Ephemeral.LockMainWindow
|
||||
|
|
@ -113,18 +125,34 @@ public class MainWindow : Window, IDisposable
|
|||
var yPos = ImGui.GetCursorPosY();
|
||||
_position.Size = ImGui.GetWindowSize();
|
||||
_position.Position = ImGui.GetWindowPos();
|
||||
if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs))
|
||||
SelectTab = TabType.None;
|
||||
var tab = FromLabel(currentTab);
|
||||
|
||||
if (tab != _config.Ephemeral.SelectedTab)
|
||||
if (!_penumbra.Available && !_ignorePenumbra)
|
||||
{
|
||||
_config.Ephemeral.SelectedTab = FromLabel(currentTab);
|
||||
_config.Ephemeral.Save();
|
||||
if (_penumbra.CurrentMajor == 0)
|
||||
DrawProblemWindow(
|
||||
"Could not attach to Penumbra. Please make sure Penumbra is installed and running.\n\nPenumbra is required for Glamourer to work properly.");
|
||||
else if (_penumbra is { CurrentMajor: PenumbraService.RequiredPenumbraBreakingVersion, CurrentMinor: >= PenumbraService.RequiredPenumbraFeatureVersion })
|
||||
DrawProblemWindow(
|
||||
$"You are currently not attached to Penumbra, seemingly by manually detaching from it.\n\nPenumbra's last API Version was {_penumbra.CurrentMajor}.{_penumbra.CurrentMinor}.\n\nPenumbra is required for Glamourer to work properly.");
|
||||
else
|
||||
DrawProblemWindow(
|
||||
$"Attaching to Penumbra failed.\n\nPenumbra's API Version was {_penumbra.CurrentMajor}.{_penumbra.CurrentMinor}, but Glamourer requires a version of {PenumbraService.RequiredPenumbraBreakingVersion}.{PenumbraService.RequiredPenumbraFeatureVersion}, where the major version has to match exactly, and the minor version has to be greater or equal.\nYou may need to update Penumbra or enable Testing Builds for it for this version of Glamourer.\n\nPenumbra is required for Glamourer to work properly.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs))
|
||||
SelectTab = TabType.None;
|
||||
var tab = FromLabel(currentTab);
|
||||
|
||||
if (_config.ShowQuickBarInTabs)
|
||||
_quickBar.DrawAtEnd(yPos);
|
||||
if (tab != _config.Ephemeral.SelectedTab)
|
||||
{
|
||||
_config.Ephemeral.SelectedTab = FromLabel(currentTab);
|
||||
_config.Ephemeral.Save();
|
||||
}
|
||||
|
||||
if (_config.ShowQuickBarInTabs)
|
||||
_quickBar.DrawAtEnd(yPos);
|
||||
}
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> ToLabel(TabType type)
|
||||
|
|
@ -186,4 +214,31 @@ public class MainWindow : Window, IDisposable
|
|||
(false, false) => $"Glamourer v{Glamourer.Version}###GlamourerMainWindow",
|
||||
(false, true) => $"Glamourer v{Glamourer.Version} (Incognito Mode)###GlamourerMainWindow",
|
||||
};
|
||||
|
||||
private void DrawProblemWindow(string text)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.SelectedRed);
|
||||
ImGui.NewLine();
|
||||
ImGui.NewLine();
|
||||
ImGuiUtil.TextWrapped(text);
|
||||
color.Pop();
|
||||
|
||||
ImGui.NewLine();
|
||||
if (ImUtf8.Button("Try Attaching Again"u8))
|
||||
_penumbra.Reattach();
|
||||
|
||||
var ignoreAllowed = _config.DeleteDesignModifier.IsActive();
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.ButtonEx("Ignore Penumbra This Time"u8,
|
||||
$"Some functionality, like automation or retaining state, will not work correctly without Penumbra.\n\nIgnore this at your own risk!{(ignoreAllowed ? string.Empty : $"\n\nHold {_config.DeleteDesignModifier} while clicking to enable this button.")}",
|
||||
default, !ignoreAllowed))
|
||||
_ignorePenumbra = true;
|
||||
|
||||
ImGui.NewLine();
|
||||
ImGui.NewLine();
|
||||
CustomGui.DrawDiscordButton(Glamourer.Messager, 0);
|
||||
ImGui.SameLine();
|
||||
ImGui.NewLine();
|
||||
ImGui.NewLine();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using OtterGui;
|
|||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.String;
|
||||
|
||||
|
|
@ -190,11 +190,11 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
DrawWindow(textures);
|
||||
}
|
||||
|
||||
private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table)
|
||||
private void DrawTable(MaterialValueIndex materialIndex, in LegacyColorTable table)
|
||||
{
|
||||
using var disabled = ImRaii.Disabled(_state.IsLocked);
|
||||
_anyChanged = false;
|
||||
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||
for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i)
|
||||
{
|
||||
var index = materialIndex with { RowIndex = i };
|
||||
ref var row = ref table[i];
|
||||
|
|
@ -205,7 +205,7 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
DrawAllRow(materialIndex, table);
|
||||
}
|
||||
|
||||
private void DrawAllRow(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table)
|
||||
private void DrawAllRow(MaterialValueIndex materialIndex, in LegacyColorTable table)
|
||||
{
|
||||
using var id = ImRaii.PushId(100);
|
||||
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
||||
|
|
@ -242,11 +242,11 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
ImGui.SameLine(0, spacing);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged,
|
||||
true))
|
||||
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||
for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i)
|
||||
stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game);
|
||||
}
|
||||
|
||||
private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table)
|
||||
private void DrawRow(ref LegacyColorTable.Row row, MaterialValueIndex index, in LegacyColorTable table)
|
||||
{
|
||||
using var id = ImRaii.PushId(index.RowIndex);
|
||||
var changed = _state.Materials.TryGetValue(index, out var value);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
using Glamourer.Interop.Material;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
|
||||
namespace Glamourer.Gui.Materials;
|
||||
|
||||
public static class ColorRowClipboard
|
||||
{
|
||||
private static ColorRow _row;
|
||||
private static MtrlFile.ColorTable _table;
|
||||
private static ColorRow _row;
|
||||
private static LegacyColorTable _table;
|
||||
|
||||
public static bool IsSet { get; private set; }
|
||||
|
||||
public static bool IsTableSet { get; private set; }
|
||||
|
||||
public static MtrlFile.ColorTable Table
|
||||
public static LegacyColorTable Table
|
||||
{
|
||||
get => _table;
|
||||
set
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Gui;
|
||||
|
||||
namespace Glamourer.Gui.Materials;
|
||||
|
|
@ -175,9 +175,9 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
|
|||
{
|
||||
_newRowIdx += 1;
|
||||
ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X);
|
||||
if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, MtrlFile.ColorTable.NumRows, "Row #%i"))
|
||||
if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, LegacyColorTable.NumUsedRows, "Row #%i"))
|
||||
{
|
||||
_newRowIdx = Math.Clamp(_newRowIdx, 1, MtrlFile.ColorTable.NumRows);
|
||||
_newRowIdx = Math.Clamp(_newRowIdx, 1, LegacyColorTable.NumUsedRows);
|
||||
_newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -341,8 +341,8 @@ public class ActorPanel
|
|||
"Reapply the current automation state for the character on top of its current state..",
|
||||
!_config.EnableAutoDesigns || _state!.IsLocked))
|
||||
{
|
||||
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false);
|
||||
_stateManager.ReapplyState(_actor, StateSource.Manual);
|
||||
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw);
|
||||
_stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -350,15 +350,15 @@ public class ActorPanel
|
|||
"Try to revert the character to the state it would have using automated designs.",
|
||||
!_config.EnableAutoDesigns || _state!.IsLocked))
|
||||
{
|
||||
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true);
|
||||
_stateManager.ReapplyState(_actor, StateSource.Manual);
|
||||
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw);
|
||||
_stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.DrawDisabledButton("Reapply", Vector2.Zero,
|
||||
"Try to reapply the configured state if something went wrong. Should generally not be necessary.",
|
||||
_state!.IsLocked))
|
||||
_stateManager.ReapplyState(_actor, StateSource.Manual);
|
||||
_stateManager.ReapplyState(_actor, false, StateSource.Manual);
|
||||
}
|
||||
|
||||
private void DrawApplyToSelf()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using ImGuiNET;
|
||||
using Glamourer.Gui.Tabs.DebugTab.IpcTester;
|
||||
using ImGuiNET;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
|
|
|||
78
Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs
Normal file
78
Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
60
Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs
Normal file
60
Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
77
Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs
Normal file
77
Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Api.IpcSubscribers;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
68
Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs
Normal file
68
Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
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, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key,
|
||||
_flags);
|
||||
|
||||
IpcTesterHelpers.DrawIntro(SetItemName.Label);
|
||||
if (ImGui.Button("Set##Name"))
|
||||
_lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
210
Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs
Normal file
210
Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Api.Enums;
|
||||
using Glamourer.Api.Helpers;
|
||||
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.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;
|
||||
private string? _getStateString;
|
||||
|
||||
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);
|
||||
StateChanged.Disable();
|
||||
GPoseChanged.Disable();
|
||||
}
|
||||
|
||||
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(GetStateBase64.Label);
|
||||
if (ImGui.Button("Get##Base64Idx"))
|
||||
{
|
||||
(_lastError, _getStateString) = new GetStateBase64(_pluginInterface).Invoke(_gameObjectIndex, _key);
|
||||
_stateString = _getStateString ?? "No State Available";
|
||||
ImGui.OpenPopup("State");
|
||||
}
|
||||
|
||||
IpcTesterHelpers.DrawIntro(GetStateBase64Name.Label);
|
||||
if (ImGui.Button("Get##Base64Idx"))
|
||||
{
|
||||
(_lastError, _getStateString) = new GetStateBase64Name(_pluginInterface).Invoke(_gameObjectName, _key);
|
||||
_stateString = _getStateString ?? "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));
|
||||
if (_stateString == null)
|
||||
return;
|
||||
|
||||
using var p = ImRaii.Popup("State");
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
if (ImGui.Button("Copy to Clipboard"))
|
||||
ImGui.SetClipboardText(_stateString);
|
||||
if (_stateString[0] is '{')
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Copy as Base64") && _state != null)
|
||||
ImGui.SetClipboardText(DesignConverter.ToBase64(_state));
|
||||
}
|
||||
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGuiUtil.TextWrapped(_stateString ?? string.Empty);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,9 @@ public unsafe class ModelEvaluationPanel(
|
|||
ImGuiUtil.DrawTableColumn("Scale");
|
||||
ImGuiUtil.DrawTableColumn(actor.Valid ? actor.AsObject->Scale.ToString(CultureInfo.InvariantCulture) : "No Character");
|
||||
ImGuiUtil.DrawTableColumn(model.Valid ? model.AsDrawObject->Object.Scale.ToString() : "No Model");
|
||||
ImGuiUtil.DrawTableColumn(model.IsCharacterBase ? $"{*(float*)(model.Address + 0x270)} {*(float*)(model.Address + 0x274)}" : "No CharacterBase");
|
||||
ImGuiUtil.DrawTableColumn(model.IsCharacterBase
|
||||
? $"{*(float*)(model.Address + 0x270)} {*(float*)(model.Address + 0x274)}"
|
||||
: "No CharacterBase");
|
||||
}
|
||||
|
||||
private void DrawParameters(Actor actor, Model model)
|
||||
|
|
@ -229,7 +231,7 @@ public unsafe class ModelEvaluationPanel(
|
|||
? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData
|
||||
: new CustomizeArray();
|
||||
var modelCustomize = model.IsHuman
|
||||
? *(CustomizeArray*)model.AsHuman->Customize.Data
|
||||
? *(CustomizeArray*)&model.AsHuman->Customize
|
||||
: new CustomizeArray();
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
|
|||
if (ImGui.SmallButton("Reattach"))
|
||||
_penumbra.Reattach();
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Version");
|
||||
ImGuiUtil.DrawTableColumn($"{_penumbra.CurrentMajor}.{_penumbra.CurrentMinor}");
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Attached When");
|
||||
ImGuiUtil.DrawTableColumn(_penumbra.AttachTime.ToLocalTime().ToLongTimeString());
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Draw Object");
|
||||
ImGui.TableNextColumn();
|
||||
var address = _drawObject.Address;
|
||||
|
|
|
|||
|
|
@ -136,6 +136,13 @@ public class DesignDetailTab
|
|||
if (hovered || ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Display or hide this design in your quick design bar.");
|
||||
|
||||
var forceRedraw = _selector.Selected!.ForcedRedraw;
|
||||
ImGuiUtil.DrawFrameColumn("Force Redrawing");
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Checkbox("##ForceRedraw", ref forceRedraw))
|
||||
_manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw);
|
||||
ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means.");
|
||||
|
||||
ImGuiUtil.DrawFrameColumn("Color");
|
||||
var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color;
|
||||
ImGui.TableNextColumn();
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
AddButton(ImportDesignButton, 10);
|
||||
AddButton(CloneDesignButton, 20);
|
||||
AddButton(DeleteButton, 1000);
|
||||
UnsubscribeRightClickLeaf(RenameLeaf);
|
||||
SetRenameSearchPath(_config.ShowRename);
|
||||
SetFilterTooltip();
|
||||
|
||||
if (_config.Ephemeral.SelectedDesign == Guid.Empty)
|
||||
|
|
@ -74,6 +76,59 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
SelectByValue(design);
|
||||
}
|
||||
|
||||
public void SetRenameSearchPath(RenameField value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case RenameField.RenameSearchPath:
|
||||
SubscribeRightClickLeaf(RenameLeafDesign, 1000);
|
||||
UnsubscribeRightClickLeaf(RenameDesign);
|
||||
break;
|
||||
case RenameField.RenameData:
|
||||
UnsubscribeRightClickLeaf(RenameLeafDesign);
|
||||
SubscribeRightClickLeaf(RenameDesign, 1000);
|
||||
break;
|
||||
case RenameField.BothSearchPathPrio:
|
||||
UnsubscribeRightClickLeaf(RenameLeafDesign);
|
||||
UnsubscribeRightClickLeaf(RenameDesign);
|
||||
SubscribeRightClickLeaf(RenameLeafDesign, 1001);
|
||||
SubscribeRightClickLeaf(RenameDesign, 1000);
|
||||
break;
|
||||
case RenameField.BothDataPrio:
|
||||
UnsubscribeRightClickLeaf(RenameLeafDesign);
|
||||
UnsubscribeRightClickLeaf(RenameDesign);
|
||||
SubscribeRightClickLeaf(RenameLeafDesign, 1000);
|
||||
SubscribeRightClickLeaf(RenameDesign, 1001);
|
||||
break;
|
||||
default:
|
||||
UnsubscribeRightClickLeaf(RenameLeafDesign);
|
||||
UnsubscribeRightClickLeaf(RenameDesign);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RenameLeafDesign(DesignFileSystem.Leaf leaf)
|
||||
{
|
||||
ImGui.Separator();
|
||||
RenameLeaf(leaf);
|
||||
}
|
||||
|
||||
private void RenameDesign(DesignFileSystem.Leaf leaf)
|
||||
{
|
||||
ImGui.Separator();
|
||||
var currentName = leaf.Value.Name.Text;
|
||||
if (ImGui.IsWindowAppearing())
|
||||
ImGui.SetKeyboardFocusHere(0);
|
||||
ImGui.TextUnformatted("Rename Design:");
|
||||
if (ImGui.InputText("##RenameDesign", ref currentName, 256, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
{
|
||||
_designManager.Rename(leaf.Value, currentName);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip("Enter a new name here to rename the changed design.");
|
||||
}
|
||||
|
||||
protected override void Select(FileSystem<Design>.Leaf? leaf, bool clear, in DesignState storage = default)
|
||||
{
|
||||
base.Select(leaf, clear, storage);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using OtterGui;
|
|||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using System;
|
||||
using static Glamourer.Gui.Tabs.HeaderDrawer;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||
|
|
@ -38,8 +37,8 @@ public class DesignPanel
|
|||
private readonly CustomizeParameterDrawer _parameterDrawer;
|
||||
private readonly DesignLinkDrawer _designLinkDrawer;
|
||||
private readonly MaterialDrawer _materials;
|
||||
private readonly Button[] _leftButtons;
|
||||
private readonly Button[] _rightButtons;
|
||||
private readonly Button[] _leftButtons;
|
||||
private readonly Button[] _rightButtons;
|
||||
|
||||
|
||||
public DesignPanel(DesignFileSystemSelector selector,
|
||||
|
|
@ -78,6 +77,7 @@ public class DesignPanel
|
|||
new SetFromClipboardButton(this),
|
||||
new UndoButton(this),
|
||||
new ExportToClipboardButton(this),
|
||||
new ApplyCharacterButton(this),
|
||||
];
|
||||
_rightButtons =
|
||||
[
|
||||
|
|
@ -561,4 +561,35 @@ public class DesignPanel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ApplyCharacterButton(DesignPanel panel) : Button
|
||||
{
|
||||
public override bool Visible
|
||||
=> panel._selector.Selected != null && panel._objects.Player.Valid;
|
||||
|
||||
protected override string Description
|
||||
=> "Overwrite this design with your character's current state.";
|
||||
|
||||
protected override FontAwesomeIcon Icon
|
||||
=> FontAwesomeIcon.UserEdit;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
var (player, actor) = panel._objects.PlayerData;
|
||||
if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state))
|
||||
throw new Exception("No player state available.");
|
||||
|
||||
var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state))
|
||||
?? throw new Exception("The clipboard did not contain valid data.");
|
||||
panel._manager.ApplyDesign(panel._selector.Selected!, design);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selector.Selected!.Name}.",
|
||||
$"Could not apply player state to design {panel._selector.Selected!.Identifier}", NotificationType.Error, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,24 +11,14 @@ using OtterGui.Raii;
|
|||
|
||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||
|
||||
public class ModAssociationsTab
|
||||
public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager)
|
||||
{
|
||||
private readonly PenumbraService _penumbra;
|
||||
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);
|
||||
}
|
||||
private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log);
|
||||
private (Mod, ModSettings)[]? _copy;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var h = ImRaii.CollapsingHeader("Mod Associations");
|
||||
using var h = ImRaii.CollapsingHeader("Mod Associations");
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"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"
|
||||
|
|
@ -39,49 +29,78 @@ public class ModAssociationsTab
|
|||
|
||||
DrawApplyAllButton();
|
||||
DrawTable();
|
||||
DrawCopyButtons();
|
||||
}
|
||||
|
||||
private void DrawCopyButtons()
|
||||
{
|
||||
var size = new Vector2((ImGui.GetContentRegionAvail().X - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0);
|
||||
if (ImGui.Button("Copy All to Clipboard", size))
|
||||
_copy = selector.Selected!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Add from Clipboard", size,
|
||||
_copy != null
|
||||
? $"Add {_copy.Length} mod association(s) from clipboard."
|
||||
: "Copy some mod associations to the clipboard, first.", _copy == null))
|
||||
foreach (var (mod, setting) in _copy!)
|
||||
manager.UpdateMod(selector.Selected!, mod, setting);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton("Set from Clipboard", size,
|
||||
_copy != null
|
||||
? $"Set {_copy.Length} mod association(s) from clipboard and discard existing."
|
||||
: "Copy some mod associations to the clipboard, first.", _copy == null))
|
||||
{
|
||||
while (selector.Selected!.AssociatedMods.Count > 0)
|
||||
manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]);
|
||||
foreach (var (mod, setting) in _copy!)
|
||||
manager.AddMod(selector.Selected!, mod, setting);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawApplyAllButton()
|
||||
{
|
||||
var current = _penumbra.CurrentCollection;
|
||||
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {current}##applyAll",
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, current is "<Unavailable>"))
|
||||
var (id, name) = penumbra.CurrentCollection;
|
||||
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll",
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty))
|
||||
ApplyAll();
|
||||
}
|
||||
|
||||
public void DrawApplyButton()
|
||||
{
|
||||
var current = _penumbra.CurrentCollection;
|
||||
var (id, name) = penumbra.CurrentCollection;
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply Mod Associations", Vector2.Zero,
|
||||
$"Try to apply all associated mod settings to Penumbras current collection {current}",
|
||||
_selector.Selected!.AssociatedMods.Count == 0 || current is "<Unavailable>"))
|
||||
$"Try to apply all associated mod settings to Penumbras current collection {name}",
|
||||
selector.Selected!.AssociatedMods.Count == 0 || id == Guid.Empty))
|
||||
ApplyAll();
|
||||
}
|
||||
|
||||
public void ApplyAll()
|
||||
{
|
||||
foreach (var (mod, settings) in _selector.Selected!.AssociatedMods)
|
||||
_penumbra.SetMod(mod, settings);
|
||||
foreach (var (mod, settings) in selector.Selected!.AssociatedMods)
|
||||
penumbra.SetMod(mod, settings);
|
||||
}
|
||||
|
||||
private void DrawTable()
|
||||
{
|
||||
using var table = ImRaii.Table("Mods", 7, ImGuiTableFlags.RowBg);
|
||||
using var table = ImRaii.Table("Mods", 5, ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("##Delete", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
||||
ImGui.TableSetupColumn("##Update", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
||||
ImGui.TableSetupColumn("##Buttons", ImGuiTableColumnFlags.WidthFixed,
|
||||
ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2);
|
||||
ImGui.TableSetupColumn("Mod Name", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Directory Name", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("State").X);
|
||||
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Priority").X);
|
||||
ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X);
|
||||
ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Applym").X);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
Mod? removedMod = null;
|
||||
Mod? removedMod = 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);
|
||||
DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp);
|
||||
|
|
@ -94,10 +113,10 @@ public class ModAssociationsTab
|
|||
DrawNewModRow();
|
||||
|
||||
if (removedMod.HasValue)
|
||||
_manager.RemoveMod(_selector.Selected!, removedMod.Value);
|
||||
|
||||
manager.RemoveMod(selector.Selected!, removedMod.Value);
|
||||
|
||||
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)
|
||||
|
|
@ -105,22 +124,28 @@ public class ModAssociationsTab
|
|||
removedMod = null;
|
||||
updatedMod = null;
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||
"Delete this mod from associations", false, true))
|
||||
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
||||
var spacing = ImGui.GetStyle().ItemInnerSpacing.X;
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize,
|
||||
"Delete this mod from associations.", false, true))
|
||||
removedMod = mod;
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||
"Update the settings of this mod association", false, true);
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize,
|
||||
"Copy this mod setting to clipboard.", false, true))
|
||||
_copy = [(mod, settings)];
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), buttonSize,
|
||||
"Update the settings of this mod association.", false, true);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
var (_, newSettings) = _penumbra.GetMods().FirstOrDefault(m => m.Mod == mod);
|
||||
var newSettings = penumbra.GetModSettings(mod);
|
||||
if (ImGui.IsItemClicked())
|
||||
updatedMod = (mod, newSettings);
|
||||
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale);
|
||||
using var tt = ImRaii.Tooltip();
|
||||
using var tt = ImRaii.Tooltip();
|
||||
ImGui.Separator();
|
||||
var namesDifferent = mod.Name != mod.DirectoryName;
|
||||
ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0));
|
||||
|
|
@ -143,17 +168,13 @@ public class ModAssociationsTab
|
|||
ModCombo.DrawSettingsRight(newSettings);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var selected = ImGui.Selectable($"{mod.Name}##name");
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
ImGui.TableNextColumn();
|
||||
selected |= ImGui.Selectable($"{mod.DirectoryName}##directory");
|
||||
hovered |= ImGui.IsItemHovered();
|
||||
if (selected)
|
||||
_penumbra.OpenModPage(mod);
|
||||
if (hovered)
|
||||
ImGui.SetTooltip("Click to open mod page in Penumbra.");
|
||||
if (ImGui.Selectable($"{mod.Name}##name"))
|
||||
penumbra.OpenModPage(mod);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra.");
|
||||
ImGui.TableNextColumn();
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
|
|
@ -163,10 +184,10 @@ public class ModAssociationsTab
|
|||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.RightAlign(settings.Priority.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty,
|
||||
!_penumbra.Available))
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty,
|
||||
!penumbra.Available))
|
||||
{
|
||||
var text = _penumbra.SetMod(mod, settings);
|
||||
var text = penumbra.SetMod(mod, settings);
|
||||
if (text.Length > 0)
|
||||
Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false);
|
||||
}
|
||||
|
|
@ -199,16 +220,15 @@ public class ModAssociationsTab
|
|||
{
|
||||
var currentName = _modCombo.CurrentSelection.Mod.Name;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
var tt = currentName.IsNullOrEmpty()
|
||||
? "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."
|
||||
: string.Empty;
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, tt.Length > 0,
|
||||
true))
|
||||
_manager.AddMod(_selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings);
|
||||
manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings);
|
||||
ImGui.TableNextColumn();
|
||||
_modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty,
|
||||
ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight());
|
||||
|
|
|
|||
26
Glamourer/Gui/Tabs/DesignTab/RenameField.cs
Normal file
26
Glamourer/Gui/Tabs/DesignTab/RenameField.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||
|
||||
public enum RenameField
|
||||
{
|
||||
None,
|
||||
RenameSearchPath,
|
||||
RenameData,
|
||||
BothSearchPathPrio,
|
||||
BothDataPrio,
|
||||
}
|
||||
|
||||
public static class RenameFieldExtensions
|
||||
{
|
||||
public static (string Name, string Desc) GetData(this RenameField value)
|
||||
=> value switch
|
||||
{
|
||||
RenameField.None => ("None", "Show no rename fields in the context menu for designs."),
|
||||
RenameField.RenameSearchPath => ("Search Path", "Show only the search path / move field in the context menu for designs."),
|
||||
RenameField.RenameData => ("Design Name", "Show only the design name field in the context menu for designs."),
|
||||
RenameField.BothSearchPathPrio => ("Both (Focus Search Path)",
|
||||
"Show both rename fields in the context menu for designs, but put the keyboard cursor on the search path field."),
|
||||
RenameField.BothDataPrio => ("Both (Focus Design Name)",
|
||||
"Show both rename fields in the context menu for designs, but put the keyboard cursor on the design name field"),
|
||||
_ => (string.Empty, string.Empty),
|
||||
};
|
||||
}
|
||||
195
Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs
Normal file
195
Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
using Dalamud.Interface;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Text.EndObjects;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.SettingsTab;
|
||||
|
||||
public class CodeDrawer(Configuration config, CodeService codeService, FunModule funModule) : IUiService
|
||||
{
|
||||
private static ReadOnlySpan<byte> Tooltip
|
||||
=> "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. "u8
|
||||
+ "They allow for some fun easter-egg modes that usually manipulate the appearance of all players you see (including yourself) in some way."u8;
|
||||
|
||||
private static ReadOnlySpan<byte> DragDropLabel
|
||||
=> "##CheatDrag"u8;
|
||||
|
||||
private bool _showCodeHints;
|
||||
private string _currentCode = string.Empty;
|
||||
private int _dragCodeIdx = -1;
|
||||
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
var show = ImGui.CollapsingHeader("Cheat Codes");
|
||||
DrawTooltip();
|
||||
|
||||
if (!show)
|
||||
return;
|
||||
|
||||
DrawCodeInput();
|
||||
DrawCopyButtons();
|
||||
var knownFlags = DrawCodes();
|
||||
DrawCodeHints(knownFlags);
|
||||
}
|
||||
|
||||
private void DrawCodeInput()
|
||||
{
|
||||
var color = codeService.CheckCode(_currentCode).Item2 is not 0 ? ColorId.ActorAvailable : ColorId.ActorUnavailable;
|
||||
using var border = ImRaii.PushFrameBorder(ImUtf8.GlobalScale, color.Value(), _currentCode.Length > 0);
|
||||
ImGui.SetNextItemWidth(500 * ImUtf8.GlobalScale + ImUtf8.ItemSpacing.X);
|
||||
if (ImUtf8.InputText("##Code"u8, ref _currentCode, "Enter Cheat Code..."u8, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
{
|
||||
codeService.AddCode(_currentCode);
|
||||
_currentCode = string.Empty;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
DrawTooltip();
|
||||
}
|
||||
|
||||
private void DrawCopyButtons()
|
||||
{
|
||||
var buttonSize = new Vector2(250 * ImUtf8.GlobalScale, 0);
|
||||
if (ImUtf8.Button("Who am I?!?"u8, buttonSize))
|
||||
funModule.WhoAmI();
|
||||
ImUtf8.HoverTooltip(
|
||||
"Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImUtf8.Button("Who is that!?!"u8, buttonSize))
|
||||
funModule.WhoIsThat();
|
||||
ImUtf8.HoverTooltip(
|
||||
"Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8);
|
||||
}
|
||||
|
||||
private CodeService.CodeFlag DrawCodes()
|
||||
{
|
||||
var canDelete = config.DeleteDesignModifier.IsActive();
|
||||
CodeService.CodeFlag knownFlags = 0;
|
||||
for (var i = 0; i < config.Codes.Count; ++i)
|
||||
{
|
||||
using var id = ImUtf8.PushId(i);
|
||||
var (code, state) = config.Codes[i];
|
||||
var (action, flag) = codeService.CheckCode(code);
|
||||
if (flag is 0)
|
||||
continue;
|
||||
|
||||
var data = CodeService.GetData(flag);
|
||||
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Trash,
|
||||
$"Delete this cheat code.{(canDelete ? string.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}",
|
||||
!canDelete))
|
||||
{
|
||||
action!(false);
|
||||
config.Codes.RemoveAt(i--);
|
||||
codeService.SaveState();
|
||||
}
|
||||
|
||||
knownFlags |= flag;
|
||||
ImUtf8.SameLineInner();
|
||||
if (ImUtf8.Checkbox("\0"u8, ref state))
|
||||
{
|
||||
action!(state);
|
||||
codeService.SaveState();
|
||||
}
|
||||
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
ImGui.SameLine();
|
||||
ImUtf8.Selectable(code, false);
|
||||
hovered |= ImGui.IsItemHovered();
|
||||
DrawSource(i, code);
|
||||
DrawTarget(i);
|
||||
if (hovered)
|
||||
{
|
||||
using var tt = ImUtf8.Tooltip();
|
||||
ImUtf8.Text(data.Effect);
|
||||
}
|
||||
}
|
||||
|
||||
return knownFlags;
|
||||
}
|
||||
|
||||
private void DrawSource(int idx, string code)
|
||||
{
|
||||
using var source = ImUtf8.DragDropSource();
|
||||
if (!source)
|
||||
return;
|
||||
|
||||
if (!DragDropSource.SetPayload(DragDropLabel))
|
||||
_dragCodeIdx = idx;
|
||||
ImUtf8.Text($"Dragging {code}...");
|
||||
}
|
||||
|
||||
private void DrawTarget(int idx)
|
||||
{
|
||||
using var target = ImUtf8.DragDropTarget();
|
||||
if (!target.IsDropping(DragDropLabel) || _dragCodeIdx == -1)
|
||||
return;
|
||||
|
||||
if (config.Codes.Move(_dragCodeIdx, idx))
|
||||
codeService.SaveState();
|
||||
_dragCodeIdx = -1;
|
||||
}
|
||||
|
||||
private void DrawCodeHints(CodeService.CodeFlag knownFlags)
|
||||
{
|
||||
if (knownFlags.HasFlag(CodeService.AllHintCodes))
|
||||
return;
|
||||
|
||||
if (ImUtf8.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8))
|
||||
_showCodeHints = !_showCodeHints;
|
||||
|
||||
if (!_showCodeHints)
|
||||
return;
|
||||
|
||||
foreach (var code in Enum.GetValues<CodeService.CodeFlag>())
|
||||
{
|
||||
if (knownFlags.HasFlag(code))
|
||||
continue;
|
||||
|
||||
var data = CodeService.GetData(code);
|
||||
if (!data.Display)
|
||||
continue;
|
||||
|
||||
ImGui.Dummy(Vector2.Zero);
|
||||
ImGui.Separator();
|
||||
ImGui.Dummy(Vector2.Zero);
|
||||
ImUtf8.Text(data.Effect);
|
||||
using var indent = ImRaii.PushIndent(2);
|
||||
using (ImUtf8.Group())
|
||||
{
|
||||
ImUtf8.Text("Capitalized letters: "u8);
|
||||
ImUtf8.Text("Punctuation: "u8);
|
||||
}
|
||||
|
||||
ImUtf8.SameLineInner();
|
||||
using (ImUtf8.Group())
|
||||
{
|
||||
using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImUtf8.Text($"{data.CapitalCount}");
|
||||
ImUtf8.Text($"{data.Punctuation}");
|
||||
}
|
||||
|
||||
ImUtf8.TextWrapped(data.Hint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void DrawTooltip()
|
||||
{
|
||||
if (!ImGui.IsItemHovered())
|
||||
return;
|
||||
|
||||
ImGui.SetNextWindowSize(new Vector2(400, 0));
|
||||
using var tt = ImUtf8.Tooltip();
|
||||
ImUtf8.TextWrapped(Tooltip);
|
||||
}
|
||||
}
|
||||
36
Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs
Normal file
36
Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Style;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
using ObjectManager = Glamourer.Interop.ObjectManager;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.SettingsTab;
|
||||
|
||||
|
|
@ -15,13 +14,14 @@ public class CollectionOverrideDrawer(
|
|||
CollectionOverrideService collectionOverrides,
|
||||
Configuration config,
|
||||
ObjectManager objects,
|
||||
ActorManager actors) : IService
|
||||
ActorManager actors,
|
||||
PenumbraService penumbra,
|
||||
CollectionCombo combo) : IService
|
||||
{
|
||||
private string _newIdentifier = string.Empty;
|
||||
private ActorIdentifier[] _identifiers = [];
|
||||
private int _dragDropIndex = -1;
|
||||
private Exception? _exception;
|
||||
private string _collection = string.Empty;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
|
|
@ -32,58 +32,113 @@ public class CollectionOverrideDrawer(
|
|||
if (!header)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg);
|
||||
using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
||||
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("name", ImGuiTableColumnFlags.WidthStretch, 0.25f);
|
||||
|
||||
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];
|
||||
using var id = ImRaii.PushId(i);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true))
|
||||
collectionOverrides.DeleteOverride(i--);
|
||||
var (guid, _, newName) = combo.CurrentSelection;
|
||||
collectionOverrides.ChangeOverride(idx, guid, newName);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Selectable(config.Ephemeral.IncognitoMode ? identifier.Incognito(null) : identifier.ToString());
|
||||
|
||||
using (var target = ImRaii.DragDropTarget())
|
||||
{
|
||||
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);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TextUnformatted($" {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();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PersonCirclePlus.ToIconString(), buttonSize, "Add override for current player.",
|
||||
!objects.Player.Valid, true))
|
||||
collectionOverrides.AddOverride([objects.PlayerData.Identifier], _collection.Length > 0 ? _collection : "TempCollection");
|
||||
!objects.Player.Valid && currentId != Guid.Empty, true))
|
||||
collectionOverrides.AddOverride([objects.PlayerData.Identifier], currentId, currentName);
|
||||
|
||||
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))
|
||||
try
|
||||
{
|
||||
|
|
@ -91,7 +146,7 @@ public class CollectionOverrideDrawer(
|
|||
}
|
||||
catch (ActorIdentifierFactory.IdentifierParseError e)
|
||||
{
|
||||
_exception = e;
|
||||
_exception = e;
|
||||
_identifiers = [];
|
||||
}
|
||||
|
||||
|
|
@ -101,9 +156,9 @@ public class CollectionOverrideDrawer(
|
|||
? "Please enter an identifier string first."
|
||||
: $"The identifier string {_newIdentifier} does not result in a valid identifier{(_exception == null ? "." : $":\n\n{_exception?.Message}")}";
|
||||
|
||||
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is 'T', true))
|
||||
collectionOverrides.AddOverride(_identifiers, _collection.Length > 0 ? _collection : "TempCollection");
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is not 'A', true))
|
||||
collectionOverrides.AddOverride(_identifiers, currentId, currentName);
|
||||
|
||||
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
|
|
@ -114,9 +169,5 @@ public class CollectionOverrideDrawer(
|
|||
|
||||
if (ImGui.IsItemHovered())
|
||||
ActorIdentifierFactory.WriteUserStringTooltip(false);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
ImGui.InputTextWithHint("##collection", "Enter Collection...", ref _collection, 80);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ using Glamourer.Designs;
|
|||
using Glamourer.Gui.Tabs.DesignTab;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.PalettePlus;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -19,16 +17,15 @@ namespace Glamourer.Gui.Tabs.SettingsTab;
|
|||
public class SettingsTab(
|
||||
Configuration config,
|
||||
DesignFileSystemSelector selector,
|
||||
CodeService codeService,
|
||||
ContextMenuService contextMenuService,
|
||||
UiBuilder uiBuilder,
|
||||
GlamourerChangelog changelog,
|
||||
FunModule funModule,
|
||||
IKeyState keys,
|
||||
DesignColorUi designColorUi,
|
||||
PaletteImport paletteImport,
|
||||
PalettePlusChecker paletteChecker,
|
||||
CollectionOverrideDrawer overrides)
|
||||
CollectionOverrideDrawer overrides,
|
||||
CodeDrawer codeDrawer)
|
||||
: ITab
|
||||
{
|
||||
private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray();
|
||||
|
|
@ -36,8 +33,6 @@ public class SettingsTab(
|
|||
public ReadOnlySpan<byte> Label
|
||||
=> "Settings"u8;
|
||||
|
||||
private string _currentCode = string.Empty;
|
||||
|
||||
public void DrawContent()
|
||||
{
|
||||
using var child = ImRaii.Child("MainWindowChild");
|
||||
|
|
@ -57,7 +52,7 @@ public class SettingsTab(
|
|||
DrawInterfaceSettings();
|
||||
DrawColorSettings();
|
||||
overrides.Draw();
|
||||
DrawCodes();
|
||||
codeDrawer.Draw();
|
||||
}
|
||||
|
||||
MainWindow.DrawSupportButtons(changelog.Changelog);
|
||||
|
|
@ -161,6 +156,7 @@ public class SettingsTab(
|
|||
|
||||
Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.",
|
||||
config.SmallEquip, v => config.SmallEquip = v);
|
||||
DrawHeightUnitSettings();
|
||||
Checkbox("Show Application Checkboxes",
|
||||
"Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules.",
|
||||
!config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v);
|
||||
|
|
@ -168,6 +164,7 @@ public class SettingsTab(
|
|||
"A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale,
|
||||
config.DeleteDesignModifier, v => config.DeleteDesignModifier = v))
|
||||
config.Save();
|
||||
DrawRenameSettings();
|
||||
Checkbox("Auto-Open Design Folders",
|
||||
"Have design folders open or closed as their default state after launching.", config.OpenFoldersByDefault,
|
||||
v => config.OpenFoldersByDefault = v);
|
||||
|
|
@ -295,69 +292,6 @@ public class SettingsTab(
|
|||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
private void DrawCodes()
|
||||
{
|
||||
const string tooltip =
|
||||
"Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. They allow for some fun easter-egg modes that usually manipulate the appearance of all players you see (including yourself) in some way.\n\n"
|
||||
+ "Cheat Codes are generally pop culture references, but it is unlikely you will be able to guess any of them based on nothing. Some codes have been published on the discord server, but other than that, we are still undecided on how and when to publish them or add any new ones. Maybe some will be hidden in the change logs or on the help pages. Or maybe I will just add hints in this section later on.\n\n"
|
||||
+ "In any case, you are not losing out on anything important if you never look at this section and there is no real reason to go on a treasure hunt for them. It is mostly something I added because it was fun for me.";
|
||||
|
||||
var show = ImGui.CollapsingHeader("Cheat Codes");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetNextWindowSize(new Vector2(400, 0));
|
||||
using var tt = ImRaii.Tooltip();
|
||||
ImGuiUtil.TextWrapped(tooltip);
|
||||
}
|
||||
|
||||
if (!show)
|
||||
return;
|
||||
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, _currentCode.Length > 0))
|
||||
{
|
||||
var color = codeService.CheckCode(_currentCode) != null ? ColorId.ActorAvailable : ColorId.ActorUnavailable;
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Border, color.Value(), _currentCode.Length > 0);
|
||||
if (ImGui.InputTextWithHint("##Code", "Enter Cheat Code...", ref _currentCode, 512, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
if (codeService.AddCode(_currentCode))
|
||||
_currentCode = string.Empty;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker(tooltip);
|
||||
|
||||
DrawCodeHints();
|
||||
|
||||
if (config.Codes.Count <= 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < config.Codes.Count; ++i)
|
||||
{
|
||||
var (code, state) = config.Codes[i];
|
||||
var action = codeService.CheckCode(code);
|
||||
if (action == null)
|
||||
continue;
|
||||
|
||||
if (ImGui.Checkbox(code, ref state))
|
||||
{
|
||||
action(state);
|
||||
codeService.SaveState();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Button("Who am I?!?"))
|
||||
funModule.WhoAmI();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Who is that!?!"))
|
||||
funModule.WhoIsThat();
|
||||
}
|
||||
|
||||
private void DrawCodeHints()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Checkbox(string label, string tooltip, bool current, Action<bool> setter)
|
||||
{
|
||||
|
|
@ -411,4 +345,68 @@ public class SettingsTab(
|
|||
|
||||
ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the designs tab.");
|
||||
}
|
||||
|
||||
private void DrawRenameSettings()
|
||||
{
|
||||
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||
using (var combo = ImRaii.Combo("##renameSettings", config.ShowRename.GetData().Name))
|
||||
{
|
||||
if (combo)
|
||||
foreach (var value in Enum.GetValues<RenameField>())
|
||||
{
|
||||
var (name, desc) = value.GetData();
|
||||
if (ImGui.Selectable(name, config.ShowRename == value))
|
||||
{
|
||||
config.ShowRename = value;
|
||||
selector.SetRenameSearchPath(value);
|
||||
config.Save();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(desc);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
const string tt =
|
||||
"Select which of the two renaming input fields are visible when opening the right-click context menu of a design in the design selector.";
|
||||
ImGuiComponents.HelpMarker(tt);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Rename Fields in Design Context Menu");
|
||||
ImGuiUtil.HoverTooltip(tt);
|
||||
}
|
||||
|
||||
private void DrawHeightUnitSettings()
|
||||
{
|
||||
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||
using (var combo = ImRaii.Combo("##heightUnit", HeightDisplayTypeName(config.HeightDisplayType)))
|
||||
{
|
||||
if (combo)
|
||||
foreach (var type in Enum.GetValues<HeightDisplayType>())
|
||||
{
|
||||
if (ImGui.Selectable(HeightDisplayTypeName(type), type == config.HeightDisplayType) && type != config.HeightDisplayType)
|
||||
{
|
||||
config.HeightDisplayType = type;
|
||||
config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
const string tt = "Select how to display the height of characters in real-world units, if at all.";
|
||||
ImGuiComponents.HelpMarker(tt);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Character Height Display Type");
|
||||
ImGuiUtil.HoverTooltip(tt);
|
||||
}
|
||||
|
||||
private string HeightDisplayTypeName(HeightDisplayType type)
|
||||
=> type switch
|
||||
{
|
||||
HeightDisplayType.None => "Do Not Display",
|
||||
HeightDisplayType.Centimetre => "Centimetres (000.0 cm)",
|
||||
HeightDisplayType.Metre => "Metres (0.00 m)",
|
||||
HeightDisplayType.Wrong => "Inches (00.0 in)",
|
||||
HeightDisplayType.WrongFoot => "Feet (0'00'')",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,18 +73,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
|
|||
}
|
||||
|
||||
var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1];
|
||||
Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->MainHand);
|
||||
Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[10], ref entry->OffHand);
|
||||
Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->Head);
|
||||
Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->Body);
|
||||
Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->Hands);
|
||||
Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->Legs);
|
||||
Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->Feet);
|
||||
Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->Ears);
|
||||
Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->Neck);
|
||||
Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->Wrists);
|
||||
Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->RingRight);
|
||||
Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->RingLeft);
|
||||
Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->ItemsSpan[0]);
|
||||
Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[1], ref entry->ItemsSpan[1]);
|
||||
Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->ItemsSpan[2]);
|
||||
Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->ItemsSpan[3]);
|
||||
Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->ItemsSpan[5]);
|
||||
Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->ItemsSpan[6]);
|
||||
Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->ItemsSpan[7]);
|
||||
Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->ItemsSpan[8]);
|
||||
Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->ItemsSpan[9]);
|
||||
Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->ItemsSpan[10]);
|
||||
Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->ItemsSpan[11]);
|
||||
Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->ItemsSpan[12]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -98,18 +98,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
|
|||
_itemList.Add((slot, FixId(item.ItemID), item.Stain));
|
||||
}
|
||||
|
||||
Add(EquipSlot.MainHand, ref entry->MainHand);
|
||||
Add(EquipSlot.OffHand, ref entry->OffHand);
|
||||
Add(EquipSlot.Head, ref entry->Head);
|
||||
Add(EquipSlot.Body, ref entry->Body);
|
||||
Add(EquipSlot.Hands, ref entry->Hands);
|
||||
Add(EquipSlot.Legs, ref entry->Legs);
|
||||
Add(EquipSlot.Feet, ref entry->Feet);
|
||||
Add(EquipSlot.Ears, ref entry->Ears);
|
||||
Add(EquipSlot.Neck, ref entry->Neck);
|
||||
Add(EquipSlot.Wrists, ref entry->Wrists);
|
||||
Add(EquipSlot.RFinger, ref entry->RingRight);
|
||||
Add(EquipSlot.LFinger, ref entry->RingLeft);
|
||||
Add(EquipSlot.MainHand, ref entry->ItemsSpan[0]);
|
||||
Add(EquipSlot.OffHand, ref entry->ItemsSpan[1]);
|
||||
Add(EquipSlot.Head, ref entry->ItemsSpan[2]);
|
||||
Add(EquipSlot.Body, ref entry->ItemsSpan[3]);
|
||||
Add(EquipSlot.Hands, ref entry->ItemsSpan[5]);
|
||||
Add(EquipSlot.Legs, ref entry->ItemsSpan[6]);
|
||||
Add(EquipSlot.Feet, ref entry->ItemsSpan[7]);
|
||||
Add(EquipSlot.Ears, ref entry->ItemsSpan[8]);
|
||||
Add(EquipSlot.Neck, ref entry->ItemsSpan[9]);
|
||||
Add(EquipSlot.Wrists, ref entry->ItemsSpan[10]);
|
||||
Add(EquipSlot.RFinger, ref entry->ItemsSpan[11]);
|
||||
Add(EquipSlot.LFinger, ref entry->ItemsSpan[12]);
|
||||
}
|
||||
|
||||
_movedItemsEvent.Invoke(_itemList.ToArray());
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using Lumina.Data.Files;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.String.Functions;
|
||||
using SharpGen.Runtime;
|
||||
using Vortice.Direct3D11;
|
||||
using Vortice.DXGI;
|
||||
using static Penumbra.GameData.Files.MtrlFile;
|
||||
using MapFlags = Vortice.Direct3D11.MapFlags;
|
||||
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
|
||||
|
||||
|
|
@ -14,14 +14,14 @@ namespace Glamourer.Interop.Material;
|
|||
|
||||
public unsafe class DirectXService(IFramework framework) : IService
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly ConcurrentDictionary<nint, (DateTime Update, ColorTable Table)> _textures = [];
|
||||
private readonly object _lock = new();
|
||||
private readonly ConcurrentDictionary<nint, (DateTime Update, LegacyColorTable Table)> _textures = [];
|
||||
|
||||
/// <summary> Generate a color table the way the game does inside the original texture, and release the original. </summary>
|
||||
/// <param name="original"> The original texture that will be replaced with a new one. </param>
|
||||
/// <param name="colorTable"> The input color table. </param>
|
||||
/// <returns> Success or failure. </returns>
|
||||
public bool ReplaceColorTable(Texture** original, in ColorTable colorTable)
|
||||
public bool ReplaceColorTable(Texture** original, in LegacyColorTable colorTable)
|
||||
{
|
||||
if (original == null)
|
||||
return false;
|
||||
|
|
@ -38,7 +38,7 @@ public unsafe class DirectXService(IFramework framework) : IService
|
|||
if (texture.IsInvalid)
|
||||
return false;
|
||||
|
||||
fixed (ColorTable* ptr = &colorTable)
|
||||
fixed (LegacyColorTable* ptr = &colorTable)
|
||||
{
|
||||
if (!texture.Texture->InitializeContents(ptr))
|
||||
return false;
|
||||
|
|
@ -51,7 +51,7 @@ public unsafe class DirectXService(IFramework framework) : IService
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetColorTable(Texture* texture, out ColorTable table)
|
||||
public bool TryGetColorTable(Texture* texture, out LegacyColorTable table)
|
||||
{
|
||||
if (_textures.TryGetValue((nint)texture, out var p) && framework.LastUpdateUTC == p.Update)
|
||||
{
|
||||
|
|
@ -73,7 +73,7 @@ public unsafe class DirectXService(IFramework framework) : IService
|
|||
/// <param name="texture"> A pointer to the internal texture struct containing the GPU handle. </param>
|
||||
/// <param name="table"> The returned color table. </param>
|
||||
/// <returns> Whether the table could be fetched. </returns>
|
||||
private static bool TextureColorTable(Texture* texture, out ColorTable table)
|
||||
private static bool TextureColorTable(Texture* texture, out LegacyColorTable table)
|
||||
{
|
||||
if (texture == null)
|
||||
{
|
||||
|
|
@ -114,7 +114,7 @@ public unsafe class DirectXService(IFramework framework) : IService
|
|||
}
|
||||
|
||||
/// <summary> Turn a mapped texture into a color table. </summary>
|
||||
private static ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map)
|
||||
private static LegacyColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map)
|
||||
{
|
||||
var desc = resource.Description1;
|
||||
|
||||
|
|
@ -133,14 +133,14 @@ public unsafe class DirectXService(IFramework framework) : IService
|
|||
/// <param name="height"> The height of the texture. (Needs to be 16).</param>
|
||||
/// <param name="pitch"> The stride in the texture data. </param>
|
||||
/// <returns></returns>
|
||||
private static ColorTable ReadTexture(nint data, int length, int height, int pitch)
|
||||
private static LegacyColorTable ReadTexture(nint data, int length, int height, int pitch)
|
||||
{
|
||||
// Check that the data has sufficient dimension and size.
|
||||
var expectedSize = sizeof(Half) * MaterialService.TextureWidth * height * 4;
|
||||
if (length < expectedSize || sizeof(ColorTable) != expectedSize || height != MaterialService.TextureHeight)
|
||||
if (length < expectedSize || sizeof(LegacyColorTable) != expectedSize || height != MaterialService.TextureHeight)
|
||||
return default;
|
||||
|
||||
var ret = new ColorTable();
|
||||
var ret = new LegacyColorTable();
|
||||
var target = (byte*)&ret;
|
||||
// If the stride is the same as in the table, just copy.
|
||||
if (pitch == MaterialService.TextureWidth)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop.Material;
|
||||
|
|
@ -12,12 +12,12 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
|||
private readonly IFramework _framework;
|
||||
private readonly DirectXService _directXService;
|
||||
|
||||
public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid;
|
||||
public MtrlFile.ColorTable LastOriginalColorTable { get; private set; }
|
||||
private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid;
|
||||
private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex;
|
||||
private ObjectIndex _objectIndex = ObjectIndex.AnyIndex;
|
||||
private MtrlFile.ColorTable _originalColorTable;
|
||||
public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid;
|
||||
public LegacyColorTable LastOriginalColorTable { get; private set; }
|
||||
private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid;
|
||||
private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex;
|
||||
private ObjectIndex _objectIndex = ObjectIndex.AnyIndex;
|
||||
private LegacyColorTable _originalColorTable;
|
||||
|
||||
public LiveColorTablePreviewer(global::Penumbra.GameData.Interop.ObjectManager objects, IFramework framework, DirectXService directXService)
|
||||
{
|
||||
|
|
@ -78,7 +78,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||
for (var i = 0; i < LegacyColorTable.NumUsedRows; ++i)
|
||||
{
|
||||
table[i].Diffuse = diffuse;
|
||||
table[i].Emissive = emissive;
|
||||
|
|
@ -92,7 +92,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
|||
_objectIndex = ObjectIndex.AnyIndex;
|
||||
}
|
||||
|
||||
public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, MtrlFile.ColorTable table)
|
||||
public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, LegacyColorTable table)
|
||||
{
|
||||
if (_valueIndex.DrawObject is not MaterialValueIndex.DrawObjectType.Invalid)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Glamourer.State;
|
|||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
|||
|
||||
/// <summary> Update and apply the glamourer state of an actor according to the application sources when updated by the game. </summary>
|
||||
private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData,
|
||||
ref MtrlFile.ColorTable colorTable)
|
||||
ref LegacyColorTable colorTable)
|
||||
{
|
||||
var deleteList = _deleteList.Value!;
|
||||
deleteList.Clear();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Interop;
|
||||
using static Penumbra.GameData.Files.MtrlFile;
|
||||
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
|
||||
|
||||
namespace Glamourer.Interop.Material;
|
||||
|
|
@ -10,10 +10,10 @@ namespace Glamourer.Interop.Material;
|
|||
public static unsafe class MaterialService
|
||||
{
|
||||
public const int TextureWidth = 4;
|
||||
public const int TextureHeight = ColorTable.NumRows;
|
||||
public const int TextureHeight = LegacyColorTable.NumUsedRows;
|
||||
public const int MaterialsPerModel = 4;
|
||||
|
||||
public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture)
|
||||
public static bool GenerateNewColorTable(in LegacyColorTable colorTable, out Texture* texture)
|
||||
{
|
||||
var textureSize = stackalloc int[2];
|
||||
textureSize[0] = TextureWidth;
|
||||
|
|
@ -24,7 +24,7 @@ public static unsafe class MaterialService
|
|||
if (texture == null)
|
||||
return false;
|
||||
|
||||
fixed (ColorTable* ptr = &colorTable)
|
||||
fixed (LegacyColorTable* ptr = &colorTable)
|
||||
{
|
||||
return texture->InitializeContents(ptr);
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ public static unsafe class MaterialService
|
|||
/// <param name="modelSlot"> The model slot. </param>
|
||||
/// <param name="materialSlot"> The material slot in the model. </param>
|
||||
/// <returns> A pointer to the color table or null. </returns>
|
||||
public static ColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot)
|
||||
public static LegacyColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot)
|
||||
{
|
||||
if (!model.IsCharacterBase)
|
||||
return null;
|
||||
|
|
@ -66,6 +66,6 @@ public static unsafe class MaterialService
|
|||
if (material == null || material->ColorTable == null)
|
||||
return null;
|
||||
|
||||
return (ColorTable*)material->ColorTable;
|
||||
return (LegacyColorTable*)material->ColorTable;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using FFXIVClientStructs.Interop;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Interop;
|
||||
|
||||
namespace Glamourer.Interop.Material;
|
||||
|
|
@ -143,7 +143,7 @@ public readonly record struct MaterialValueIndex(
|
|||
=> materialIndex < MaterialService.MaterialsPerModel;
|
||||
|
||||
public static bool ValidateRow(byte rowIndex)
|
||||
=> rowIndex < MtrlFile.ColorTable.NumRows;
|
||||
=> rowIndex < LegacyColorTable.NumUsedRows;
|
||||
|
||||
private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueDesign>;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Files;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
|
|||
public float SpecularStrength = specularStrength;
|
||||
public float GlossStrength = glossStrength;
|
||||
|
||||
public ColorRow(in MtrlFile.ColorTable.Row row)
|
||||
public ColorRow(in LegacyColorTable.Row row)
|
||||
: this(Root(row.Diffuse), Root(row.Specular), Root(row.Emissive), row.SpecularStrength, row.GlossStrength)
|
||||
{ }
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
|
|||
private static float Root(float value)
|
||||
=> value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value);
|
||||
|
||||
public readonly bool Apply(ref MtrlFile.ColorTable.Row row)
|
||||
public readonly bool Apply(ref LegacyColorTable.Row row)
|
||||
{
|
||||
var ret = false;
|
||||
var d = Square(Diffuse);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
|||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ public sealed unsafe class PrepareColorSet
|
|||
}
|
||||
|
||||
public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId,
|
||||
out MtrlFile.ColorTable table)
|
||||
out LegacyColorTable table)
|
||||
{
|
||||
if (material->ColorTable == null)
|
||||
{
|
||||
|
|
@ -63,7 +63,7 @@ public sealed unsafe class PrepareColorSet
|
|||
return false;
|
||||
}
|
||||
|
||||
var newTable = *(MtrlFile.ColorTable*)material->ColorTable;
|
||||
var newTable = *(LegacyColorTable*)material->ColorTable;
|
||||
if (stainId.Id != 0)
|
||||
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
|
||||
table = newTable;
|
||||
|
|
@ -71,11 +71,12 @@ public sealed unsafe class PrepareColorSet
|
|||
}
|
||||
|
||||
/// <summary> Assumes the actor is valid. </summary>
|
||||
public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out MtrlFile.ColorTable table)
|
||||
public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out LegacyColorTable table)
|
||||
{
|
||||
var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex;
|
||||
var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex;
|
||||
if (!index.TryGetModel(actor, out var model))
|
||||
return false;
|
||||
|
||||
var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx];
|
||||
if (handle == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,16 +22,13 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
|
|||
return;
|
||||
}
|
||||
|
||||
var collections = new HashSet<string>();
|
||||
var collections = new HashSet<Guid>();
|
||||
|
||||
foreach (var actor in data.Objects)
|
||||
{
|
||||
var (collection, overridden) = overrides.GetCollection(actor, state.Identifier);
|
||||
if (collection.Length == 0)
|
||||
{
|
||||
Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}.");
|
||||
var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier);
|
||||
if (collection == Guid.Empty)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!collections.Add(collection))
|
||||
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);
|
||||
if (collection.Length <= 0)
|
||||
return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty, false);
|
||||
var (collection, name, overridden) = overrides.GetCollection(actor);
|
||||
if (collection == Guid.Empty)
|
||||
return ([$"{actor.Utf8Name} uses no mods."], 0, Guid.Empty, string.Empty, false);
|
||||
|
||||
var messages = new List<string>();
|
||||
var appliedMods = 0;
|
||||
|
|
@ -65,6 +62,6 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O
|
|||
++appliedMods;
|
||||
}
|
||||
|
||||
return (messages, appliedMods, collection, overridden);
|
||||
return (messages, appliedMods, collection, name, overridden);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -79,14 +79,14 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
|||
if (!_objects.TryGetValue(id, out var actors) || !actors.Valid)
|
||||
continue;
|
||||
|
||||
var collection = _penumbra.GetActorCollection(actors.Objects[0]);
|
||||
if (collection != name)
|
||||
var collection = _penumbra.GetActorCollection(actors.Objects[0], out _);
|
||||
if (collection != collectionId)
|
||||
continue;
|
||||
|
||||
_actions.Enqueue((state, () =>
|
||||
{
|
||||
foreach (var actor in actors.Objects)
|
||||
_state.ReapplyState(actor, state, StateSource.IpcManual);
|
||||
_state.ReapplyState(actor, state, false, StateSource.IpcManual);
|
||||
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}.");
|
||||
}, WaitFrames));
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
|||
{
|
||||
// Only update once per frame.
|
||||
var playerName = _penumbra.GetCurrentPlayerCollection();
|
||||
if (playerName != name)
|
||||
if (playerName != collectionId)
|
||||
return;
|
||||
|
||||
var currentFrame = _framework.LastUpdateUTC;
|
||||
|
|
@ -106,7 +106,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
|||
_frame = currentFrame;
|
||||
_framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
_state.ReapplyState(_objects.Player, StateSource.IpcManual);
|
||||
_state.ReapplyState(_objects.Player, false, StateSource.IpcManual);
|
||||
Glamourer.Log.Debug(
|
||||
$"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player).");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using Dalamud.Plugin;
|
||||
using Glamourer.Events;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Interop;
|
||||
|
|
@ -10,8 +9,6 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
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 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()
|
||||
: this(new Dictionary<string, IList<string>>(), 0, false)
|
||||
: this(new Dictionary<string, List<string>>(), 0, false)
|
||||
{ }
|
||||
|
||||
public static ModSettings Empty
|
||||
|
|
@ -36,46 +33,52 @@ public readonly record struct ModSettings(IDictionary<string, IList<string>> Set
|
|||
|
||||
public unsafe class PenumbraService : IDisposable
|
||||
{
|
||||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
public const int RequiredPenumbraBreakingVersion = 5;
|
||||
public const int RequiredPenumbraFeatureVersion = 0;
|
||||
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
||||
private 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 DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _modSettingChanged;
|
||||
|
||||
private readonly EventSubscriber _initializedEvent;
|
||||
private readonly EventSubscriber _disposedEvent;
|
||||
private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier;
|
||||
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;
|
||||
|
||||
public bool Available { get; private set; }
|
||||
public bool Available { get; private set; }
|
||||
public int CurrentMajor { get; private set; }
|
||||
public int CurrentMinor { get; private set; }
|
||||
public DateTime AttachTime { get; private set; }
|
||||
|
||||
public PenumbraService(DalamudPluginInterface pi, PenumbraReloaded penumbraReloaded)
|
||||
{
|
||||
_pluginInterface = pi;
|
||||
_penumbraReloaded = penumbraReloaded;
|
||||
_initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach);
|
||||
_disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach);
|
||||
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi);
|
||||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi);
|
||||
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi);
|
||||
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi);
|
||||
_modSettingChanged = Ipc.ModSettingChanged.Subscriber(pi);
|
||||
_initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach);
|
||||
_disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach);
|
||||
_tooltipSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemTooltip.Subscriber(pi);
|
||||
_clickSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemClicked.Subscriber(pi);
|
||||
_createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi);
|
||||
_creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi);
|
||||
_modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi);
|
||||
Reattach();
|
||||
}
|
||||
|
||||
|
|
@ -92,35 +95,71 @@ 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;
|
||||
remove => _creatingCharacterBase.Event -= value;
|
||||
}
|
||||
|
||||
public event Action<nint, string, nint> CreatedCharacterBase
|
||||
public event Action<nint, Guid, nint> CreatedCharacterBase
|
||||
{
|
||||
add => _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;
|
||||
remove => _modSettingChanged.Event -= value;
|
||||
}
|
||||
|
||||
public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods()
|
||||
public Dictionary<Guid, string> GetCollections()
|
||||
=> Available ? _collections!.Invoke() : [];
|
||||
|
||||
public ModSettings GetModSettings(in Mod mod)
|
||||
{
|
||||
if (!Available)
|
||||
return Array.Empty<(Mod Mod, ModSettings Settings)>();
|
||||
return ModSettings.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var allMods = _getMods.Invoke();
|
||||
var collection = _currentCollection.Invoke(ApiCollectionType.Current);
|
||||
var collection = _currentCollection!.Invoke(ApiCollectionType.Current);
|
||||
var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName);
|
||||
if (ec is not PenumbraApiEc.Success)
|
||||
return ModSettings.Empty;
|
||||
|
||||
return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1) : ModSettings.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Error fetching mod settings for {mod.DirectoryName} from Penumbra:\n{ex}");
|
||||
return ModSettings.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (!Available)
|
||||
return [];
|
||||
|
||||
try
|
||||
{
|
||||
var allMods = _getMods!.Invoke();
|
||||
var collection = _currentCollection!.Invoke(ApiCollectionType.Current);
|
||||
return allMods
|
||||
.Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, true)))
|
||||
.Select(m => (m.Key, m.Value, _getCurrentSettings!.Invoke(collection!.Value.Id, m.Key)))
|
||||
.Where(t => t.Item3.Item1 is PenumbraApiEc.Success)
|
||||
.Select(t => (new Mod(t.Item2, t.Item1),
|
||||
!t.Item3.Item2.HasValue
|
||||
|
|
@ -135,25 +174,28 @@ public unsafe class PenumbraService : IDisposable
|
|||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Error fetching mods from Penumbra:\n{ex}");
|
||||
return Array.Empty<(Mod Mod, ModSettings Settings)>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
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.",
|
||||
NotificationType.Info, false);
|
||||
}
|
||||
|
||||
public string CurrentCollection
|
||||
=> Available ? _currentCollection.Invoke(ApiCollectionType.Current) : "<Unavailable>";
|
||||
public (Guid Id, string Name) CurrentCollection
|
||||
=> Available ? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value : (Guid.Empty, "<Unavailable>");
|
||||
|
||||
/// <summary>
|
||||
/// Try to set all mod settings as desired. Only sets when the mod should be enabled.
|
||||
/// If it is disabled, ignore all other settings.
|
||||
/// </summary>
|
||||
public string SetMod(Mod mod, ModSettings settings, string? collection = null)
|
||||
public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null)
|
||||
{
|
||||
if (!Available)
|
||||
return "Penumbra is not available.";
|
||||
|
|
@ -161,8 +203,8 @@ public unsafe class PenumbraService : IDisposable
|
|||
var sb = new StringBuilder();
|
||||
try
|
||||
{
|
||||
collection ??= _currentCollection.Invoke(ApiCollectionType.Current);
|
||||
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled);
|
||||
var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id;
|
||||
var ec = _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled);
|
||||
switch (ec)
|
||||
{
|
||||
case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found.";
|
||||
|
|
@ -172,14 +214,14 @@ public unsafe class PenumbraService : IDisposable
|
|||
if (!settings.Enabled)
|
||||
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.");
|
||||
|
||||
foreach (var (setting, list) in settings.Settings)
|
||||
{
|
||||
ec = list.Count == 1
|
||||
? _setModSetting.Invoke(collection, mod.DirectoryName, mod.Name, setting, list[0])
|
||||
: _setModSettings.Invoke(collection, mod.DirectoryName, mod.Name, setting, (IReadOnlyList<string>)list);
|
||||
? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0])
|
||||
: _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list);
|
||||
switch (ec)
|
||||
{
|
||||
case PenumbraApiEc.OptionGroupMissing:
|
||||
|
|
@ -206,55 +248,54 @@ public unsafe class PenumbraService : IDisposable
|
|||
}
|
||||
|
||||
/// <summary> Obtain the name of the collection currently assigned to the player. </summary>
|
||||
public string GetCurrentPlayerCollection()
|
||||
public Guid GetCurrentPlayerCollection()
|
||||
{
|
||||
if (!Available)
|
||||
return string.Empty;
|
||||
return Guid.Empty;
|
||||
|
||||
var (valid, _, name) = _objectCollection.Invoke(0);
|
||||
return valid ? name : string.Empty;
|
||||
var (valid, _, (id, _)) = _objectCollection!.Invoke(0);
|
||||
return valid ? id : Guid.Empty;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
return string.Empty;
|
||||
{
|
||||
name = string.Empty;
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
var (valid, _, name) = _objectCollection.Invoke(actor.Index.Index);
|
||||
return valid ? name : string.Empty;
|
||||
(var valid, _, (var id, name)) = _objectCollection!.Invoke(actor.Index.Index);
|
||||
return valid ? id : Guid.Empty;
|
||||
}
|
||||
|
||||
/// <summary> Obtain the game object corresponding to a draw object. </summary>
|
||||
public Actor GameObjectFromDrawObject(Model drawObject)
|
||||
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null;
|
||||
=> Available ? _drawObjectInfo!.Invoke(drawObject.Address).Item1 : Actor.Null;
|
||||
|
||||
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
|
||||
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>
|
||||
public void RedrawObject(Actor actor, RedrawType settings)
|
||||
{
|
||||
if (!actor || !Available)
|
||||
if (!actor)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_redrawSubscriber.Invoke(actor.AsObject->ObjectIndex, settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Glamourer.Log.Debug($"Failure redrawing object:\n{e}");
|
||||
}
|
||||
RedrawObject(actor.Index, settings);
|
||||
}
|
||||
|
||||
/// <summary> Try to redraw the given actor. </summary>
|
||||
public void RedrawObject(ObjectIndex index, RedrawType settings)
|
||||
{
|
||||
if (!Available)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_redrawSubscriber.Invoke(index.Index, settings);
|
||||
_redraw!.Invoke(index.Index, settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -269,34 +310,55 @@ public unsafe class PenumbraService : IDisposable
|
|||
{
|
||||
Unattach();
|
||||
|
||||
var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke();
|
||||
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
|
||||
AttachTime = DateTime.UtcNow;
|
||||
try
|
||||
{
|
||||
(CurrentMajor, CurrentMinor) = new global::Penumbra.Api.IpcSubscribers.ApiVersion(_pluginInterface).Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
(CurrentMajor, CurrentMinor) = new global::Penumbra.Api.IpcSubscribers.Legacy.ApiVersions(_pluginInterface).Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
CurrentMajor = 0;
|
||||
CurrentMinor = 0;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentMajor != RequiredPenumbraBreakingVersion || CurrentMinor < RequiredPenumbraFeatureVersion)
|
||||
throw new Exception(
|
||||
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||
$"Invalid Version {CurrentMajor}.{CurrentMinor:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||
|
||||
_tooltipSubscriber.Enable();
|
||||
_clickSubscriber.Enable();
|
||||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_modSettingChanged.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||
_getMods = Ipc.GetMods.Subscriber(_pluginInterface);
|
||||
_currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface);
|
||||
_getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface);
|
||||
_setMod = Ipc.TrySetMod.Subscriber(_pluginInterface);
|
||||
_setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface);
|
||||
_setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface);
|
||||
_setModSettings = Ipc.TrySetModSettings.Subscriber(_pluginInterface);
|
||||
_openModPage = Ipc.OpenMainWindow.Subscriber(_pluginInterface);
|
||||
Available = true;
|
||||
_collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface);
|
||||
_collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface);
|
||||
_redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface);
|
||||
_drawObjectInfo = new global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo(_pluginInterface);
|
||||
_cutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex(_pluginInterface);
|
||||
_objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface);
|
||||
_getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface);
|
||||
_currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface);
|
||||
_getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface);
|
||||
_setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface);
|
||||
_setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface);
|
||||
_setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface);
|
||||
_setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface);
|
||||
_openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface);
|
||||
Available = true;
|
||||
_penumbraReloaded.Invoke();
|
||||
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Unattach();
|
||||
Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}");
|
||||
}
|
||||
}
|
||||
|
|
@ -311,7 +373,21 @@ public unsafe class PenumbraService : IDisposable
|
|||
_modSettingChanged.Disable();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,9 +136,9 @@ public unsafe class ScalingService : IDisposable
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void SetHeightCustomize(Character* character, byte gender, byte bodyType, byte clan, byte height)
|
||||
{
|
||||
character->DrawData.CustomizeData.Sex = gender;
|
||||
character->DrawData.CustomizeData.BodyType = bodyType;
|
||||
character->DrawData.CustomizeData.Tribe = clan;
|
||||
character->DrawData.CustomizeData.Data[(int)CustomizeIndex.Height] = height;
|
||||
character->DrawData.CustomizeData.Sex = gender;
|
||||
character->DrawData.CustomizeData.BodyType = bodyType;
|
||||
character->DrawData.CustomizeData.Tribe = clan;
|
||||
character->DrawData.CustomizeData.Height = height;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,21 +43,22 @@ public class VisorService : IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on);
|
||||
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, byte on);
|
||||
|
||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook;
|
||||
|
||||
private void SetupVisorDetour(nint human, ushort modelId, bool on)
|
||||
private void SetupVisorDetour(nint human, ushort modelId, byte value)
|
||||
{
|
||||
var originalOn = on;
|
||||
var originalOn = value != 0;
|
||||
var on = originalOn;
|
||||
// Invoke an event that can change the requested value
|
||||
// and also control whether the function should be called at all.
|
||||
Event.Invoke(human, false, ref on);
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn}).");
|
||||
Glamourer.Log.Verbose(
|
||||
$"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn} from {value} with {modelId}).");
|
||||
|
||||
SetupVisorDetour((Model)human, modelId, on);
|
||||
SetupVisorDetour(human, modelId, on);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -69,6 +70,6 @@ public class VisorService : IDisposable
|
|||
private unsafe void SetupVisorDetour(Model human, ushort modelId, bool on)
|
||||
{
|
||||
human.AsCharacterBase->VisorToggled = on;
|
||||
_setupVisorHook.Original(human.Address, modelId, on);
|
||||
_setupVisorHook.Original(human.Address, modelId, on ? (byte)1 : (byte)0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ public class CodeService
|
|||
Dolphins = 0x080000,
|
||||
}
|
||||
|
||||
public static readonly CodeFlag AllHintCodes =
|
||||
Enum.GetValues<CodeFlag>().Where(f => GetData(f).Display).Aggregate((CodeFlag)0, (f1, f2) => f1 | f2);
|
||||
|
||||
public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins;
|
||||
public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins;
|
||||
|
||||
|
|
@ -81,7 +84,7 @@ public class CodeService
|
|||
var changes = false;
|
||||
for (var i = 0; i < _config.Codes.Count; ++i)
|
||||
{
|
||||
var enabled = CheckCode(_config.Codes[i].Code);
|
||||
var enabled = CheckCode(_config.Codes[i].Code).Item1;
|
||||
if (enabled == null)
|
||||
{
|
||||
_config.Codes.RemoveAt(i--);
|
||||
|
|
@ -100,7 +103,7 @@ public class CodeService
|
|||
|
||||
public bool AddCode(string name)
|
||||
{
|
||||
if (CheckCode(name) == null || _config.Codes.Any(p => p.Code == name))
|
||||
if (CheckCode(name).Item1 is null || _config.Codes.Any(p => p.Code == name))
|
||||
return false;
|
||||
|
||||
_config.Codes.Add((name, false));
|
||||
|
|
@ -108,16 +111,14 @@ public class CodeService
|
|||
return true;
|
||||
}
|
||||
|
||||
public Action<bool>? CheckCode(string name)
|
||||
public (Action<bool>?, CodeFlag) CheckCode(string name)
|
||||
{
|
||||
var flag = GetCode(name);
|
||||
if (flag == 0)
|
||||
return null;
|
||||
return (null, 0);
|
||||
|
||||
var badFlags = ~GetMutuallyExclusive(flag);
|
||||
return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag;
|
||||
|
||||
;
|
||||
return (v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag, flag);
|
||||
}
|
||||
|
||||
public CodeFlag GetCode(string name)
|
||||
|
|
@ -205,4 +206,30 @@ public class CodeService
|
|||
CodeFlag.Dolphins => [ 0x64, 0xC6, 0x2E, 0x7C, 0x22, 0x3A, 0x42, 0xF5, 0xC3, 0x93, 0x4F, 0x70, 0x1F, 0xFD, 0xFA, 0x3C, 0x98, 0xD2, 0x7C, 0xD8, 0x88, 0xA7, 0x3D, 0x1D, 0x0D, 0xD6, 0x70, 0x15, 0x28, 0x2E, 0x79, 0xE7 ],
|
||||
_ => [],
|
||||
};
|
||||
|
||||
public static (bool Display, int CapitalCount, string Punctuation, string Hint, string Effect) GetData(CodeFlag flag)
|
||||
=> flag switch
|
||||
{
|
||||
CodeFlag.Clown => (true, 3, ",.", "A punchline uttered by Rorschach.", "Randomizes dyes for every player."),
|
||||
CodeFlag.Emperor => (true, 1, ".", "A truth about clothes that only a child will speak.", "Randomizes clothing for every player."),
|
||||
CodeFlag.Individual => (true, 2, "'!'!", "Something an unwilling prophet tries to convince his followers of.", "Randomizes customizations for every player."),
|
||||
CodeFlag.Dwarf => (true, 1, "!", "A centuries old metaphor about humility and the progress of science.", "Sets the player character to minimum height and all other players to maximum height."),
|
||||
CodeFlag.Giant => (true, 2, "!", "A Swift renaming of one of the most famous literary openings of all time.", "Sets the player character to maximum height and all other players to minimum height."),
|
||||
CodeFlag.OopsHyur => (true, 1, "','.", "An alkaline quote attributed to Marilyn Monroe.", "Turns all players to Hyur."),
|
||||
CodeFlag.OopsElezen => (true, 1, ".", "A line from a Futurama song about the far future.", "Turns all players to Elezen."),
|
||||
CodeFlag.OopsLalafell => (true, 2, ",!", "The name of a discontinued plugin.", "Turns all players to Lalafell."),
|
||||
CodeFlag.OopsMiqote => (true, 3, ".", "A Sandman story.", "Turns all players to Miqo'te."),
|
||||
CodeFlag.OopsRoegadyn => (true, 2, "!", "A line from a Steven Universe song about his desires.", "Turns all players to Roegadyn."),
|
||||
CodeFlag.OopsAuRa => (true, 1, "',.", "Something a plumber hates to hear, made to something a scaly hates to hear and initial Au Ra designs.", "Turns all players to Au Ra."),
|
||||
CodeFlag.OopsHrothgar => (true, 1, "',...", "A meme about the attractiveness of anthropomorphic animals.", "Turns all players to Hrothgar."),
|
||||
CodeFlag.OopsViera => (true, 2, "!'!", "A panicked exclamation about bunny arithmetics.", "Turns all players to Viera."),
|
||||
CodeFlag.SixtyThree => (true, 2, "", "The title of a famous LGBTQ-related french play and movie.", "Inverts the gender of every player."),
|
||||
CodeFlag.Shirts => (true, 2, "-.", "A pre-internet meme about disappointing rewards for an adventure, adapted to this specific cheat code.", "Highlights all items in the Unlocks tab as if they were unlocked."),
|
||||
CodeFlag.World => (true, 1, ",.", "A quote about being more important than other people.", "Sets every player except the player character themselves to job-appropriate gear."),
|
||||
CodeFlag.Elephants => (true, 1, "!", "Appropriate lyrics that can also be found in Glamourer's changelogs.", "Sets every player to the elephant costume in varying shades of pink."),
|
||||
CodeFlag.Crown => (true, 1, ".", "A famous Shakespearean line.", "Sets every player with a mentor symbol enabled to the clown's hat."),
|
||||
CodeFlag.Dolphins => (true, 5, ",", "The farewell of the second smartest species on Earth.", "Sets every player to a Namazu hat with different costume bodies."),
|
||||
CodeFlag.Artisan => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."),
|
||||
_ => (false, 0, string.Empty, string.Empty, string.Empty),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
|
@ -11,7 +13,7 @@ namespace Glamourer.Services;
|
|||
|
||||
public sealed class CollectionOverrideService : IService, ISavable
|
||||
{
|
||||
public const int Version = 1;
|
||||
public const int Version = 2;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly PenumbraService _penumbra;
|
||||
|
|
@ -24,48 +26,71 @@ public sealed class CollectionOverrideService : IService, ISavable
|
|||
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)
|
||||
identifier = _actors.FromObject(actor.AsObject, out _, true, true, true);
|
||||
|
||||
return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret)
|
||||
? (ret.Collection, true)
|
||||
: (_penumbra.GetActorCollection(actor), false);
|
||||
? (ret.CollectionId, ret.DisplayName, true)
|
||||
: (_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;
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> 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;
|
||||
|
||||
foreach (var id in identifiers.Where(i => i.IsValid))
|
||||
{
|
||||
_overrides.Add((id, collection));
|
||||
Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collection}.");
|
||||
_overrides.Add((id, collectionId, displayName));
|
||||
Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collectionId}.");
|
||||
_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;
|
||||
|
||||
var current = _overrides[idx];
|
||||
if (current.Collection == newCollection)
|
||||
if (current.CollectionId == newCollectionId)
|
||||
return;
|
||||
|
||||
_overrides[idx] = current with { Collection = newCollection };
|
||||
Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.Collection} to {newCollection}.");
|
||||
_overrides[idx] = current with
|
||||
{
|
||||
CollectionId = newCollectionId,
|
||||
DisplayName = newDisplayName,
|
||||
};
|
||||
Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.CollectionId} to {newCollectionId}.");
|
||||
_saveService.QueueSave(this);
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +127,7 @@ public sealed class CollectionOverrideService : IService, ISavable
|
|||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
if (jObj["Overrides"] is not JArray array)
|
||||
{
|
||||
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>())
|
||||
{
|
||||
var collection = token["Collection"]?.ToObject<string>() ?? string.Empty;
|
||||
var identifier = _actors.FromJson(token);
|
||||
var collectionIdentifier = token["Collection"]?.ToObject<string>() ?? string.Empty;
|
||||
var identifier = _actors.FromJson(token);
|
||||
var displayName = token["DisplayName"]?.ToObject<string>() ?? collectionIdentifier;
|
||||
if (!identifier.IsValid)
|
||||
Glamourer.Log.Warning($"Invalid identifier for collection override with collection [{collection}], skipped.");
|
||||
else if (collection.Length == 0)
|
||||
Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped.");
|
||||
else
|
||||
_overrides.Add((identifier, collection));
|
||||
{
|
||||
Glamourer.Log.Warning(
|
||||
$"Invalid identifier for collection override with collection [{token["Collection"]}], skipped.");
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored.");
|
||||
return;
|
||||
|
|
@ -147,10 +206,11 @@ public sealed class CollectionOverrideService : IService, ISavable
|
|||
JArray SerializeOverrides()
|
||||
{
|
||||
var jArray = new JArray();
|
||||
foreach (var (actor, collection) in _overrides)
|
||||
foreach (var (actor, collection, displayName) in _overrides)
|
||||
{
|
||||
var obj = actor.ToJson();
|
||||
obj["Collection"] = collection;
|
||||
obj["Collection"] = collection;
|
||||
obj["DisplayName"] = displayName;
|
||||
jArray.Add(obj);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ using Dalamud.Plugin.Services;
|
|||
using Glamourer.Automation;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Designs.Special;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Interop;
|
||||
|
|
@ -18,7 +20,7 @@ using ObjectManager = Glamourer.Interop.ObjectManager;
|
|||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
public class CommandService : IDisposable
|
||||
public class CommandService : IDisposable, IApiService
|
||||
{
|
||||
private const string RandomString = "random";
|
||||
private const string MainCommandString = "/glamourer";
|
||||
|
|
@ -39,11 +41,12 @@ public class CommandService : IDisposable
|
|||
private readonly ModSettingApplier _modApplier;
|
||||
private readonly ItemManager _items;
|
||||
private readonly RandomDesignGenerator _randomDesign;
|
||||
private readonly CustomizeService _customizeService;
|
||||
|
||||
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
|
||||
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
|
||||
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier,
|
||||
ItemManager items, RandomDesignGenerator randomDesign)
|
||||
ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService)
|
||||
{
|
||||
_commands = commands;
|
||||
_mainWindow = mainWindow;
|
||||
|
|
@ -60,6 +63,7 @@ public class CommandService : IDisposable
|
|||
_modApplier = modApplier;
|
||||
_items = items;
|
||||
_randomDesign = randomDesign;
|
||||
_customizeService = customizeService;
|
||||
|
||||
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
||||
_commands.AddHandler(ApplyCommandString,
|
||||
|
|
@ -118,13 +122,14 @@ public class CommandService : IDisposable
|
|||
"apply" => Apply(argument),
|
||||
"reapply" => ReapplyState(argument),
|
||||
"revert" => Revert(argument),
|
||||
"reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false),
|
||||
"reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false),
|
||||
"reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true),
|
||||
"automation" => SetAutomation(argument),
|
||||
"copy" => CopyState(argument),
|
||||
"save" => SaveState(argument),
|
||||
"delete" => Delete(argument),
|
||||
"applyitem" => ApplyItem(argument),
|
||||
"applycustomization" => ApplyCustomization(argument),
|
||||
_ => PrintHelp(argumentList[0]),
|
||||
};
|
||||
}
|
||||
|
|
@ -155,6 +160,9 @@ public class CommandService : IDisposable
|
|||
.AddCommand("automation", "Change the state of automated design sets. Use without arguments for help.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddCommand("applyitem", "Apply a specific item to a character. Use without arguments for help.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddCommand("applycustomization", "Apply a specific customization value to a character. Use without arguments for help.")
|
||||
.BuiltString);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -321,8 +329,8 @@ public class CommandService : IDisposable
|
|||
{
|
||||
if (_stateManager.GetOrCreate(identifier, actor, out var state))
|
||||
{
|
||||
_autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert);
|
||||
_stateManager.ReapplyState(actor, StateSource.Manual);
|
||||
_autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, out var forcedRedraw);
|
||||
_stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -371,7 +379,7 @@ public class CommandService : IDisposable
|
|||
return true;
|
||||
|
||||
foreach (var actor in data.Objects)
|
||||
_stateManager.ReapplyState(actor, StateSource.Manual);
|
||||
_stateManager.ReapplyState(actor, false, StateSource.Manual);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -451,6 +459,152 @@ public class CommandService : IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool ApplyCustomization(string arguments)
|
||||
{
|
||||
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (split.Length is not 2)
|
||||
return PrintCustomizationHelp();
|
||||
|
||||
var customizationSplit = split[0].Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (customizationSplit.Length < 2)
|
||||
return PrintCustomizationHelp();
|
||||
|
||||
if (!Enum.TryParse(customizationSplit[0], true, out CustomizeIndex customizeIndex)
|
||||
|| !CustomizationExtensions.AllBasic.Contains(customizeIndex))
|
||||
{
|
||||
if (!int.TryParse(customizationSplit[0], out var customizeInt)
|
||||
|| customizeInt < 0
|
||||
|| customizeInt >= CustomizationExtensions.AllBasic.Length)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("The customization type ").AddYellow(customizationSplit[0], true)
|
||||
.AddText(" could not be identified as a valid type.").BuiltString);
|
||||
return false;
|
||||
}
|
||||
|
||||
customizeIndex = CustomizationExtensions.AllBasic[customizeInt];
|
||||
}
|
||||
|
||||
var valueString = customizationSplit[1].ToLowerInvariant();
|
||||
var (wrapAround, offset) = valueString switch
|
||||
{
|
||||
"next" => (true, (sbyte)1),
|
||||
"previous" => (true, (sbyte)-1),
|
||||
"plus" => (false, (sbyte)1),
|
||||
"minus" => (false, (sbyte)-1),
|
||||
_ => (false, (sbyte)0),
|
||||
};
|
||||
byte? baseValue = null;
|
||||
if (offset == 0)
|
||||
{
|
||||
if (byte.TryParse(valueString, out var b))
|
||||
{
|
||||
baseValue = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("The customization value ").AddPurple(valueString, true)
|
||||
.AddText(" could not be parsed.")
|
||||
.BuiltString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (customizationSplit.Length < 3 || !byte.TryParse(customizationSplit[2], out var multiplier))
|
||||
multiplier = 1;
|
||||
|
||||
if (!IdentifierHandling(split[1], out var identifiers, false, true))
|
||||
return false;
|
||||
|
||||
_objects.Update();
|
||||
foreach (var identifier in identifiers)
|
||||
{
|
||||
if (!_objects.TryGetValue(identifier, out var actors))
|
||||
{
|
||||
if (_stateManager.TryGetValue(identifier, out var state))
|
||||
ApplyToState(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var actor in actors.Objects)
|
||||
{
|
||||
if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state))
|
||||
ApplyToState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
void ApplyToState(ActorState state)
|
||||
{
|
||||
var customize = state.ModelData.Customize;
|
||||
if (!state.ModelData.IsHuman)
|
||||
return;
|
||||
|
||||
var set = _customizeService.Manager.GetSet(customize.Clan, customize.Gender);
|
||||
if (!set.IsAvailable(customizeIndex))
|
||||
return;
|
||||
|
||||
if (baseValue != null)
|
||||
{
|
||||
var v = baseValue.Value;
|
||||
if (set.Type(customizeIndex) is CharaMakeParams.MenuType.ListSelector)
|
||||
--v;
|
||||
set.DataByValue(customizeIndex, new CustomizeValue(v), out var data, customize.Face);
|
||||
if (data != null)
|
||||
_stateManager.ChangeCustomize(state, customizeIndex, data.Value.Value, ApplySettings.Manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
var idx = set.DataByValue(customizeIndex, customize[customizeIndex], out var data, customize.Face);
|
||||
var count = set.Count(customizeIndex, customize.Face);
|
||||
var m = multiplier % count;
|
||||
var newIdx = offset is 1
|
||||
? idx >= count - m
|
||||
? wrapAround
|
||||
? m + idx - count
|
||||
: count - 1
|
||||
: idx + m
|
||||
: idx < m
|
||||
? wrapAround
|
||||
? count - m + idx
|
||||
: 0
|
||||
: idx - m;
|
||||
data = set.Data(customizeIndex, newIdx, customize.Face);
|
||||
_stateManager.ChangeCustomize(state, customizeIndex, data.Value.Value, ApplySettings.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrintCustomizationHelp()
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour applycustomization ").AddYellow("[Customization Type]")
|
||||
.AddPurple(" [Value, Next, Previous, Minus, or Plus] ")
|
||||
.AddBlue("<Amount>")
|
||||
.AddText(" | ")
|
||||
.AddGreen("[Character Identifier]")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》 Valid ").AddPurple("values")
|
||||
.AddText(" depend on the the character's gender, clan, and the customization type.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》 ").AddPurple("Plus").AddText(" and ").AddPurple("Minus")
|
||||
.AddText(" are the same as pressing the + and - buttons in the UI, times the optional ").AddBlue(" amount").AddText(".")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》 ").AddPurple("Next").AddText(" and ").AddPurple("Previous")
|
||||
.AddText(" is similar to Plus and Minus, but with wrap-around on reaching the end.").BuiltString);
|
||||
var builder = new SeStringBuilder().AddText(" 》 Available ").AddYellow("Customization Types")
|
||||
.AddText(" are either a number in ")
|
||||
.AddYellow($"[0, {CustomizationExtensions.AllBasic.Length}]")
|
||||
.AddText(" or one of ");
|
||||
foreach (var index in CustomizationExtensions.AllBasic.SkipLast(1))
|
||||
builder.AddYellow(index.ToString()).AddText(", ");
|
||||
_chat.Print(builder.AddYellow(CustomizationExtensions.AllBasic[^1].ToString()).AddText(".").BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddText(" 》 The item name is case-insensitive. Numeric IDs are preferred before item names.")
|
||||
.BuiltString);
|
||||
PlayerIdentifierHelp(false, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Apply(string arguments)
|
||||
{
|
||||
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
|
@ -534,14 +688,14 @@ public class CommandService : IDisposable
|
|||
if (!applyMods || design is not Design d)
|
||||
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)
|
||||
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
|
||||
|
||||
if (appliedMods > 0)
|
||||
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)
|
||||
|
|
|
|||
23
Glamourer/Services/HeightService.cs
Normal file
23
Glamourer/Services/HeightService.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
public unsafe class HeightService : IService
|
||||
{
|
||||
[Signature("E8 ?? ?? ?? FF 48 8B 0D ?? ?? ?? ?? 0F 28 F0")]
|
||||
private readonly delegate* unmanaged[Stdcall]<CharacterUtility*, byte, byte, byte, byte, float> _calculateHeight = null!;
|
||||
|
||||
public HeightService(IGameInteropProvider interop)
|
||||
=> interop.InitializeFromAttributes(this);
|
||||
|
||||
public float Height(CustomizeValue height, SubRace clan, Gender gender, CustomizeValue bodyType)
|
||||
=> _calculateHeight(CharacterUtility.Instance(), height.Value, (byte)clan, (byte)((byte)gender - 1), bodyType.Value);
|
||||
|
||||
public float Height(in CustomizeArray customize)
|
||||
=> Height(customize[CustomizeIndex.Height], customize.Clan, customize.Gender, customize.BodyType);
|
||||
}
|
||||
|
|
@ -65,15 +65,26 @@ public class ItemManager
|
|||
if (itemId == SmallclothesId(slot))
|
||||
return SmallClothesItem(slot);
|
||||
|
||||
if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item))
|
||||
if (!itemId.IsItem)
|
||||
{
|
||||
var item = EquipItem.FromId(itemId);
|
||||
item = slot is EquipSlot.MainHand or EquipSlot.OffHand
|
||||
? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant)
|
||||
: Identify(slot, item.PrimaryId, item.Variant);
|
||||
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 new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, 0, 0,
|
||||
0,
|
||||
0);
|
||||
|
||||
return item;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public EquipItem Resolve(FullEquipType type, ItemId itemId)
|
||||
|
|
@ -86,9 +97,8 @@ public class ItemManager
|
|||
return EquipItem.FromId(itemId);
|
||||
|
||||
if (item.Type != type)
|
||||
return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, 0, 0,
|
||||
0,
|
||||
0);
|
||||
return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant,
|
||||
0, 0, 0, 0);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Plugin;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Api.Api;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
|
|
@ -43,12 +44,12 @@ public static class StaticServiceManager
|
|||
.AddData()
|
||||
.AddDesigns()
|
||||
.AddState()
|
||||
.AddUi()
|
||||
.AddApi();
|
||||
.AddUi();
|
||||
DalamudServices.AddServices(services, pi);
|
||||
services.AddIServices(typeof(EquipItem).Assembly);
|
||||
services.AddIServices(typeof(Glamourer).Assembly);
|
||||
services.AddIServices(typeof(ImRaii).Assembly);
|
||||
services.AddSingleton<IGlamourerApi>(p => p.GetRequiredService<GlamourerApi>());
|
||||
services.CreateProvider();
|
||||
return services;
|
||||
}
|
||||
|
|
@ -164,8 +165,4 @@ public static class StaticServiceManager
|
|||
.AddSingleton<DesignQuickBar>()
|
||||
.AddSingleton<DesignColorUi>()
|
||||
.AddSingleton<NpcCombo>();
|
||||
|
||||
private static ServiceManager AddApi(this ServiceManager services)
|
||||
=> services.AddSingleton<CommandService>()
|
||||
.AddSingleton<GlamourerIpc>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class StateEditor(
|
|||
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||
|
||||
if (slot is EquipSlot.MainHand)
|
||||
ApplyMainhandPeriphery(state, item, settings);
|
||||
ApplyMainhandPeriphery(state, item, null, settings);
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
|
|
@ -114,7 +114,7 @@ public class StateEditor(
|
|||
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||
|
||||
if (slot is EquipSlot.MainHand)
|
||||
ApplyMainhandPeriphery(state, item, settings);
|
||||
ApplyMainhandPeriphery(state, item, stain, settings);
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
|
|
@ -212,7 +212,9 @@ public class StateEditor(
|
|||
mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key))
|
||||
return;
|
||||
|
||||
var requiresRedraw = oldModelId != mergedDesign.Design.DesignData.ModelId || !mergedDesign.Design.DesignData.IsHuman;
|
||||
var requiresRedraw = mergedDesign.ForcedRedraw
|
||||
|| oldModelId != mergedDesign.Design.DesignData.ModelId
|
||||
|| !mergedDesign.Design.DesignData.IsHuman;
|
||||
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
|
|
@ -389,18 +391,19 @@ public class StateEditor(
|
|||
|
||||
|
||||
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
|
||||
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings)
|
||||
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainId? newStain, ApplySettings settings)
|
||||
{
|
||||
if (!Config.ChangeEntireItem || !settings.Source.IsManual())
|
||||
return;
|
||||
|
||||
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
|
||||
var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
|
||||
var stain = newStain ?? state.ModelData.Stain(EquipSlot.MainHand);
|
||||
if (offhand.Valid)
|
||||
ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), settings);
|
||||
ChangeEquip(state, EquipSlot.OffHand, offhand, stain, settings);
|
||||
|
||||
if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets))
|
||||
ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands),
|
||||
state.ModelData.Stain(EquipSlot.Hands), settings);
|
||||
stain, settings);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public class StateListener : IDisposable
|
|||
/// Weapons and meta flags are updated independently.
|
||||
/// We also need to apply fixed designs here.
|
||||
/// </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;
|
||||
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
|
||||
|
|
@ -725,7 +725,7 @@ public class StateListener : IDisposable
|
|||
_changeCustomizeService.Unsubscribe(OnCustomizeChanged);
|
||||
}
|
||||
|
||||
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject)
|
||||
private void OnCreatedCharacterBase(nint gameObject, Guid _, nint drawObject)
|
||||
{
|
||||
if (_condition[ConditionFlag.CreatingCharacter])
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -402,18 +402,18 @@ public sealed class StateManager(
|
|||
}
|
||||
}
|
||||
|
||||
public void ReapplyState(Actor actor, StateSource source)
|
||||
public void ReapplyState(Actor actor, bool forceRedraw, StateSource source)
|
||||
{
|
||||
if (!GetOrCreate(actor, out var state))
|
||||
return;
|
||||
|
||||
ReapplyState(actor, state, source);
|
||||
ReapplyState(actor, state, forceRedraw, source);
|
||||
}
|
||||
|
||||
public void ReapplyState(Actor actor, ActorState state, StateSource source)
|
||||
public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source)
|
||||
{
|
||||
var data = Applier.ApplyAll(state,
|
||||
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
|
||||
forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
|
||||
StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
"Character"
|
||||
],
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "1.2.0.0",
|
||||
"TestingAssemblyVersion": "1.2.0.0",
|
||||
"AssemblyVersion": "1.2.0.8",
|
||||
"TestingAssemblyVersion": "1.2.2.2",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 9,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue