mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-15 05:04:16 +01:00
Merge branch 'colortable'
This commit is contained in:
commit
73266811a0
66 changed files with 2458 additions and 286 deletions
|
|
@ -11,7 +11,9 @@ namespace Glamourer.Api;
|
||||||
public partial class GlamourerIpc
|
public partial class GlamourerIpc
|
||||||
{
|
{
|
||||||
public const string LabelApplyAll = "Glamourer.ApplyAll";
|
public const string LabelApplyAll = "Glamourer.ApplyAll";
|
||||||
|
public const string LabelApplyAllOnce = "Glamourer.ApplyAllOnce";
|
||||||
public const string LabelApplyAllToCharacter = "Glamourer.ApplyAllToCharacter";
|
public const string LabelApplyAllToCharacter = "Glamourer.ApplyAllToCharacter";
|
||||||
|
public const string LabelApplyAllOnceToCharacter = "Glamourer.ApplyAllOnceToCharacter";
|
||||||
public const string LabelApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment";
|
public const string LabelApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment";
|
||||||
public const string LabelApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter";
|
public const string LabelApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter";
|
||||||
public const string LabelApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization";
|
public const string LabelApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization";
|
||||||
|
|
@ -24,11 +26,15 @@ public partial class GlamourerIpc
|
||||||
public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock";
|
public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock";
|
||||||
public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock";
|
public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock";
|
||||||
|
|
||||||
public const string LabelApplyByGuid = "Glamourer.ApplyByGuid";
|
public const string LabelApplyByGuid = "Glamourer.ApplyByGuid";
|
||||||
public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter";
|
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> _applyAllProvider;
|
||||||
|
private readonly ActionProvider<string, string> _applyAllOnceProvider;
|
||||||
private readonly ActionProvider<string, Character?> _applyAllToCharacterProvider;
|
private readonly ActionProvider<string, Character?> _applyAllToCharacterProvider;
|
||||||
|
private readonly ActionProvider<string, Character?> _applyAllOnceToCharacterProvider;
|
||||||
private readonly ActionProvider<string, string> _applyOnlyEquipmentProvider;
|
private readonly ActionProvider<string, string> _applyOnlyEquipmentProvider;
|
||||||
private readonly ActionProvider<string, Character?> _applyOnlyEquipmentToCharacterProvider;
|
private readonly ActionProvider<string, Character?> _applyOnlyEquipmentToCharacterProvider;
|
||||||
private readonly ActionProvider<string, string> _applyOnlyCustomizationProvider;
|
private readonly ActionProvider<string, string> _applyOnlyCustomizationProvider;
|
||||||
|
|
@ -42,14 +48,22 @@ public partial class GlamourerIpc
|
||||||
private readonly ActionProvider<string, Character?, uint> _applyOnlyCustomizationToCharacterProviderLock;
|
private readonly ActionProvider<string, Character?, uint> _applyOnlyCustomizationToCharacterProviderLock;
|
||||||
|
|
||||||
private readonly ActionProvider<Guid, string> _applyByGuidProvider;
|
private readonly ActionProvider<Guid, string> _applyByGuidProvider;
|
||||||
|
private readonly ActionProvider<Guid, string> _applyByGuidOnceProvider;
|
||||||
private readonly ActionProvider<Guid, Character?> _applyByGuidToCharacterProvider;
|
private readonly ActionProvider<Guid, Character?> _applyByGuidToCharacterProvider;
|
||||||
|
private readonly ActionProvider<Guid, Character?> _applyByGuidOnceToCharacterProvider;
|
||||||
|
|
||||||
public static ActionSubscriber<string, string> ApplyAllSubscriber(DalamudPluginInterface pi)
|
public static ActionSubscriber<string, string> ApplyAllSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelApplyAll);
|
=> new(pi, LabelApplyAll);
|
||||||
|
|
||||||
|
public static ActionSubscriber<string, string> ApplyAllOnceSubscriber(DalamudPluginInterface pi)
|
||||||
|
=> new(pi, LabelApplyAllOnce);
|
||||||
|
|
||||||
public static ActionSubscriber<string, Character?> ApplyAllToCharacterSubscriber(DalamudPluginInterface pi)
|
public static ActionSubscriber<string, Character?> ApplyAllToCharacterSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelApplyAllToCharacter);
|
=> new(pi, LabelApplyAllToCharacter);
|
||||||
|
|
||||||
|
public static ActionSubscriber<string, Character?> ApplyAllOnceToCharacterSubscriber(DalamudPluginInterface pi)
|
||||||
|
=> new(pi, LabelApplyAllOnceToCharacter);
|
||||||
|
|
||||||
public static ActionSubscriber<string, string> ApplyOnlyEquipmentSubscriber(DalamudPluginInterface pi)
|
public static ActionSubscriber<string, string> ApplyOnlyEquipmentSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelApplyOnlyEquipment);
|
=> new(pi, LabelApplyOnlyEquipment);
|
||||||
|
|
||||||
|
|
@ -65,15 +79,27 @@ public partial class GlamourerIpc
|
||||||
public static ActionSubscriber<Guid, string> ApplyByGuidSubscriber(DalamudPluginInterface pi)
|
public static ActionSubscriber<Guid, string> ApplyByGuidSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelApplyByGuid);
|
=> new(pi, LabelApplyByGuid);
|
||||||
|
|
||||||
|
public static ActionSubscriber<Guid, string> ApplyByGuidOnceSubscriber(DalamudPluginInterface pi)
|
||||||
|
=> new(pi, LabelApplyByGuidOnce);
|
||||||
|
|
||||||
public static ActionSubscriber<Guid, Character?> ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi)
|
public static ActionSubscriber<Guid, Character?> ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelApplyByGuidToCharacter);
|
=> new(pi, LabelApplyByGuidToCharacter);
|
||||||
|
|
||||||
|
public static ActionSubscriber<Guid, Character?> ApplyByGuidOnceToCharacterSubscriber(DalamudPluginInterface pi)
|
||||||
|
=> new(pi, LabelApplyByGuidOnceToCharacter);
|
||||||
|
|
||||||
public void ApplyAll(string base64, string characterName)
|
public void ApplyAll(string base64, string characterName)
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0);
|
=> 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)
|
public void ApplyAllToCharacter(string base64, Character? character)
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0);
|
=> 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)
|
public void ApplyOnlyEquipment(string base64, string characterName)
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, 0);
|
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, 0);
|
||||||
|
|
||||||
|
|
@ -107,12 +133,18 @@ public partial class GlamourerIpc
|
||||||
|
|
||||||
|
|
||||||
public void ApplyByGuid(Guid identifier, string characterName)
|
public void ApplyByGuid(Guid identifier, string characterName)
|
||||||
=> ApplyDesignByGuid(identifier, FindActors(characterName), 0);
|
=> 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)
|
public void ApplyByGuidToCharacter(Guid identifier, Character? character)
|
||||||
=> ApplyDesignByGuid(identifier, FindActors(character), 0);
|
=> ApplyDesignByGuid(identifier, FindActors(character), 0, false);
|
||||||
|
|
||||||
private void ApplyDesign(DesignBase? design, IEnumerable<ActorIdentifier> actors, byte version, uint lockCode)
|
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)
|
if (design == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -130,12 +162,13 @@ public partial class GlamourerIpc
|
||||||
|
|
||||||
if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode))
|
if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode))
|
||||||
{
|
{
|
||||||
_stateManager.ApplyDesign(state, design, new ApplySettings(Source:StateSource.Ipc, Key:lockCode));
|
_stateManager.ApplyDesign(state, design,
|
||||||
|
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode));
|
||||||
state.Lock(lockCode);
|
state.Lock(lockCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyDesignByGuid(Guid identifier, IEnumerable<ActorIdentifier> actors, uint lockCode)
|
private void ApplyDesignByGuid(Guid identifier, IEnumerable<ActorIdentifier> actors, uint lockCode, bool once)
|
||||||
=> ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode);
|
=> ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode, once);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.Events;
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
|
|
@ -18,7 +19,7 @@ public partial class GlamourerIpc
|
||||||
private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null)
|
private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null)
|
||||||
{
|
{
|
||||||
foreach (var actor in actors.Objects)
|
foreach (var actor in actors.Objects)
|
||||||
_stateChangedProvider.Invoke(type, actor.Address, new Lazy<string>(() => _designConverter.ShareBase64(state)));
|
_stateChangedProvider.Invoke(type, actor.Address, new Lazy<string>(() => _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGPoseChanged(bool value)
|
private void OnGPoseChanged(bool value)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Glamourer.Designs;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
|
@ -40,6 +41,6 @@ public partial class GlamourerIpc
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _designConverter.ShareBase64(state);
|
return _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Events;
|
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
|
@ -83,7 +82,7 @@ public partial class GlamourerIpc
|
||||||
foreach (var id in actors)
|
foreach (var id in actors)
|
||||||
{
|
{
|
||||||
if (_stateManager.TryGetValue(id, out var state))
|
if (_stateManager.TryGetValue(id, out var state))
|
||||||
_stateManager.ResetState(state, StateSource.Ipc, lockCode);
|
_stateManager.ResetState(state, StateSource.IpcFixed, lockCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,30 @@ public partial class GlamourerIpc
|
||||||
ItemInvalid,
|
ItemInvalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string LabelSetItem = "Glamourer.SetItem";
|
public const string LabelSetItem = "Glamourer.SetItem";
|
||||||
public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName";
|
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> _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> _setItemByActorNameProvider;
|
||||||
|
private readonly FuncProvider<string, byte, ulong, byte, uint, int> _setItemOnceByActorNameProvider;
|
||||||
|
|
||||||
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemSubscriber(DalamudPluginInterface pi)
|
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelSetItem);
|
=> 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)
|
public static FuncSubscriber<string, byte, ulong, byte, uint, int> SetItemByActorNameSubscriber(DalamudPluginInterface pi)
|
||||||
=> new(pi, LabelSetItemByActorName);
|
=> new(pi, LabelSetItemByActorName);
|
||||||
|
|
||||||
private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key)
|
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)
|
if (itemId.Id == 0)
|
||||||
itemId = ItemManager.NothingId(slot);
|
itemId = ItemManager.NothingId(slot);
|
||||||
|
|
@ -57,11 +67,12 @@ public partial class GlamourerIpc
|
||||||
if (!state.ModelData.IsHuman)
|
if (!state.ModelData.IsHuman)
|
||||||
return GlamourerErrorCode.ActorNotHuman;
|
return GlamourerErrorCode.ActorNotHuman;
|
||||||
|
|
||||||
_stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key:key));
|
_stateManager.ChangeEquip(state, slot, item, stainId,
|
||||||
|
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key));
|
||||||
return GlamourerErrorCode.Success;
|
return GlamourerErrorCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key)
|
private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once)
|
||||||
{
|
{
|
||||||
if (itemId.Id == 0)
|
if (itemId.Id == 0)
|
||||||
itemId = ItemManager.NothingId(slot);
|
itemId = ItemManager.NothingId(slot);
|
||||||
|
|
@ -84,7 +95,8 @@ public partial class GlamourerIpc
|
||||||
if (!state.ModelData.IsHuman)
|
if (!state.ModelData.IsHuman)
|
||||||
return GlamourerErrorCode.ActorNotHuman;
|
return GlamourerErrorCode.ActorNotHuman;
|
||||||
|
|
||||||
_stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key: key));
|
_stateManager.ChangeEquip(state, slot, item, stainId,
|
||||||
|
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key));
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,11 @@ public sealed partial class GlamourerIpc : IDisposable
|
||||||
_getAllCustomizationFromCharacterProvider =
|
_getAllCustomizationFromCharacterProvider =
|
||||||
new FuncProvider<Character?, string?>(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter);
|
new FuncProvider<Character?, string?>(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter);
|
||||||
|
|
||||||
_applyAllProvider = new ActionProvider<string, string>(pi, LabelApplyAll, ApplyAll);
|
_applyAllProvider = new ActionProvider<string, string>(pi, LabelApplyAll, ApplyAll);
|
||||||
_applyAllToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllToCharacter, ApplyAllToCharacter);
|
_applyAllOnceProvider = new ActionProvider<string, string>(pi, LabelApplyAll, ApplyAllOnce);
|
||||||
_applyOnlyEquipmentProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment);
|
_applyAllToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllToCharacter, ApplyAllToCharacter);
|
||||||
|
_applyAllOnceToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllToCharacter, ApplyAllOnceToCharacter);
|
||||||
|
_applyOnlyEquipmentProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment);
|
||||||
_applyOnlyEquipmentToCharacterProvider =
|
_applyOnlyEquipmentToCharacterProvider =
|
||||||
new ActionProvider<string, Character?>(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter);
|
new ActionProvider<string, Character?>(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter);
|
||||||
_applyOnlyCustomizationProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization);
|
_applyOnlyCustomizationProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization);
|
||||||
|
|
@ -66,8 +68,11 @@ public sealed partial class GlamourerIpc : IDisposable
|
||||||
_applyOnlyCustomizationToCharacterProviderLock =
|
_applyOnlyCustomizationToCharacterProviderLock =
|
||||||
new ActionProvider<string, Character?, uint>(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock);
|
new ActionProvider<string, Character?, uint>(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock);
|
||||||
|
|
||||||
_applyByGuidProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuid, ApplyByGuid);
|
_applyByGuidProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuid, ApplyByGuid);
|
||||||
|
_applyByGuidOnceProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuidOnce, ApplyByGuidOnce);
|
||||||
_applyByGuidToCharacterProvider = new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter);
|
_applyByGuidToCharacterProvider = new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter);
|
||||||
|
_applyByGuidOnceToCharacterProvider =
|
||||||
|
new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidOnceToCharacter, ApplyByGuidOnceToCharacter);
|
||||||
|
|
||||||
_revertProvider = new ActionProvider<string>(pi, LabelRevert, Revert);
|
_revertProvider = new ActionProvider<string>(pi, LabelRevert, Revert);
|
||||||
_revertCharacterProvider = new ActionProvider<Character?>(pi, LabelRevertCharacter, RevertCharacter);
|
_revertCharacterProvider = new ActionProvider<Character?>(pi, LabelRevertCharacter, RevertCharacter);
|
||||||
|
|
@ -83,9 +88,14 @@ public sealed partial class GlamourerIpc : IDisposable
|
||||||
_gPoseChangedProvider = new EventProvider<bool>(pi, LabelGPoseChanged);
|
_gPoseChangedProvider = new EventProvider<bool>(pi, LabelGPoseChanged);
|
||||||
|
|
||||||
_setItemProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItem,
|
_setItemProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItem,
|
||||||
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key));
|
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false));
|
||||||
_setItemByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemByActorName,
|
_setItemOnceProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItem,
|
||||||
(name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key));
|
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true));
|
||||||
|
|
||||||
|
_setItemByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemOnceByActorName,
|
||||||
|
(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);
|
_stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc);
|
||||||
_gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc);
|
_gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc);
|
||||||
|
|
@ -102,7 +112,9 @@ public sealed partial class GlamourerIpc : IDisposable
|
||||||
_getAllCustomizationFromCharacterProvider.Dispose();
|
_getAllCustomizationFromCharacterProvider.Dispose();
|
||||||
|
|
||||||
_applyAllProvider.Dispose();
|
_applyAllProvider.Dispose();
|
||||||
|
_applyAllOnceProvider.Dispose();
|
||||||
_applyAllToCharacterProvider.Dispose();
|
_applyAllToCharacterProvider.Dispose();
|
||||||
|
_applyAllOnceToCharacterProvider.Dispose();
|
||||||
_applyOnlyEquipmentProvider.Dispose();
|
_applyOnlyEquipmentProvider.Dispose();
|
||||||
_applyOnlyEquipmentToCharacterProvider.Dispose();
|
_applyOnlyEquipmentToCharacterProvider.Dispose();
|
||||||
_applyOnlyCustomizationProvider.Dispose();
|
_applyOnlyCustomizationProvider.Dispose();
|
||||||
|
|
@ -113,8 +125,11 @@ public sealed partial class GlamourerIpc : IDisposable
|
||||||
_applyOnlyEquipmentToCharacterProviderLock.Dispose();
|
_applyOnlyEquipmentToCharacterProviderLock.Dispose();
|
||||||
_applyOnlyCustomizationProviderLock.Dispose();
|
_applyOnlyCustomizationProviderLock.Dispose();
|
||||||
_applyOnlyCustomizationToCharacterProviderLock.Dispose();
|
_applyOnlyCustomizationToCharacterProviderLock.Dispose();
|
||||||
|
|
||||||
_applyByGuidProvider.Dispose();
|
_applyByGuidProvider.Dispose();
|
||||||
|
_applyByGuidOnceProvider.Dispose();
|
||||||
_applyByGuidToCharacterProvider.Dispose();
|
_applyByGuidToCharacterProvider.Dispose();
|
||||||
|
_applyByGuidOnceToCharacterProvider.Dispose();
|
||||||
|
|
||||||
_revertProvider.Dispose();
|
_revertProvider.Dispose();
|
||||||
_revertCharacterProvider.Dispose();
|
_revertCharacterProvider.Dispose();
|
||||||
|
|
@ -133,7 +148,9 @@ public sealed partial class GlamourerIpc : IDisposable
|
||||||
_getDesignListProvider.Dispose();
|
_getDesignListProvider.Dispose();
|
||||||
|
|
||||||
_setItemProvider.Dispose();
|
_setItemProvider.Dispose();
|
||||||
|
_setItemOnceProvider.Dispose();
|
||||||
_setItemByActorNameProvider.Dispose();
|
_setItemByActorNameProvider.Dispose();
|
||||||
|
_setItemOnceByActorNameProvider.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<ActorIdentifier> FindActors(string actorName)
|
private IEnumerable<ActorIdentifier> FindActors(string actorName)
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,7 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
|
|
||||||
var mergedDesign = _designMerger.Merge(
|
var mergedDesign = _designMerger.Merge(
|
||||||
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]),
|
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]),
|
||||||
state.ModelData, true, false);
|
state.ModelData, true, _config.AlwaysApplyAssociatedMods);
|
||||||
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false));
|
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ public class Configuration : IPluginConfiguration, ISavable
|
||||||
public bool UseRgbForColors { get; set; } = true;
|
public bool UseRgbForColors { get; set; } = true;
|
||||||
public bool ShowColorConfig { get; set; } = true;
|
public bool ShowColorConfig { get; set; } = true;
|
||||||
public bool ChangeEntireItem { get; set; } = false;
|
public bool ChangeEntireItem { get; set; } = false;
|
||||||
|
public bool AlwaysApplyAssociatedMods { get; set; } = false;
|
||||||
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
||||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||||
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
||||||
|
|
|
||||||
84
Glamourer/Designs/ApplicationRules.cs
Normal file
84
Glamourer/Designs/ApplicationRules.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.State;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
public readonly struct ApplicationRules(
|
||||||
|
EquipFlag equip,
|
||||||
|
CustomizeFlag customize,
|
||||||
|
CrestFlag crest,
|
||||||
|
CustomizeParameterFlag parameters,
|
||||||
|
MetaFlag meta)
|
||||||
|
{
|
||||||
|
public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant,
|
||||||
|
CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All);
|
||||||
|
|
||||||
|
public static ApplicationRules FromModifiers(ActorState state)
|
||||||
|
=> FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
|
||||||
|
|
||||||
|
public static ApplicationRules NpcFromModifiers()
|
||||||
|
=> NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
|
||||||
|
|
||||||
|
public static ApplicationRules AllButParameters(ActorState state)
|
||||||
|
=> new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta);
|
||||||
|
|
||||||
|
public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift)
|
||||||
|
=> new(ctrl || !shift ? EquipFlagExtensions.All : 0,
|
||||||
|
!ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
ctrl || !shift ? MetaFlag.VisorState : 0);
|
||||||
|
|
||||||
|
public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift)
|
||||||
|
{
|
||||||
|
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
|
||||||
|
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
|
||||||
|
var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant;
|
||||||
|
var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All;
|
||||||
|
var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0;
|
||||||
|
if (equip != 0)
|
||||||
|
meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState;
|
||||||
|
|
||||||
|
return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply(DesignBase design)
|
||||||
|
{
|
||||||
|
design.ApplyEquip = Equip;
|
||||||
|
design.ApplyCustomize = Customize;
|
||||||
|
design.ApplyCrest = Crest;
|
||||||
|
design.ApplyParameters = Parameters;
|
||||||
|
design.ApplyMeta = Meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EquipFlag Equip
|
||||||
|
=> equip & EquipFlagExtensions.All;
|
||||||
|
|
||||||
|
public CustomizeFlag Customize
|
||||||
|
=> customize & CustomizeFlagExtensions.AllRelevant;
|
||||||
|
|
||||||
|
public CrestFlag Crest
|
||||||
|
=> crest & CrestExtensions.AllRelevant;
|
||||||
|
|
||||||
|
public CustomizeParameterFlag Parameters
|
||||||
|
=> parameters & CustomizeParameterExtensions.All;
|
||||||
|
|
||||||
|
public MetaFlag Meta
|
||||||
|
=> meta & MetaExtensions.All;
|
||||||
|
|
||||||
|
public static CustomizeParameterFlag ComputeParameters(in DesignData model, in DesignData game,
|
||||||
|
CustomizeParameterFlag baseFlags = CustomizeParameterExtensions.All)
|
||||||
|
{
|
||||||
|
foreach (var flag in baseFlags.Iterate())
|
||||||
|
{
|
||||||
|
var modelValue = model.Parameters[flag];
|
||||||
|
var gameValue = game.Parameters[flag];
|
||||||
|
if (modelValue.NearEqual(gameValue))
|
||||||
|
baseFlags &= ~flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseFlags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ public sealed class Design : DesignBase, ISavable
|
||||||
{
|
{
|
||||||
Tags = [.. other.Tags];
|
Tags = [.. other.Tags];
|
||||||
Description = other.Description;
|
Description = other.Description;
|
||||||
|
QuickDesign = other.QuickDesign;
|
||||||
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
|
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ public sealed class Design : DesignBase, ISavable
|
||||||
public string Description { get; internal set; } = string.Empty;
|
public string Description { get; internal set; } = string.Empty;
|
||||||
public string[] Tags { get; internal set; } = [];
|
public string[] Tags { get; internal set; } = [];
|
||||||
public int Index { get; internal set; }
|
public int Index { get; internal set; }
|
||||||
|
public bool QuickDesign { get; internal set; } = true;
|
||||||
public string Color { get; internal set; } = string.Empty;
|
public string Color { get; internal set; } = string.Empty;
|
||||||
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
|
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
|
||||||
public LinkContainer Links { get; private set; } = [];
|
public LinkContainer Links { get; private set; } = [];
|
||||||
|
|
@ -64,11 +66,13 @@ public sealed class Design : DesignBase, ISavable
|
||||||
["Name"] = Name.Text,
|
["Name"] = Name.Text,
|
||||||
["Description"] = Description,
|
["Description"] = Description,
|
||||||
["Color"] = Color,
|
["Color"] = Color,
|
||||||
|
["QuickDesign"] = QuickDesign,
|
||||||
["Tags"] = JArray.FromObject(Tags),
|
["Tags"] = JArray.FromObject(Tags),
|
||||||
["WriteProtected"] = WriteProtected(),
|
["WriteProtected"] = WriteProtected(),
|
||||||
["Equipment"] = SerializeEquipment(),
|
["Equipment"] = SerializeEquipment(),
|
||||||
["Customize"] = SerializeCustomize(),
|
["Customize"] = SerializeCustomize(),
|
||||||
["Parameters"] = SerializeParameters(),
|
["Parameters"] = SerializeParameters(),
|
||||||
|
["Materials"] = SerializeMaterials(),
|
||||||
["Mods"] = SerializeMods(),
|
["Mods"] = SerializeMods(),
|
||||||
["Links"] = Links.Serialize(),
|
["Links"] = Links.Serialize(),
|
||||||
};
|
};
|
||||||
|
|
@ -124,6 +128,7 @@ public sealed class Design : DesignBase, ISavable
|
||||||
Description = json["Description"]?.ToObject<string>() ?? string.Empty,
|
Description = json["Description"]?.ToObject<string>() ?? string.Empty,
|
||||||
Tags = ParseTags(json),
|
Tags = ParseTags(json),
|
||||||
LastEdit = json["LastEdit"]?.ToObject<DateTimeOffset>() ?? creationDate,
|
LastEdit = json["LastEdit"]?.ToObject<DateTimeOffset>() ?? creationDate,
|
||||||
|
QuickDesign = json["QuickDesign"]?.ToObject<bool>() ?? true,
|
||||||
};
|
};
|
||||||
if (design.LastEdit < creationDate)
|
if (design.LastEdit < creationDate)
|
||||||
design.LastEdit = creationDate;
|
design.LastEdit = creationDate;
|
||||||
|
|
@ -132,6 +137,7 @@ public sealed class Design : DesignBase, ISavable
|
||||||
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
||||||
LoadMods(json["Mods"], design);
|
LoadMods(json["Mods"], design);
|
||||||
LoadParameters(json["Parameters"], design, design.Name);
|
LoadParameters(json["Parameters"], design, design.Name);
|
||||||
|
LoadMaterials(json["Materials"], design, design.Name);
|
||||||
LoadLinks(linkLoader, json["Links"], design);
|
LoadLinks(linkLoader, json["Links"], design);
|
||||||
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
||||||
return design;
|
return design;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -14,7 +14,16 @@ public class DesignBase
|
||||||
{
|
{
|
||||||
public const int FileVersion = 1;
|
public const int FileVersion = 1;
|
||||||
|
|
||||||
private DesignData _designData = new();
|
private DesignData _designData = new();
|
||||||
|
private readonly DesignMaterialManager _materials = new();
|
||||||
|
|
||||||
|
/// <summary> For read-only information about custom material color changes. </summary>
|
||||||
|
public IReadOnlyList<(uint, MaterialValueDesign)> Materials
|
||||||
|
=> _materials.Values;
|
||||||
|
|
||||||
|
/// <summary> To make it clear something is edited here. </summary>
|
||||||
|
public DesignMaterialManager GetMaterialDataRef()
|
||||||
|
=> _materials;
|
||||||
|
|
||||||
/// <summary> For read-only information about the actual design. </summary>
|
/// <summary> For read-only information about the actual design. </summary>
|
||||||
public ref readonly DesignData DesignData
|
public ref readonly DesignData DesignData
|
||||||
|
|
@ -30,6 +39,7 @@ public class DesignBase
|
||||||
CustomizeSet = SetCustomizationSet(customize);
|
CustomizeSet = SetCustomizationSet(customize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Used when importing .cma or .chara files. </summary>
|
||||||
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||||
{
|
{
|
||||||
_designData = designData;
|
_designData = designData;
|
||||||
|
|
@ -42,6 +52,7 @@ public class DesignBase
|
||||||
internal DesignBase(DesignBase clone)
|
internal DesignBase(DesignBase clone)
|
||||||
{
|
{
|
||||||
_designData = clone._designData;
|
_designData = clone._designData;
|
||||||
|
_materials = clone._materials.Clone();
|
||||||
CustomizeSet = clone.CustomizeSet;
|
CustomizeSet = clone.CustomizeSet;
|
||||||
ApplyCustomize = clone.ApplyCustomizeRaw;
|
ApplyCustomize = clone.ApplyCustomizeRaw;
|
||||||
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
||||||
|
|
@ -75,9 +86,9 @@ public class DesignBase
|
||||||
internal CustomizeFlag ApplyCustomizeRaw
|
internal CustomizeFlag ApplyCustomizeRaw
|
||||||
=> _applyCustomize;
|
=> _applyCustomize;
|
||||||
|
|
||||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||||
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
||||||
private bool _writeProtected;
|
private bool _writeProtected;
|
||||||
|
|
||||||
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
||||||
|
|
@ -113,7 +124,6 @@ public class DesignBase
|
||||||
|
|
||||||
_writeProtected = value;
|
_writeProtected = value;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DoApplyEquip(EquipSlot slot)
|
public bool DoApplyEquip(EquipSlot slot)
|
||||||
|
|
@ -233,6 +243,7 @@ public class DesignBase
|
||||||
["Equipment"] = SerializeEquipment(),
|
["Equipment"] = SerializeEquipment(),
|
||||||
["Customize"] = SerializeCustomize(),
|
["Customize"] = SerializeCustomize(),
|
||||||
["Parameters"] = SerializeParameters(),
|
["Parameters"] = SerializeParameters(),
|
||||||
|
["Materials"] = SerializeMaterials(),
|
||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -351,6 +362,45 @@ public class DesignBase
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected JObject SerializeMaterials()
|
||||||
|
{
|
||||||
|
var ret = new JObject();
|
||||||
|
foreach (var (key, value) in Materials)
|
||||||
|
ret[key.ToString("X16")] = JToken.FromObject(value);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void LoadMaterials(JToken? materials, DesignBase design, string name)
|
||||||
|
{
|
||||||
|
if (materials is not JObject obj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.GetMaterialDataRef().Clear();
|
||||||
|
foreach (var (key, value) in obj.Properties().Zip(obj.PropertyValues()))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var k = uint.Parse(key.Name, NumberStyles.HexNumber);
|
||||||
|
var v = value.ToObject<MaterialValueDesign>();
|
||||||
|
if (!MaterialValueIndex.FromKey(k, out var idx))
|
||||||
|
{
|
||||||
|
Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.",
|
||||||
|
NotificationType.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!design.GetMaterialDataRef().TryAddValue(MaterialValueIndex.FromKey(k), v))
|
||||||
|
Glamourer.Messager.NotificationMessage($"Duplicate material value key {k} for design {name}, skipped.",
|
||||||
|
NotificationType.Warning);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Messager.NotificationMessage(ex, $"Error parsing material value for design {name}, skipped",
|
||||||
|
NotificationType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Deserialization
|
#region Deserialization
|
||||||
|
|
@ -371,6 +421,7 @@ public class DesignBase
|
||||||
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
|
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
|
||||||
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
|
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
|
||||||
LoadParameters(json["Parameters"], ret, "Temporary Design");
|
LoadParameters(json["Parameters"], ret, "Temporary Design");
|
||||||
|
LoadMaterials(json["Materials"], ret, "Temporary Design");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.GameData;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Glamourer.Utility;
|
using Glamourer.Utility;
|
||||||
|
|
@ -7,11 +7,17 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans, DesignLinkLoader _linkLoader)
|
public class DesignConverter(
|
||||||
|
ItemManager _items,
|
||||||
|
DesignManager _designs,
|
||||||
|
CustomizeService _customize,
|
||||||
|
HumanModelList _humans,
|
||||||
|
DesignLinkLoader _linkLoader)
|
||||||
{
|
{
|
||||||
public const byte Version = 6;
|
public const byte Version = 6;
|
||||||
|
|
||||||
|
|
@ -21,9 +27,9 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
||||||
public JObject ShareJObject(Design design)
|
public JObject ShareJObject(Design design)
|
||||||
=> design.JsonSerialize();
|
=> design.JsonSerialize();
|
||||||
|
|
||||||
public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
|
public JObject ShareJObject(ActorState state, in ApplicationRules rules)
|
||||||
{
|
{
|
||||||
var design = Convert(state, equipFlags, customizeFlags, crestFlags, parameterFlags);
|
var design = Convert(state, rules);
|
||||||
return ShareJObject(design);
|
return ShareJObject(design);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,33 +39,24 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
||||||
public string ShareBase64(DesignBase design)
|
public string ShareBase64(DesignBase design)
|
||||||
=> ShareBase64(ShareJObject(design));
|
=> ShareBase64(ShareJObject(design));
|
||||||
|
|
||||||
public string ShareBase64(ActorState state)
|
public string ShareBase64(ActorState state, in ApplicationRules rules)
|
||||||
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All);
|
=> ShareBase64(state.ModelData, state.Materials, rules);
|
||||||
|
|
||||||
public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
|
public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
|
||||||
=> ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags);
|
|
||||||
|
|
||||||
public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
|
|
||||||
{
|
{
|
||||||
var design = Convert(data, equipFlags, customizeFlags, crestFlags, parameterFlags);
|
var design = Convert(data, materials, rules);
|
||||||
return ShareBase64(ShareJObject(design));
|
return ShareBase64(ShareJObject(design));
|
||||||
}
|
}
|
||||||
|
|
||||||
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
|
public DesignBase Convert(ActorState state, in ApplicationRules rules)
|
||||||
=> Convert(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags);
|
=> Convert(state.ModelData, state.Materials, rules);
|
||||||
|
|
||||||
public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
|
public DesignBase Convert(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
|
||||||
{
|
{
|
||||||
var design = _designs.CreateTemporary();
|
var design = _designs.CreateTemporary();
|
||||||
design.ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
rules.Apply(design);
|
||||||
design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
|
||||||
design.ApplyCrest = crestFlags & CrestExtensions.All;
|
|
||||||
design.ApplyParameters = parameterFlags & CustomizeParameterExtensions.All;
|
|
||||||
design.SetApplyMeta(MetaIndex.HatState, design.DoApplyEquip(EquipSlot.Head));
|
|
||||||
design.SetApplyMeta(MetaIndex.VisorState, design.DoApplyEquip(EquipSlot.Head));
|
|
||||||
design.SetApplyMeta(MetaIndex.WeaponState, design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
|
|
||||||
design.SetApplyMeta(MetaIndex.Wetness, true);
|
|
||||||
design.SetDesignData(_customize, data);
|
design.SetDesignData(_customize, data);
|
||||||
|
ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip);
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +136,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ShareBase64(JObject jObject)
|
private static string ShareBase64(JToken jObject)
|
||||||
{
|
{
|
||||||
var json = jObject.ToString(Formatting.None);
|
var json = jObject.ToString(Formatting.None);
|
||||||
var compressed = json.Compress(Version);
|
var compressed = json.Compress(Version);
|
||||||
|
|
@ -187,4 +184,29 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
||||||
|
|
||||||
yield return (EquipSlot.OffHand, oh, offhand.Stain);
|
yield return (EquipSlot.OffHand, oh, offhand.Stain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,
|
||||||
|
EquipFlag equipFlags = EquipFlagExtensions.All)
|
||||||
|
{
|
||||||
|
foreach (var (key, value) in materials.Values)
|
||||||
|
{
|
||||||
|
var idx = MaterialValueIndex.FromKey(key);
|
||||||
|
if (idx.RowIndex >= MtrlFile.ColorTable.NumRows)
|
||||||
|
continue;
|
||||||
|
if (idx.MaterialIndex >= MaterialService.MaterialsPerModel)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var slot = idx.DrawObject switch
|
||||||
|
{
|
||||||
|
MaterialValueIndex.DrawObjectType.Human => idx.SlotIndex < 10 ? ((uint)idx.SlotIndex).ToEquipSlot() : EquipSlot.Unknown,
|
||||||
|
MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0 => EquipSlot.MainHand,
|
||||||
|
MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0 => EquipSlot.OffHand,
|
||||||
|
_ => EquipSlot.Unknown,
|
||||||
|
};
|
||||||
|
if (slot is EquipSlot.Unknown || (slot.ToBothFlags() & equipFlags) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
manager.AddOrUpdateValue(idx, value.Convert());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -250,6 +251,14 @@ public class DesignEditor(
|
||||||
|
|
||||||
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
|
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
|
||||||
ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]);
|
ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]);
|
||||||
|
|
||||||
|
foreach (var (key, value) in other.Materials)
|
||||||
|
{
|
||||||
|
if (!value.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
design.GetMaterialDataRef().AddOrUpdateValue(MaterialValueIndex.FromKey(key), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. </summary>
|
/// <summary> Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. </summary>
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,18 @@ public sealed class DesignManager : DesignEditor
|
||||||
DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value);
|
DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Set the quick design bar display status of a design. </summary>
|
||||||
|
public void SetQuickDesign(Design design, bool value)
|
||||||
|
{
|
||||||
|
if (value == design.QuickDesign)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, value);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Edit Application Rules
|
#region Edit Application Rules
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,9 @@ public sealed class DesignChanged()
|
||||||
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
|
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
|
||||||
WriteProtection,
|
WriteProtection,
|
||||||
|
|
||||||
|
/// <summary> An existing design changed its display status for the quick design bar. Data is the new value [bool]. </summary>
|
||||||
|
QuickDesignBar,
|
||||||
|
|
||||||
/// <summary> An existing design changed one of the meta flags. Data is the flag, whether it was about their applying and the new value [(MetaFlag, bool, bool)]. </summary>
|
/// <summary> An existing design changed one of the meta flags. Data is the flag, whether it was about their applying and the new value [(MetaFlag, bool, bool)]. </summary>
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ namespace Glamourer.Events
|
||||||
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
|
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
|
||||||
Parameter,
|
Parameter,
|
||||||
|
|
||||||
|
/// <summary> A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)]. </summary>
|
||||||
|
MaterialValue,
|
||||||
|
|
||||||
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
|
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
|
||||||
Design,
|
Design,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using Newtonsoft.Json;
|
namespace Glamourer.GameData;
|
||||||
|
|
||||||
namespace Glamourer.GameData;
|
|
||||||
|
|
||||||
public readonly struct CustomizeParameterValue
|
public readonly struct CustomizeParameterValue
|
||||||
{
|
{
|
||||||
|
|
@ -50,3 +48,25 @@ public readonly struct CustomizeParameterValue
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> _data.ToString();
|
=> _data.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class VectorExtensions
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool NearEqual(this Vector3 lhs, Vector3 rhs, float eps = 1e-9f)
|
||||||
|
=> (lhs - rhs).LengthSquared() < eps;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool NearEqual(this Vector4 lhs, Vector4 rhs, float eps = 1e-9f)
|
||||||
|
=> (lhs - rhs).LengthSquared() < eps;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool NearEqual(this CustomizeParameterValue lhs, CustomizeParameterValue rhs, float eps = 1e-9f)
|
||||||
|
=> NearEqual(lhs.InternalQuadruple, rhs.InternalQuadruple, eps);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool NearEqual(this float lhs, float rhs, float eps = 1e-5f)
|
||||||
|
{
|
||||||
|
var diff = lhs - rhs;
|
||||||
|
return diff < 0 ? diff > -eps : diff < eps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@
|
||||||
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
<PackageReference Include="Dalamud.ContextMenu" Version="1.3.1" />
|
<PackageReference Include="Dalamud.ContextMenu" Version="1.3.1" />
|
||||||
|
<PackageReference Include="Vortice.Direct3D11" Version="3.4.2-beta" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
|
if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
|
||||||
ImGui.OpenPopup(ColorPickerPopupName);
|
ImGui.OpenPopup(ColorPickerPopupName);
|
||||||
|
else if (current >= 0 && CaptureMouseWheel(ref current, 0, _currentCount))
|
||||||
|
{
|
||||||
|
var data = _set.Data(_currentIndex, current, _customize.Face);
|
||||||
|
UpdateValue(data.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var npc = false;
|
var npc = false;
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ public partial class CustomizationDrawer
|
||||||
using var bigGroup = ImRaii.Group();
|
using var bigGroup = ImRaii.Group();
|
||||||
var label = _currentOption;
|
var label = _currentOption;
|
||||||
|
|
||||||
var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face);
|
var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face);
|
||||||
var npc = false;
|
var originalCurrent = current;
|
||||||
|
var npc = false;
|
||||||
if (current < 0)
|
if (current < 0)
|
||||||
{
|
{
|
||||||
label = $"{_currentOption} (NPC)";
|
label = $"{_currentOption} (NPC)";
|
||||||
|
|
@ -32,7 +33,14 @@ public partial class CustomizationDrawer
|
||||||
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
||||||
{
|
{
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||||
|
{
|
||||||
ImGui.OpenPopup(IconSelectorPopup);
|
ImGui.OpenPopup(IconSelectorPopup);
|
||||||
|
}
|
||||||
|
else if (originalCurrent >= 0 && CaptureMouseWheel(ref current, 0, _currentCount))
|
||||||
|
{
|
||||||
|
var data = _set.Data(_currentIndex, current, _customize.Face);
|
||||||
|
UpdateValue(data.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using OtterGuiInternal;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -34,7 +35,8 @@ public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
var tmp = (int)_currentByte.Value;
|
var tmp = (int)_currentByte.Value;
|
||||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||||
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp)
|
||||||
|
|| CaptureMouseWheel(ref tmp, 0, _currentCount - 1))
|
||||||
UpdateValue((CustomizeValue)tmp);
|
UpdateValue((CustomizeValue)tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,11 +44,10 @@ public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
var tmp = (int)_currentByte.Value;
|
var tmp = (int)_currentByte.Value;
|
||||||
ImGui.SetNextItemWidth(_inputIntSize);
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
|
var cap = ImGui.GetIO().KeyCtrl ? byte.MaxValue : _currentCount - 1;
|
||||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||||
{
|
{
|
||||||
var newValue = (CustomizeValue)(ImGui.GetIO().KeyCtrl
|
var newValue = (CustomizeValue)Math.Clamp(tmp, 0, cap);
|
||||||
? Math.Clamp(tmp, 0, byte.MaxValue)
|
|
||||||
: Math.Clamp(tmp, 0, _currentCount - 1));
|
|
||||||
UpdateValue(newValue);
|
UpdateValue(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +74,10 @@ public partial class CustomizationDrawer
|
||||||
else if (ImGui.GetIO().KeyCtrl)
|
else if (ImGui.GetIO().KeyCtrl)
|
||||||
UpdateValue((CustomizeValue)value);
|
UpdateValue((CustomizeValue)value);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CheckWheel();
|
||||||
|
}
|
||||||
|
|
||||||
if (!_withApply)
|
if (!_withApply)
|
||||||
ImGuiUtil.HoverTooltip("Hold Control to force updates with invalid/unknown options at your own risk.");
|
ImGuiUtil.HoverTooltip("Hold Control to force updates with invalid/unknown options at your own risk.");
|
||||||
|
|
@ -81,15 +86,29 @@ public partial class CustomizationDrawer
|
||||||
if (ImGuiUtil.DrawDisabledButton("-", new Vector2(ImGui.GetFrameHeight()), "Select the previous available option in order.",
|
if (ImGuiUtil.DrawDisabledButton("-", new Vector2(ImGui.GetFrameHeight()), "Select the previous available option in order.",
|
||||||
currentIndex <= 0))
|
currentIndex <= 0))
|
||||||
UpdateValue(_set.Data(_currentIndex, currentIndex - 1, _customize.Face).Value);
|
UpdateValue(_set.Data(_currentIndex, currentIndex - 1, _customize.Face).Value);
|
||||||
|
else
|
||||||
|
CheckWheel();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton("+", new Vector2(ImGui.GetFrameHeight()), "Select the next available option in order.",
|
if (ImGuiUtil.DrawDisabledButton("+", new Vector2(ImGui.GetFrameHeight()), "Select the next available option in order.",
|
||||||
currentIndex >= _currentCount - 1 || npc))
|
currentIndex >= _currentCount - 1 || npc))
|
||||||
UpdateValue(_set.Data(_currentIndex, currentIndex + 1, _customize.Face).Value);
|
UpdateValue(_set.Data(_currentIndex, currentIndex + 1, _customize.Face).Value);
|
||||||
|
else
|
||||||
|
CheckWheel();
|
||||||
|
return;
|
||||||
|
|
||||||
|
void CheckWheel()
|
||||||
|
{
|
||||||
|
if (currentIndex < 0 || !CaptureMouseWheel(ref currentIndex, 0, _currentCount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||||
|
UpdateValue(data.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawListSelector(CustomizeIndex index, bool indexedBy1)
|
private void DrawListSelector(CustomizeIndex index, bool indexedBy1)
|
||||||
{
|
{
|
||||||
using var id = SetId(index);
|
using var id = SetId(index);
|
||||||
using var bigGroup = ImRaii.Group();
|
using var bigGroup = ImRaii.Group();
|
||||||
|
|
||||||
using (_ = ImRaii.Disabled(_locked))
|
using (_ = ImRaii.Disabled(_locked))
|
||||||
|
|
@ -122,29 +141,31 @@ public partial class CustomizationDrawer
|
||||||
private void ListCombo0()
|
private void ListCombo0()
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||||
var current = _currentByte.Value;
|
var current = (int)_currentByte.Value;
|
||||||
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current + 1}");
|
using (var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current + 1}"))
|
||||||
|
|
||||||
if (!combo)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == current))
|
if (combo)
|
||||||
UpdateValue((CustomizeValue)i);
|
|
||||||
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == current))
|
||||||
|
UpdateValue((CustomizeValue)i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CaptureMouseWheel(ref current, 0, _currentCount))
|
||||||
|
UpdateValue((CustomizeValue)current);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ListInputInt0()
|
private void ListInputInt0()
|
||||||
{
|
{
|
||||||
var tmp = _currentByte.Value + 1;
|
var tmp = _currentByte.Value + 1;
|
||||||
ImGui.SetNextItemWidth(_inputIntSize);
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
|
var cap = ImGui.GetIO().KeyCtrl ? byte.MaxValue + 1 : _currentCount;
|
||||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||||
{
|
{
|
||||||
var newValue = (CustomizeValue)(ImGui.GetIO().KeyCtrl
|
var newValue = Math.Clamp(tmp, 1, cap);
|
||||||
? Math.Clamp(tmp, 1, byte.MaxValue + 1)
|
UpdateValue((CustomizeValue)(newValue - 1));
|
||||||
: Math.Clamp(tmp, 1, _currentCount));
|
|
||||||
UpdateValue(newValue - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]\n"
|
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]\n"
|
||||||
|
|
@ -154,28 +175,29 @@ public partial class CustomizationDrawer
|
||||||
private void ListCombo1()
|
private void ListCombo1()
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||||
var current = _currentByte.Value;
|
var current = (int)_currentByte.Value;
|
||||||
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current}");
|
using (var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current}"))
|
||||||
|
|
||||||
if (!combo)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (var i = 1; i <= _currentCount; ++i)
|
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable($"{_currentOption} #{i}##combo", i == current))
|
if (combo)
|
||||||
UpdateValue((CustomizeValue)i);
|
for (var i = 1; i <= _currentCount; ++i)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable($"{_currentOption} #{i}##combo", i == current))
|
||||||
|
UpdateValue((CustomizeValue)i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CaptureMouseWheel(ref current, 1, _currentCount))
|
||||||
|
UpdateValue((CustomizeValue)current);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ListInputInt1()
|
private void ListInputInt1()
|
||||||
{
|
{
|
||||||
var tmp = (int)_currentByte.Value;
|
var tmp = (int)_currentByte.Value;
|
||||||
ImGui.SetNextItemWidth(_inputIntSize);
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
|
var (offset, cap) = ImGui.GetIO().KeyCtrl ? (0, byte.MaxValue) : (1, _currentCount);
|
||||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||||
{
|
{
|
||||||
var newValue = (CustomizeValue)(ImGui.GetIO().KeyCtrl
|
var newValue = (CustomizeValue)Math.Clamp(tmp, offset, cap);
|
||||||
? Math.Clamp(tmp, 0, byte.MaxValue)
|
|
||||||
: Math.Clamp(tmp, 1, _currentCount));
|
|
||||||
UpdateValue(newValue);
|
UpdateValue(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,6 +205,26 @@ public partial class CustomizationDrawer
|
||||||
+ "Hold Control to force updates with invalid/unknown options at your own risk.");
|
+ "Hold Control to force updates with invalid/unknown options at your own risk.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CaptureMouseWheel(ref int value, int offset, int cap)
|
||||||
|
{
|
||||||
|
if (!ImGui.IsItemHovered() || !ImGui.GetIO().KeyCtrl)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ImGuiInternal.ItemSetUsingMouseWheel();
|
||||||
|
|
||||||
|
var mw = (int)ImGui.GetIO().MouseWheel;
|
||||||
|
if (mw == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value -= offset;
|
||||||
|
value = mw switch
|
||||||
|
{
|
||||||
|
< 0 => offset + (value + cap + mw) % cap,
|
||||||
|
_ => offset + (value + mw) % cap,
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Draw a customize checkbox.
|
// Draw a customize checkbox.
|
||||||
private void DrawCheckbox(CustomizeIndex idx)
|
private void DrawCheckbox(CustomizeIndex idx)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
using Glamourer.GameData;
|
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
|
@ -25,7 +24,7 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
|
||||||
|
|
||||||
protected DesignComboBase(Func<IReadOnlyList<Tuple<Design, string>>> generator, Logger log, DesignChanged designChanged,
|
protected DesignComboBase(Func<IReadOnlyList<Tuple<Design, string>>> generator, Logger log, DesignChanged designChanged,
|
||||||
TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
|
TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
|
||||||
: base(generator, log)
|
: base(generator, MouseWheelType.Unmodified, log)
|
||||||
{
|
{
|
||||||
_designChanged = designChanged;
|
_designChanged = designChanged;
|
||||||
TabSelected = tabSelected;
|
TabSelected = tabSelected;
|
||||||
|
|
@ -38,7 +37,10 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
|
||||||
=> _config.IncognitoMode;
|
=> _config.IncognitoMode;
|
||||||
|
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
=> _designChanged.Unsubscribe(OnDesignChange);
|
{
|
||||||
|
_designChanged.Unsubscribe(OnDesignChange);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
{
|
{
|
||||||
|
|
@ -118,63 +120,87 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<Design, string>>,
|
||||||
{
|
{
|
||||||
case DesignChanged.Type.Created:
|
case DesignChanged.Type.Created:
|
||||||
case DesignChanged.Type.Renamed:
|
case DesignChanged.Type.Renamed:
|
||||||
Cleanup();
|
case DesignChanged.Type.ChangedColor:
|
||||||
break;
|
|
||||||
case DesignChanged.Type.Deleted:
|
case DesignChanged.Type.Deleted:
|
||||||
Cleanup();
|
case DesignChanged.Type.QuickDesignBar:
|
||||||
if (CurrentSelection?.Item1 == design)
|
var priorState = IsInitialized;
|
||||||
|
if (priorState)
|
||||||
|
Cleanup();
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(s => ReferenceEquals(s.Item1, CurrentSelection?.Item1));
|
||||||
|
if (CurrentSelectionIdx >= 0)
|
||||||
{
|
{
|
||||||
CurrentSelectionIdx = Items.Count > 0 ? 0 : -1;
|
CurrentSelection = Items[CurrentSelectionIdx];
|
||||||
CurrentSelection = Items[CurrentSelectionIdx];
|
}
|
||||||
|
else if (Items.Count > 0)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = 0;
|
||||||
|
CurrentSelection = Items[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentSelection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!priorState)
|
||||||
|
Cleanup();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DesignCombo : DesignComboBase
|
public abstract class DesignCombo : DesignComboBase
|
||||||
{
|
{
|
||||||
private readonly DesignManager _manager;
|
protected DesignCombo(Logger log, DesignChanged designChanged, TabSelected tabSelected,
|
||||||
|
EphemeralConfig config, DesignColors designColors, Func<IReadOnlyList<Tuple<Design, string>>> generator)
|
||||||
public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected,
|
: base(generator, log, designChanged, tabSelected, config, designColors)
|
||||||
EphemeralConfig config, DesignColors designColors)
|
|
||||||
: base(() => designs.Designs
|
|
||||||
.Select(d => new Tuple<Design, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
|
||||||
.OrderBy(d => d.Item2)
|
|
||||||
.ToList(), log, designChanged, tabSelected, config, designColors)
|
|
||||||
{
|
{
|
||||||
_manager = designs;
|
if (Items.Count == 0)
|
||||||
if (designs.Designs.Count == 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CurrentSelection = Items[0];
|
CurrentSelection = Items[0];
|
||||||
CurrentSelectionIdx = 0;
|
CurrentSelectionIdx = 0;
|
||||||
|
base.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Design? Design
|
public Design? Design
|
||||||
=> CurrentSelection?.Item1;
|
=> CurrentSelection?.Item1;
|
||||||
|
|
||||||
public void Draw(float width)
|
public void Draw(float width)
|
||||||
{
|
=> Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width);
|
||||||
Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width);
|
|
||||||
if (ImGui.IsItemHovered() && _manager.Designs.Count > 1)
|
|
||||||
{
|
|
||||||
var mouseWheel = -(int)ImGui.GetIO().MouseWheel % _manager.Designs.Count;
|
|
||||||
CurrentSelectionIdx = mouseWheel switch
|
|
||||||
{
|
|
||||||
< 0 when CurrentSelectionIdx < 0 => _manager.Designs.Count - 1 + mouseWheel,
|
|
||||||
< 0 => (CurrentSelectionIdx + _manager.Designs.Count + mouseWheel) % _manager.Designs.Count,
|
|
||||||
> 0 when CurrentSelectionIdx < 0 => mouseWheel,
|
|
||||||
> 0 => (CurrentSelectionIdx + mouseWheel) % _manager.Designs.Count,
|
|
||||||
_ => CurrentSelectionIdx,
|
|
||||||
};
|
|
||||||
CurrentSelection = Items[CurrentSelectionIdx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RevertDesignCombo : DesignComboBase, IDisposable
|
public sealed class QuickDesignCombo(
|
||||||
|
DesignManager designs,
|
||||||
|
DesignFileSystem fileSystem,
|
||||||
|
Logger log,
|
||||||
|
DesignChanged designChanged,
|
||||||
|
TabSelected tabSelected,
|
||||||
|
EphemeralConfig config,
|
||||||
|
DesignColors designColors)
|
||||||
|
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
|
||||||
|
[
|
||||||
|
.. designs.Designs
|
||||||
|
.Where(d => d.QuickDesign)
|
||||||
|
.Select(d => new Tuple<Design, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
||||||
|
.OrderBy(d => d.Item2),
|
||||||
|
]);
|
||||||
|
|
||||||
|
public sealed class LinkDesignCombo(
|
||||||
|
DesignManager designs,
|
||||||
|
DesignFileSystem fileSystem,
|
||||||
|
Logger log,
|
||||||
|
DesignChanged designChanged,
|
||||||
|
TabSelected tabSelected,
|
||||||
|
EphemeralConfig config,
|
||||||
|
DesignColors designColors)
|
||||||
|
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
|
||||||
|
[
|
||||||
|
.. designs.Designs
|
||||||
|
.Select(d => new Tuple<Design, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
||||||
|
.OrderBy(d => d.Item2),
|
||||||
|
]);
|
||||||
|
|
||||||
|
public sealed class RevertDesignCombo : DesignComboBase
|
||||||
{
|
{
|
||||||
public const int RevertDesignIndex = -1228;
|
public const int RevertDesignIndex = -1228;
|
||||||
public readonly Design RevertDesign;
|
public readonly Design RevertDesign;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing;
|
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing;
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly DesignCombo _designCombo;
|
private readonly QuickDesignCombo _designCombo;
|
||||||
private readonly StateManager _stateManager;
|
private readonly StateManager _stateManager;
|
||||||
private readonly AutoDesignApplier _autoDesignApplier;
|
private readonly AutoDesignApplier _autoDesignApplier;
|
||||||
private readonly ObjectManager _objects;
|
private readonly ObjectManager _objects;
|
||||||
|
|
@ -34,7 +34,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
private DateTime _keyboardToggle = DateTime.UnixEpoch;
|
private DateTime _keyboardToggle = DateTime.UnixEpoch;
|
||||||
private int _numButtons;
|
private int _numButtons;
|
||||||
|
|
||||||
public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState,
|
public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState,
|
||||||
ObjectManager objects, AutoDesignApplier autoDesignApplier)
|
ObjectManager objects, AutoDesignApplier autoDesignApplier)
|
||||||
: base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking)
|
: base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking)
|
||||||
{
|
{
|
||||||
|
|
@ -299,7 +299,8 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
(true, false) => 3,
|
(true, false) => 3,
|
||||||
(false, false) => 2,
|
(false, false) => 2,
|
||||||
};
|
};
|
||||||
Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X, ImGui.GetFrameHeight());
|
Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X,
|
||||||
|
ImGui.GetFrameHeight());
|
||||||
return Size.Value.X;
|
return Size.Value.X;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using Penumbra.GameData.Structs;
|
||||||
namespace Glamourer.Gui.Equipment;
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites)
|
public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites)
|
||||||
: FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log)
|
: FilterComboColors(_comboWidth, MouseWheelType.Control, CreateFunc(_stains, _favorites), Glamourer.Log)
|
||||||
{
|
{
|
||||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
{
|
{
|
||||||
|
|
@ -36,6 +36,9 @@ public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, Fa
|
||||||
return base.DrawSelectable(globalIdx, selected);
|
return base.DrawSelectable(globalIdx, selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Draw(string label, uint color, string name, bool found, bool gloss, float previewWidth)
|
||||||
|
=> base.Draw(label, color, name, found, gloss, previewWidth);
|
||||||
|
|
||||||
private static Func<IReadOnlyList<KeyValuePair<byte, (string Name, uint Color, bool Gloss)>>> CreateFunc(DictStain stains,
|
private static Func<IReadOnlyList<KeyValuePair<byte, (string Name, uint Color, bool Gloss)>>> CreateFunc(DictStain stains,
|
||||||
FavoriteManager favorites)
|
FavoriteManager favorites)
|
||||||
=> () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp)
|
=> () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
||||||
public Variant CustomVariant { get; private set; }
|
public Variant CustomVariant { get; private set; }
|
||||||
|
|
||||||
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
|
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
|
||||||
: base(() => GetItems(favorites, items, slot), log)
|
: base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log)
|
||||||
{
|
{
|
||||||
_favorites = favorites;
|
_favorites = favorites;
|
||||||
Label = GetLabel(gameData, slot);
|
Label = GetLabel(gameData, slot);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
||||||
private float _innerWidth;
|
private float _innerWidth;
|
||||||
|
|
||||||
public WeaponCombo(ItemManager items, FullEquipType type, Logger log)
|
public WeaponCombo(ItemManager items, FullEquipType type, Logger log)
|
||||||
: base(() => GetWeapons(items, type), log)
|
: base(() => GetWeapons(items, type), MouseWheelType.Control, log)
|
||||||
{
|
{
|
||||||
Label = GetLabel(type);
|
Label = GetLabel(type);
|
||||||
SearchByParts = true;
|
SearchByParts = true;
|
||||||
|
|
|
||||||
166
Glamourer/Gui/Materials/MaterialDrawer.cs
Normal file
166
Glamourer/Gui/Materials/MaterialDrawer.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Materials;
|
||||||
|
|
||||||
|
public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager) : IService
|
||||||
|
{
|
||||||
|
private static readonly IReadOnlyList<MaterialValueIndex.DrawObjectType> Types =
|
||||||
|
[
|
||||||
|
MaterialValueIndex.DrawObjectType.Human,
|
||||||
|
MaterialValueIndex.DrawObjectType.Mainhand,
|
||||||
|
MaterialValueIndex.DrawObjectType.Offhand,
|
||||||
|
];
|
||||||
|
|
||||||
|
private ActorState? _state;
|
||||||
|
|
||||||
|
public void DrawActorPanel(Actor actor)
|
||||||
|
{
|
||||||
|
if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var model = actor.Model;
|
||||||
|
if (!model.IsHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (model.AsCharacterBase->SlotCount < 10)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Humans should have at least 10 slots for the equipment types. Technically more.
|
||||||
|
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
|
||||||
|
{
|
||||||
|
var item = model.GetArmor(slot).ToWeapon(0);
|
||||||
|
DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) idx, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor);
|
||||||
|
if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0)
|
||||||
|
DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0));
|
||||||
|
if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0)
|
||||||
|
DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index)
|
||||||
|
{
|
||||||
|
var drawnMaterial = 1;
|
||||||
|
for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex)
|
||||||
|
{
|
||||||
|
var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex;
|
||||||
|
if (*texture == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}");
|
||||||
|
if (!tree)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMaterial(ref MtrlFile.ColorTable table, CharacterWeapon drawData, MaterialValueIndex sourceIndex)
|
||||||
|
{
|
||||||
|
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||||
|
{
|
||||||
|
var index = sourceIndex with { RowIndex = i };
|
||||||
|
ref var row = ref table[i];
|
||||||
|
DrawRow(ref row, drawData, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(index.RowIndex);
|
||||||
|
var changed = _state!.Materials.TryGetValue(index, out var value);
|
||||||
|
if (!changed)
|
||||||
|
{
|
||||||
|
var internalRow = new ColorRow(row);
|
||||||
|
value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs);
|
||||||
|
ImGui.SameLine();
|
||||||
|
applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs);
|
||||||
|
ImGui.SameLine();
|
||||||
|
applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
||||||
|
applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
|
||||||
|
applied |= ImGui.DragFloat("Specular Strength", ref value.Model.SpecularStrength, 0.1f);
|
||||||
|
if (applied)
|
||||||
|
_stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value());
|
||||||
|
ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly IReadOnlyList<string> SlotNames =
|
||||||
|
[
|
||||||
|
"Slot 1",
|
||||||
|
"Slot 2",
|
||||||
|
"Slot 3",
|
||||||
|
"Slot 4",
|
||||||
|
"Slot 5",
|
||||||
|
"Slot 6",
|
||||||
|
"Slot 7",
|
||||||
|
"Slot 8",
|
||||||
|
"Slot 9",
|
||||||
|
"Slot 10",
|
||||||
|
"Slot 11",
|
||||||
|
"Slot 12",
|
||||||
|
"Slot 13",
|
||||||
|
"Slot 14",
|
||||||
|
"Slot 15",
|
||||||
|
"Slot 16",
|
||||||
|
"Slot 17",
|
||||||
|
"Slot 18",
|
||||||
|
"Slot 19",
|
||||||
|
"Slot 20",
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly IReadOnlyList<string> SlotNamesHuman =
|
||||||
|
[
|
||||||
|
"Head",
|
||||||
|
"Body",
|
||||||
|
"Hands",
|
||||||
|
"Legs",
|
||||||
|
"Feet",
|
||||||
|
"Earrings",
|
||||||
|
"Neck",
|
||||||
|
"Wrists",
|
||||||
|
"Right Finger",
|
||||||
|
"Left Finger",
|
||||||
|
"Slot 11",
|
||||||
|
"Slot 12",
|
||||||
|
"Slot 13",
|
||||||
|
"Slot 14",
|
||||||
|
"Slot 15",
|
||||||
|
"Slot 16",
|
||||||
|
"Slot 17",
|
||||||
|
"Slot 18",
|
||||||
|
"Slot 19",
|
||||||
|
"Slot 20",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,9 @@ using Glamourer.Automation;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui.Customization;
|
using Glamourer.Gui.Customization;
|
||||||
using Glamourer.Gui.Equipment;
|
using Glamourer.Gui.Equipment;
|
||||||
|
using Glamourer.Gui.Materials;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -33,7 +35,8 @@ public class ActorPanel(
|
||||||
ImportService _importService,
|
ImportService _importService,
|
||||||
ICondition _conditions,
|
ICondition _conditions,
|
||||||
DictModelChara _modelChara,
|
DictModelChara _modelChara,
|
||||||
CustomizeParameterDrawer _parameterDrawer)
|
CustomizeParameterDrawer _parameterDrawer,
|
||||||
|
MaterialDrawer _materialDrawer)
|
||||||
{
|
{
|
||||||
private ActorIdentifier _identifier;
|
private ActorIdentifier _identifier;
|
||||||
private string _actorName = string.Empty;
|
private string _actorName = string.Empty;
|
||||||
|
|
@ -114,6 +117,9 @@ public class ActorPanel(
|
||||||
|
|
||||||
RevertButtons();
|
RevertButtons();
|
||||||
|
|
||||||
|
// TODO Materials
|
||||||
|
//if (ImGui.CollapsingHeader("Material Shit"))
|
||||||
|
// _materialDrawer.DrawActorPanel(_actor);
|
||||||
using var disabled = ImRaii.Disabled(transformationId != 0);
|
using var disabled = ImRaii.Disabled(transformationId != 0);
|
||||||
if (_state.ModelData.IsHuman)
|
if (_state.ModelData.IsHuman)
|
||||||
DrawHumanPanel();
|
DrawHumanPanel();
|
||||||
|
|
@ -316,8 +322,7 @@ public class ActorPanel(
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup("Save as Design");
|
ImGui.OpenPopup("Save as Design");
|
||||||
_newName = _state!.Identifier.ToName();
|
_newName = _state!.Identifier.ToName();
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
_newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state));
|
||||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveDesignDrawPopup()
|
private void SaveDesignDrawPopup()
|
||||||
|
|
@ -352,8 +357,7 @@ public class ActorPanel(
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
var text = _converter.ShareBase64(_state!, ApplicationRules.FromModifiers(_state!));
|
||||||
var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest, applyParameters);
|
|
||||||
ImGui.SetClipboardText(text);
|
ImGui.SetClipboardText(text);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -392,9 +396,8 @@ public class ActorPanel(
|
||||||
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
|
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
|
||||||
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
_stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters),
|
_stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)),
|
||||||
ApplySettings.Manual);
|
ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,9 +413,8 @@ public class ActorPanel(
|
||||||
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
|
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
|
||||||
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
_stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters),
|
_stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)),
|
||||||
ApplySettings.Manual);
|
ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public sealed class HumanNpcCombo(
|
||||||
DictBNpc bNpcs,
|
DictBNpc bNpcs,
|
||||||
HumanModelList humans,
|
HumanModelList humans,
|
||||||
Logger log)
|
Logger log)
|
||||||
: FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), log)
|
: FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), MouseWheelType.None, log)
|
||||||
{
|
{
|
||||||
protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj)
|
protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj)
|
||||||
=> obj.Name;
|
=> obj.Name;
|
||||||
|
|
|
||||||
|
|
@ -429,7 +429,7 @@ public class SetPanel(
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log)
|
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log)
|
||||||
: FilterComboCache<JobGroup>(() => jobs.JobGroups.Values.ToList(), log)
|
: FilterComboCache<JobGroup>(() => jobs.JobGroups.Values.ToList(), MouseWheelType.None, log)
|
||||||
{
|
{
|
||||||
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
|
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Events;
|
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,4 @@ public class DatFilePanel(ImportService _importService) : IGameDataDrawer
|
||||||
ImGui.TextUnformatted(_datFile.Value.Description);
|
ImGui.TextUnformatted(_datFile.Value.Description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,8 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
|
||||||
provider.GetRequiredService<ObjectManagerPanel>(),
|
provider.GetRequiredService<ObjectManagerPanel>(),
|
||||||
provider.GetRequiredService<PenumbraPanel>(),
|
provider.GetRequiredService<PenumbraPanel>(),
|
||||||
provider.GetRequiredService<IpcTesterPanel>(),
|
provider.GetRequiredService<IpcTesterPanel>(),
|
||||||
provider.GetRequiredService<DatFilePanel>()
|
provider.GetRequiredService<DatFilePanel>(),
|
||||||
|
provider.GetRequiredService<GlamourPlatePanel>()
|
||||||
);
|
);
|
||||||
|
|
||||||
public static DebugTabHeader CreateGameData(IServiceProvider provider)
|
public static DebugTabHeader CreateGameData(IServiceProvider provider)
|
||||||
|
|
|
||||||
135
Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs
Normal file
135
Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Glamourer.State;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Gui.Debug;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||||
|
|
||||||
|
public unsafe class GlamourPlatePanel : IGameDataDrawer
|
||||||
|
{
|
||||||
|
private readonly DesignManager _design;
|
||||||
|
private readonly ItemManager _items;
|
||||||
|
private readonly StateManager _state;
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
|
||||||
|
public string Label
|
||||||
|
=> "Glamour Plates";
|
||||||
|
|
||||||
|
public bool Disabled
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, ObjectManager objects)
|
||||||
|
{
|
||||||
|
_items = items;
|
||||||
|
_design = design;
|
||||||
|
_state = state;
|
||||||
|
_objects = objects;
|
||||||
|
interop.InitializeFromAttributes(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var manager = MirageManager.Instance();
|
||||||
|
using (ImRaii.Group())
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Address:");
|
||||||
|
ImGui.TextUnformatted("Number of Glamour Plates:");
|
||||||
|
ImGui.TextUnformatted("Glamour Plates Requested:");
|
||||||
|
ImGui.TextUnformatted("Glamour Plates Loaded:");
|
||||||
|
ImGui.TextUnformatted("Is Applying Glamour Plates:");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImRaii.Group())
|
||||||
|
{
|
||||||
|
ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}");
|
||||||
|
ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesSpan.Length.ToString());
|
||||||
|
ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString());
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.SmallButton("Request Update"))
|
||||||
|
RequestGlamour();
|
||||||
|
ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString());
|
||||||
|
ImGui.TextUnformatted(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActorState? state = null;
|
||||||
|
var (identifier, data) = _objects.PlayerData;
|
||||||
|
var enabled = data.Valid && _state.GetOrCreate(identifier, data.Objects[0], out state);
|
||||||
|
|
||||||
|
for (var i = 0; i < manager->GlamourPlatesSpan.Length; ++i)
|
||||||
|
{
|
||||||
|
using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}");
|
||||||
|
if (!tree)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ref var plate = ref manager->GlamourPlatesSpan[i];
|
||||||
|
if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled))
|
||||||
|
{
|
||||||
|
var design = CreateDesign(plate);
|
||||||
|
_state.ApplyDesign(state!, design, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (ImRaii.Group())
|
||||||
|
{
|
||||||
|
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||||
|
ImGui.TextUnformatted(slot.ToName());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImRaii.Group())
|
||||||
|
{
|
||||||
|
foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex())
|
||||||
|
ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {plate.StainIds[index]:D3}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 32 C0 48 8B 5C 24 ?? 48 8B 6C 24 ?? 48 83 C4 ?? 5F")]
|
||||||
|
private readonly delegate* unmanaged<MirageManager*, void> _requestUpdate = null!;
|
||||||
|
|
||||||
|
public void RequestGlamour()
|
||||||
|
{
|
||||||
|
var manager = MirageManager.Instance();
|
||||||
|
if (manager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_requestUpdate(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DesignBase CreateDesign(in MirageManager.GlamourPlate plate)
|
||||||
|
{
|
||||||
|
var design = _design.CreateTemporary();
|
||||||
|
design.ApplyCustomize = 0;
|
||||||
|
design.ApplyCrest = 0;
|
||||||
|
design.ApplyMeta = 0;
|
||||||
|
design.ApplyParameters = 0;
|
||||||
|
design.ApplyEquip = 0;
|
||||||
|
foreach (var (slot, index) in EquipSlotExtensions.FullSlots.WithIndex())
|
||||||
|
{
|
||||||
|
var itemId = plate.ItemIds[index];
|
||||||
|
if (itemId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var item = _items.Resolve(slot, itemId);
|
||||||
|
if (!item.Valid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
design.GetDesignDataRef().SetItem(slot, item);
|
||||||
|
design.GetDesignDataRef().SetStain(slot, plate.StainIds[index]);
|
||||||
|
design.ApplyEquip |= slot.ToBothFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
return design;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,9 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
||||||
private string _base64Apply = string.Empty;
|
private string _base64Apply = string.Empty;
|
||||||
private string _designIdentifier = string.Empty;
|
private string _designIdentifier = string.Empty;
|
||||||
private GlamourerIpc.GlamourerErrorCode _setItemEc;
|
private GlamourerIpc.GlamourerErrorCode _setItemEc;
|
||||||
|
private GlamourerIpc.GlamourerErrorCode _setItemOnceEc;
|
||||||
private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc;
|
private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc;
|
||||||
|
private GlamourerIpc.GlamourerErrorCode _setItemOnceByActorNameEc;
|
||||||
|
|
||||||
public unsafe void Draw()
|
public unsafe void Draw()
|
||||||
{
|
{
|
||||||
|
|
@ -77,12 +79,23 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
||||||
if (ImGui.Button("Apply##AllName"))
|
if (ImGui.Button("Apply##AllName"))
|
||||||
GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName);
|
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);
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGui.Button("Apply##AllCharacter"))
|
if (ImGui.Button("Apply##AllCharacter"))
|
||||||
GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface)
|
GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface)
|
||||||
.Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character);
|
.Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character);
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.Button("Apply Once##AllCharacter"))
|
||||||
|
GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface)
|
||||||
|
.Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character);
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment);
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGui.Button("Apply##EquipName"))
|
if (ImGui.Button("Apply##EquipName"))
|
||||||
|
|
@ -111,12 +124,23 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
||||||
if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1))
|
if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1))
|
||||||
GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName);
|
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);
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2))
|
if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2))
|
||||||
GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface)
|
GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface)
|
||||||
.Invoke(guid2, _objectManager.Objects[_gameObjectIndex] as Character);
|
.Invoke(guid2, _objectManager.Objects[_gameObjectIndex] as Character);
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once))
|
||||||
|
GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface)
|
||||||
|
.Invoke(guid2Once, _objectManager.Objects[_gameObjectIndex] as Character);
|
||||||
|
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock);
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -149,6 +173,17 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
||||||
ImGui.TextUnformatted(_setItemEc.ToString());
|
ImGui.TextUnformatted(_setItemEc.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.Button("Set Once##SetItem"))
|
||||||
|
_setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface)
|
||||||
|
.Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
|
||||||
|
if (_setItemOnceEc != GlamourerIpc.GlamourerErrorCode.Success)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted(_setItemOnceEc.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName);
|
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGui.Button("Set##SetItemByActorName"))
|
if (ImGui.Button("Set##SetItemByActorName"))
|
||||||
|
|
@ -159,6 +194,17 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(_setItemByActorNameEc.ToString());
|
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()
|
private void DrawItemInput()
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,15 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom
|
||||||
FilterComboCache<string>(_skipAutomatic
|
FilterComboCache<string>(_skipAutomatic
|
||||||
? _designColors.Keys.OrderBy(k => k)
|
? _designColors.Keys.OrderBy(k => k)
|
||||||
: _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName),
|
: _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName),
|
||||||
Glamourer.Log)
|
MouseWheelType.Control, Glamourer.Log)
|
||||||
{
|
{
|
||||||
|
protected override void OnMouseWheel(string preview, ref int current, int steps)
|
||||||
|
{
|
||||||
|
if (CurrentSelectionIdx < 0)
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(preview);
|
||||||
|
base.OnMouseWheel(preview, ref current, steps);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
{
|
{
|
||||||
var isAutomatic = !_skipAutomatic && globalIdx == 0;
|
var isAutomatic = !_skipAutomatic && globalIdx == 0;
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,13 @@ public class DesignDetailTab
|
||||||
Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}",
|
Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}",
|
||||||
NotificationType.Warning);
|
NotificationType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
|
ImGui.SetClipboardText(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.");
|
ImGuiUtil.HoverTooltip(
|
||||||
|
$"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard.");
|
||||||
|
|
||||||
ImGuiUtil.DrawFrameColumn("Full Selector Path");
|
ImGuiUtil.DrawFrameColumn("Full Selector Path");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -121,19 +125,33 @@ public class DesignDetailTab
|
||||||
Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error);
|
Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.DrawFrameColumn("Quick Design Bar");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGui.RadioButton("Display##qdb", _selector.Selected.QuickDesign))
|
||||||
|
_manager.SetQuickDesign(_selector.Selected!, true);
|
||||||
|
var hovered = ImGui.IsItemHovered();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Hide##qdb", !_selector.Selected.QuickDesign))
|
||||||
|
_manager.SetQuickDesign(_selector.Selected!, false);
|
||||||
|
if (hovered || ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Display or hide this design in your quick design bar.");
|
||||||
|
|
||||||
ImGuiUtil.DrawFrameColumn("Color");
|
ImGuiUtil.DrawFrameColumn("Color");
|
||||||
var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color;
|
var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color;
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design. Right-Click to revert to automatic coloring.",
|
if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n"
|
||||||
|
+ "Right-Click to revert to automatic coloring.\n"
|
||||||
|
+ "Hold Control and scroll the mousewheel to scroll.",
|
||||||
width.X - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
|
width.X - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
|
||||||
&& _colorCombo.CurrentSelection != null)
|
&& _colorCombo.CurrentSelection != null)
|
||||||
{
|
{
|
||||||
colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
|
colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
|
||||||
_manager.ChangeColor(_selector.Selected!, colorName);
|
_manager.ChangeColor(_selector.Selected!, colorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
_manager.ChangeColor(_selector.Selected!, string.Empty);
|
_manager.ChangeColor(_selector.Selected!, string.Empty);
|
||||||
|
|
||||||
if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor))
|
if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor))
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using OtterGui.Services;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
||||||
public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService
|
public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, LinkDesignCombo _combo) : IUiService
|
||||||
{
|
{
|
||||||
private int _dragDropIndex = -1;
|
private int _dragDropIndex = -1;
|
||||||
private LinkOrder _dragDropOrder = LinkOrder.None;
|
private LinkOrder _dragDropOrder = LinkOrder.None;
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,25 @@ public class DesignPanel(
|
||||||
_parameterDrawer.Draw(_manager, _selector.Selected!);
|
_parameterDrawer.Draw(_manager, _selector.Selected!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawMaterialValues()
|
||||||
|
{
|
||||||
|
if (!_config.UseAdvancedParameters)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var h = ImRaii.CollapsingHeader("Advanced Dyes");
|
||||||
|
if (!h)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var ((key, value), i) in _selector.Selected!.Materials.WithIndex())
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(i);
|
||||||
|
ImGui.TextUnformatted($"{key:X16}");
|
||||||
|
ImGui.SameLine();
|
||||||
|
var enabled = value.Enabled;
|
||||||
|
ImGui.Checkbox("Enabled", ref enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawCustomizeApplication()
|
private void DrawCustomizeApplication()
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId("Customizations");
|
using var id = ImRaii.PushId("Customizations");
|
||||||
|
|
@ -293,10 +312,10 @@ public class DesignPanel(
|
||||||
|
|
||||||
var labels = new[]
|
var labels = new[]
|
||||||
{
|
{
|
||||||
|
"Apply Wetness",
|
||||||
"Apply Hat Visibility",
|
"Apply Hat Visibility",
|
||||||
"Apply Visor State",
|
"Apply Visor State",
|
||||||
"Apply Weapon Visibility",
|
"Apply Weapon Visibility",
|
||||||
"Apply Wetness",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels))
|
foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels))
|
||||||
|
|
@ -365,6 +384,7 @@ public class DesignPanel(
|
||||||
DrawCustomize();
|
DrawCustomize();
|
||||||
DrawEquipment();
|
DrawEquipment();
|
||||||
DrawCustomizeParameters();
|
DrawCustomizeParameters();
|
||||||
|
//DrawMaterialValues(); TODO Materials
|
||||||
_designDetails.Draw();
|
_designDetails.Draw();
|
||||||
DrawApplicationRules();
|
DrawApplicationRules();
|
||||||
_modAssociations.Draw();
|
_modAssociations.Draw();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)>
|
public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)>
|
||||||
{
|
{
|
||||||
public ModCombo(PenumbraService penumbra, Logger log)
|
public ModCombo(PenumbraService penumbra, Logger log)
|
||||||
: base(penumbra.GetMods, log)
|
: base(penumbra.GetMods, MouseWheelType.None, log)
|
||||||
{
|
{
|
||||||
SearchByParts = false;
|
SearchByParts = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Dalamud.Interface.Utility;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Filesystem;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||||
|
|
@ -21,6 +22,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager
|
||||||
DrawDesignList();
|
DrawDesignList();
|
||||||
var offset = DrawMultiTagger(width);
|
var offset = DrawMultiTagger(width);
|
||||||
DrawMultiColor(width, offset);
|
DrawMultiColor(width, offset);
|
||||||
|
DrawMultiQuickDesignBar(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDesignList()
|
private void DrawDesignList()
|
||||||
|
|
@ -35,6 +37,8 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager
|
||||||
var sizeMods = availableSizePercent * 35;
|
var sizeMods = availableSizePercent * 35;
|
||||||
var sizeFolders = availableSizePercent * 65;
|
var sizeFolders = availableSizePercent * 65;
|
||||||
|
|
||||||
|
_numQuickDesignEnabled = 0;
|
||||||
|
_numDesigns = 0;
|
||||||
using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg))
|
using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg))
|
||||||
{
|
{
|
||||||
if (!table)
|
if (!table)
|
||||||
|
|
@ -61,15 +65,24 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(fullName);
|
ImGui.TextUnformatted(fullName);
|
||||||
|
|
||||||
|
if (path is not DesignFileSystem.Leaf l2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
++_numDesigns;
|
||||||
|
if (l2.Value.QuickDesign)
|
||||||
|
++_numQuickDesignEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _tag = string.Empty;
|
private string _tag = string.Empty;
|
||||||
private readonly List<Design> _addDesigns = [];
|
private int _numQuickDesignEnabled;
|
||||||
private readonly List<(Design, int)> _removeDesigns = [];
|
private int _numDesigns;
|
||||||
|
private readonly List<Design> _addDesigns = [];
|
||||||
|
private readonly List<(Design, int)> _removeDesigns = [];
|
||||||
|
|
||||||
private float DrawMultiTagger(Vector2 width)
|
private float DrawMultiTagger(Vector2 width)
|
||||||
{
|
{
|
||||||
|
|
@ -110,6 +123,30 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawMultiQuickDesignBar(float offset)
|
||||||
|
{
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted("Multi QDB:");
|
||||||
|
ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X);
|
||||||
|
var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0);
|
||||||
|
var diff = _numDesigns - _numQuickDesignEnabled;
|
||||||
|
var tt = diff == 0
|
||||||
|
? $"All {_numDesigns} selected designs are already displayed in the quick design bar."
|
||||||
|
: $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs.";
|
||||||
|
if (ImGuiUtil.DrawDisabledButton("Display Selected Designs in QDB", buttonWidth, tt, diff == 0))
|
||||||
|
foreach(var design in _selector.SelectedPaths.OfType<DesignFileSystem.Leaf>())
|
||||||
|
_editor.SetQuickDesign(design.Value, true);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
tt = _numQuickDesignEnabled == 0
|
||||||
|
? $"All {_numDesigns} selected designs are already hidden in the quick design bar."
|
||||||
|
: $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs.";
|
||||||
|
if (ImGuiUtil.DrawDisabledButton("Hide Selected Designs in QDB", buttonWidth, tt, _numQuickDesignEnabled == 0))
|
||||||
|
foreach (var design in _selector.SelectedPaths.OfType<DesignFileSystem.Leaf>())
|
||||||
|
_editor.SetQuickDesign(design.Value, false);
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawMultiColor(Vector2 width, float offset)
|
private void DrawMultiColor(Vector2 width, float offset)
|
||||||
{
|
{
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using OtterGui.Widgets;
|
||||||
namespace Glamourer.Gui.Tabs;
|
namespace Glamourer.Gui.Tabs;
|
||||||
|
|
||||||
public class NpcCombo(NpcCustomizeSet npcCustomizeSet)
|
public class NpcCombo(NpcCustomizeSet npcCustomizeSet)
|
||||||
: FilterComboCache<NpcData>(npcCustomizeSet, Glamourer.Log)
|
: FilterComboCache<NpcData>(npcCustomizeSet, MouseWheelType.None, Glamourer.Log)
|
||||||
{
|
{
|
||||||
protected override string ToString(NpcData obj)
|
protected override string ToString(NpcData obj)
|
||||||
=> obj.Name;
|
=> obj.Name;
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,8 @@ public class NpcPanel(
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
|
||||||
var data = ToDesignData();
|
var data = ToDesignData();
|
||||||
var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest, applyParameters);
|
var text = _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||||
ImGui.SetClipboardText(text);
|
ImGui.SetClipboardText(text);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -100,11 +99,9 @@ public class NpcPanel(
|
||||||
private void SaveDesignOpen()
|
private void SaveDesignOpen()
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup("Save as Design");
|
ImGui.OpenPopup("Save as Design");
|
||||||
_newName = _selector.Selection.Name;
|
_newName = _selector.Selection.Name;
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
|
||||||
|
|
||||||
var data = ToDesignData();
|
var data = ToDesignData();
|
||||||
_newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest, applyParameters);
|
_newDesign = _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveDesignDrawPopup()
|
private void SaveDesignDrawPopup()
|
||||||
|
|
@ -198,8 +195,7 @@ public class NpcPanel(
|
||||||
|
|
||||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
{
|
{
|
||||||
var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags();
|
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||||
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0);
|
|
||||||
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,8 +213,7 @@ public class NpcPanel(
|
||||||
|
|
||||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
{
|
{
|
||||||
var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags();
|
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||||
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0);
|
|
||||||
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +247,9 @@ public class NpcPanel(
|
||||||
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
|
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (_colorCombo.Draw("##colorCombo", colorName,
|
if (_colorCombo.Draw("##colorCombo", colorName,
|
||||||
"Associate a color with this NPC appearance. Right-Click to revert to automatic coloring.",
|
"Associate a color with this NPC appearance.\n"
|
||||||
|
+ "Right-Click to revert to automatic coloring.\n"
|
||||||
|
+ "Hold Control and scroll the mousewheel to scroll.",
|
||||||
width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
|
width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
|
||||||
&& _colorCombo.CurrentSelection != null)
|
&& _colorCombo.CurrentSelection != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,11 @@ public class SettingsTab(
|
||||||
"Enable the display and editing of advanced customization options like arbitrary colors.",
|
"Enable the display and editing of advanced customization options like arbitrary colors.",
|
||||||
config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters);
|
config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters);
|
||||||
PaletteImportButton();
|
PaletteImportButton();
|
||||||
|
Checkbox("Always Apply Associated Mods",
|
||||||
|
"Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"
|
||||||
|
+ "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"
|
||||||
|
+ "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.",
|
||||||
|
config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
116
Glamourer/Interop/Material/DirectXTextureHelper.cs
Normal file
116
Glamourer/Interop/Material/DirectXTextureHelper.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
using SharpGen.Runtime;
|
||||||
|
using Vortice.Direct3D11;
|
||||||
|
using Vortice.DXGI;
|
||||||
|
using MapFlags = Vortice.Direct3D11.MapFlags;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
public static unsafe class DirectXTextureHelper
|
||||||
|
{
|
||||||
|
/// <summary> Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. </summary>
|
||||||
|
/// <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>
|
||||||
|
public static bool TryGetColorTable(Texture* texture, out MtrlFile.ColorTable table)
|
||||||
|
{
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
table = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create direct x resource and ensure that it is kept alive.
|
||||||
|
using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D);
|
||||||
|
tex.AddRef();
|
||||||
|
|
||||||
|
table = GetResourceData(tex, CreateStagedClone, GetTextureData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Create a staging clone of the existing texture handle for stability reasons. </summary>
|
||||||
|
private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource)
|
||||||
|
{
|
||||||
|
var desc = resource.Description1 with
|
||||||
|
{
|
||||||
|
Usage = ResourceUsage.Staging,
|
||||||
|
BindFlags = 0,
|
||||||
|
CPUAccessFlags = CpuAccessFlags.Read,
|
||||||
|
MiscFlags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return resource.Device.As<ID3D11Device3>().CreateTexture2D1(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Turn a mapped texture into a color table. </summary>
|
||||||
|
private static MtrlFile.ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map)
|
||||||
|
{
|
||||||
|
var desc = resource.Description1;
|
||||||
|
|
||||||
|
if (desc.Format is not Format.R16G16B16A16_Float
|
||||||
|
|| desc.Width != MaterialService.TextureWidth
|
||||||
|
|| desc.Height != MaterialService.TextureHeight
|
||||||
|
|| map.DepthPitch != map.RowPitch * desc.Height)
|
||||||
|
throw new InvalidDataException("The texture was not a valid color table texture.");
|
||||||
|
|
||||||
|
return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Transform the GPU data into the color table. </summary>
|
||||||
|
/// <param name="data"> The pointer to the raw texture data. </param>
|
||||||
|
/// <param name="length"> The size of the raw texture data. </param>
|
||||||
|
/// <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 MtrlFile.ColorTable 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(MtrlFile.ColorTable) != expectedSize || height != MaterialService.TextureHeight)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var ret = new MtrlFile.ColorTable();
|
||||||
|
var target = (byte*)&ret;
|
||||||
|
// If the stride is the same as in the table, just copy.
|
||||||
|
if (pitch == MaterialService.TextureWidth)
|
||||||
|
MemoryUtility.MemCpyUnchecked(target, (void*)data, length);
|
||||||
|
// Otherwise, adapt the stride.
|
||||||
|
else
|
||||||
|
|
||||||
|
for (var y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch,
|
||||||
|
MaterialService.TextureWidth * sizeof(Half) * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Get resources of a texture. </summary>
|
||||||
|
private static TRet GetResourceData<T, TRet>(T res, Func<T, T> cloneResource, Func<T, MappedSubresource, TRet> getData)
|
||||||
|
where T : ID3D11Resource
|
||||||
|
{
|
||||||
|
using var stagingRes = cloneResource(res);
|
||||||
|
|
||||||
|
res.Device.ImmediateContext.CopyResource(stagingRes, res);
|
||||||
|
stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return getData(stagingRes, mapInfo);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
199
Glamourer/Interop/Material/MaterialManager.cs
Normal file
199
Glamourer/Interop/Material/MaterialManager.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly PrepareColorSet _event;
|
||||||
|
private readonly StateManager _stateManager;
|
||||||
|
private readonly PenumbraService _penumbra;
|
||||||
|
private readonly ActorManager _actors;
|
||||||
|
|
||||||
|
private int _lastSlot;
|
||||||
|
|
||||||
|
private readonly ThreadLocal<List<MaterialValueIndex>> _deleteList = new(() => []);
|
||||||
|
|
||||||
|
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra)
|
||||||
|
{
|
||||||
|
_stateManager = stateManager;
|
||||||
|
_actors = actors;
|
||||||
|
_penumbra = penumbra;
|
||||||
|
_event = prepareColorSet;
|
||||||
|
|
||||||
|
// TODO Material
|
||||||
|
//_event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _event.Unsubscribe(OnPrepareColorSet);
|
||||||
|
|
||||||
|
private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret)
|
||||||
|
{
|
||||||
|
var actor = _penumbra.GameObjectFromDrawObject(characterBase);
|
||||||
|
var validType = FindType(characterBase, actor, out var type);
|
||||||
|
var (slotId, materialId) = FindMaterial(characterBase, material);
|
||||||
|
|
||||||
|
if (!validType
|
||||||
|
|| slotId > 9
|
||||||
|
|| type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0
|
||||||
|
|| !actor.Identifier(_actors, out var identifier)
|
||||||
|
|| !_stateManager.TryGetValue(identifier, out var state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var min = MaterialValueIndex.Min(type, slotId, materialId);
|
||||||
|
var max = MaterialValueIndex.Max(type, slotId, materialId);
|
||||||
|
var values = state.Materials.GetValues(min, max);
|
||||||
|
if (values.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var drawData = type switch
|
||||||
|
{
|
||||||
|
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId),
|
||||||
|
_ => GetTempSlot((Weapon*)characterBase),
|
||||||
|
};
|
||||||
|
UpdateMaterialValues(state, values, drawData, ref baseColorSet);
|
||||||
|
|
||||||
|
if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture))
|
||||||
|
ret = (nint)texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
var deleteList = _deleteList.Value!;
|
||||||
|
deleteList.Clear();
|
||||||
|
for (var i = 0; i < values.Length; ++i)
|
||||||
|
{
|
||||||
|
var idx = MaterialValueIndex.FromKey(values[i].Key);
|
||||||
|
var materialValue = values[i].Value;
|
||||||
|
ref var row = ref colorTable[idx.RowIndex];
|
||||||
|
var newGame = new ColorRow(row);
|
||||||
|
if (materialValue.EqualGame(newGame, drawData))
|
||||||
|
materialValue.Model.Apply(ref row);
|
||||||
|
else
|
||||||
|
switch (materialValue.Source)
|
||||||
|
{
|
||||||
|
case StateSource.Pending:
|
||||||
|
materialValue.Model.Apply(ref row);
|
||||||
|
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual),
|
||||||
|
out _);
|
||||||
|
break;
|
||||||
|
case StateSource.IpcManual:
|
||||||
|
case StateSource.Manual:
|
||||||
|
deleteList.Add(idx);
|
||||||
|
break;
|
||||||
|
case StateSource.Fixed:
|
||||||
|
case StateSource.IpcFixed:
|
||||||
|
materialValue.Model.Apply(ref row);
|
||||||
|
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source),
|
||||||
|
out _);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var idx in deleteList)
|
||||||
|
_stateManager.ChangeMaterialValue(state, idx, default, ApplySettings.Game);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the index of a material by searching through a draw objects pointers.
|
||||||
|
/// Tries to take shortcuts for consecutive searches like when a character is newly created.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material)
|
||||||
|
{
|
||||||
|
for (var i = _lastSlot; i < characterBase->SlotCount; ++i)
|
||||||
|
{
|
||||||
|
var idx = MaterialService.MaterialsPerModel * i;
|
||||||
|
for (var j = 0; j < MaterialService.MaterialsPerModel; ++j)
|
||||||
|
{
|
||||||
|
var mat = (nint)characterBase->Materials[idx++];
|
||||||
|
if (mat != (nint)material)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_lastSlot = i;
|
||||||
|
return ((byte)i, (byte)j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _lastSlot; ++i)
|
||||||
|
{
|
||||||
|
var idx = MaterialService.MaterialsPerModel * i;
|
||||||
|
for (var j = 0; j < MaterialService.MaterialsPerModel; ++j)
|
||||||
|
{
|
||||||
|
var mat = (nint)characterBase->Materials[idx++];
|
||||||
|
if (mat != (nint)material)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_lastSlot = i;
|
||||||
|
return ((byte)i, (byte)j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte.MaxValue, byte.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Find the type of the given draw object by checking the actors pointers. </summary>
|
||||||
|
private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type)
|
||||||
|
{
|
||||||
|
type = MaterialValueIndex.DrawObjectType.Human;
|
||||||
|
if (!actor.Valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (actor.Model.AsCharacterBase == characterBase)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!actor.AsObject->IsCharacter())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase)
|
||||||
|
{
|
||||||
|
type = MaterialValueIndex.DrawObjectType.Mainhand;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase)
|
||||||
|
{
|
||||||
|
type = MaterialValueIndex.DrawObjectType.Offhand;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> We need to get the temporary set, variant and stain that is currently being set if it is available. </summary>
|
||||||
|
private CharacterWeapon GetTempSlot(Human* human, byte slotId)
|
||||||
|
{
|
||||||
|
if (human->ChangedEquipData == null)
|
||||||
|
return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0);
|
||||||
|
|
||||||
|
return ((CharacterArmor*)human->ChangedEquipData + slotId * 3)->ToWeapon(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We need to get the temporary set, variant and stain that is currently being set if it is available.
|
||||||
|
/// Weapons do not change in skeleton id without being reconstructed, so this is not changeable data.
|
||||||
|
/// </summary>
|
||||||
|
private CharacterWeapon GetTempSlot(Weapon* weapon)
|
||||||
|
{
|
||||||
|
var changedData = *(void**)((byte*)weapon + 0x918);
|
||||||
|
if (changedData == null)
|
||||||
|
return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, (StainId)weapon->ModelUnknown);
|
||||||
|
|
||||||
|
return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], ((StainId*)changedData)[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
Glamourer/Interop/Material/MaterialService.cs
Normal file
99
Glamourer/Interop/Material/MaterialService.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Lumina.Data.Files;
|
||||||
|
using static Penumbra.GameData.Files.MtrlFile;
|
||||||
|
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
public static unsafe class MaterialService
|
||||||
|
{
|
||||||
|
public const int TextureWidth = 4;
|
||||||
|
public const int TextureHeight = ColorTable.NumRows;
|
||||||
|
public const int MaterialsPerModel = 4;
|
||||||
|
|
||||||
|
/// <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 static bool ReplaceColorTable(Texture** original, in ColorTable colorTable)
|
||||||
|
{
|
||||||
|
if (original == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var textureSize = stackalloc int[2];
|
||||||
|
textureSize[0] = TextureWidth;
|
||||||
|
textureSize[1] = TextureHeight;
|
||||||
|
|
||||||
|
using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F,
|
||||||
|
(uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false);
|
||||||
|
if (texture.IsInvalid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fixed (ColorTable* ptr = &colorTable)
|
||||||
|
{
|
||||||
|
if (!texture.Texture->InitializeContents(ptr))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.Exchange(ref *(nint*)original);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture)
|
||||||
|
{
|
||||||
|
var textureSize = stackalloc int[2];
|
||||||
|
textureSize[0] = TextureWidth;
|
||||||
|
textureSize[1] = TextureHeight;
|
||||||
|
|
||||||
|
texture = Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F,
|
||||||
|
(uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7);
|
||||||
|
if (texture == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fixed (ColorTable* ptr = &colorTable)
|
||||||
|
{
|
||||||
|
return texture->InitializeContents(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Obtain a pointer to the models pointer to a specific color table texture. </summary>
|
||||||
|
/// <param name="model"></param>
|
||||||
|
/// <param name="modelSlot"></param>
|
||||||
|
/// <param name="materialSlot"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Texture** GetColorTableTexture(Model model, int modelSlot, byte materialSlot)
|
||||||
|
{
|
||||||
|
if (!model.IsCharacterBase)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var index = modelSlot * MaterialsPerModel + materialSlot;
|
||||||
|
if (index < 0 || index >= model.AsCharacterBase->ColorTableTexturesSpan.Length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var texture = (Texture**)Unsafe.AsPointer(ref model.AsCharacterBase->ColorTableTexturesSpan[index]);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Obtain a pointer to the color table of a certain material from a model. </summary>
|
||||||
|
/// <param name="model"> The draw object. </param>
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
if (!model.IsCharacterBase)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var index = modelSlot * MaterialsPerModel + materialSlot;
|
||||||
|
if (index < 0 || index >= model.AsCharacterBase->MaterialsSpan.Length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var material = (MaterialResourceHandle*)model.AsCharacterBase->MaterialsSpan[index].Value;
|
||||||
|
if (material == null || material->ColorTable == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (ColorTable*)material->ColorTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Glamourer/Interop/Material/MaterialValueIndex.cs
Normal file
162
Glamourer/Interop/Material/MaterialValueIndex.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Converter))]
|
||||||
|
public readonly record struct MaterialValueIndex(
|
||||||
|
MaterialValueIndex.DrawObjectType DrawObject,
|
||||||
|
byte SlotIndex,
|
||||||
|
byte MaterialIndex,
|
||||||
|
byte RowIndex)
|
||||||
|
{
|
||||||
|
public uint Key
|
||||||
|
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex);
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex);
|
||||||
|
|
||||||
|
public static bool FromKey(uint key, out MaterialValueIndex index)
|
||||||
|
{
|
||||||
|
index = new MaterialValueIndex(key);
|
||||||
|
return index.Valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryGetModel(Actor actor, out Model model)
|
||||||
|
{
|
||||||
|
if (!actor.Valid)
|
||||||
|
{
|
||||||
|
model = Model.Null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
model = DrawObject switch
|
||||||
|
{
|
||||||
|
DrawObjectType.Human => actor.Model,
|
||||||
|
DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject : Model.Null,
|
||||||
|
DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject : Model.Null,
|
||||||
|
_ => Model.Null,
|
||||||
|
};
|
||||||
|
return model.IsCharacterBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan<Pointer<Texture>> textures)
|
||||||
|
{
|
||||||
|
if (!TryGetModel(actor, out var model)
|
||||||
|
|| SlotIndex >= model.AsCharacterBase->SlotCount
|
||||||
|
|| model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel)
|
||||||
|
{
|
||||||
|
textures = [];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel,
|
||||||
|
MaterialService.MaterialsPerModel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryGetTexture(Actor actor, out Texture** texture)
|
||||||
|
{
|
||||||
|
if (TryGetTextures(actor, out var textures))
|
||||||
|
return TryGetTexture(textures, out texture);
|
||||||
|
|
||||||
|
texture = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryGetTexture(ReadOnlySpan<Pointer<Texture>> textures, out Texture** texture)
|
||||||
|
{
|
||||||
|
if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null)
|
||||||
|
{
|
||||||
|
texture = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (Pointer<Texture>* ptr = textures)
|
||||||
|
{
|
||||||
|
texture = (Texture**)ptr + MaterialIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryGetColorTable(Actor actor, out MtrlFile.ColorTable table)
|
||||||
|
{
|
||||||
|
if (TryGetTexture(actor, out var texture))
|
||||||
|
return TryGetColorTable(texture, out table);
|
||||||
|
|
||||||
|
table = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table)
|
||||||
|
=> DirectXTextureHelper.TryGetColorTable(*texture, out table);
|
||||||
|
|
||||||
|
public bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row)
|
||||||
|
{
|
||||||
|
if (!TryGetColorTable(actor, out var table))
|
||||||
|
{
|
||||||
|
row = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
row = table[RowIndex];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static MaterialValueIndex FromKey(uint key)
|
||||||
|
=> new(key);
|
||||||
|
|
||||||
|
public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0)
|
||||||
|
=> new(drawObject, slotIndex, materialIndex, rowIndex);
|
||||||
|
|
||||||
|
public static MaterialValueIndex Max(DrawObjectType drawObject = (DrawObjectType)byte.MaxValue, byte slotIndex = byte.MaxValue,
|
||||||
|
byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue)
|
||||||
|
=> new(drawObject, slotIndex, materialIndex, rowIndex);
|
||||||
|
|
||||||
|
public enum DrawObjectType : byte
|
||||||
|
{
|
||||||
|
Human,
|
||||||
|
Mainhand,
|
||||||
|
Offhand,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool Validate(DrawObjectType type)
|
||||||
|
=> Enum.IsDefined(type);
|
||||||
|
|
||||||
|
public static bool ValidateSlot(byte slotIndex)
|
||||||
|
=> slotIndex < 10;
|
||||||
|
|
||||||
|
public static bool ValidateMaterial(byte materialIndex)
|
||||||
|
=> materialIndex < MaterialService.MaterialsPerModel;
|
||||||
|
|
||||||
|
public static bool ValidateRow(byte rowIndex)
|
||||||
|
=> rowIndex < MtrlFile.ColorTable.NumRows;
|
||||||
|
|
||||||
|
private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex)
|
||||||
|
{
|
||||||
|
var result = (uint)rowIndex;
|
||||||
|
result |= (uint)materialIndex << 8;
|
||||||
|
result |= (uint)slotIndex << 16;
|
||||||
|
result |= (uint)((byte)type << 24);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MaterialValueIndex(uint key)
|
||||||
|
: this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private class Converter : JsonConverter<MaterialValueIndex>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, MaterialValueIndex value, JsonSerializer serializer)
|
||||||
|
=> serializer.Serialize(writer, value.Key);
|
||||||
|
|
||||||
|
public override MaterialValueIndex ReadJson(JsonReader reader, Type objectType, MaterialValueIndex existingValue, bool hasExistingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
=> FromKey(serializer.Deserialize<uint>(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
431
Glamourer/Interop/Material/MaterialValueManager.cs
Normal file
431
Glamourer/Interop/Material/MaterialValueManager.cs
Normal file
|
|
@ -0,0 +1,431 @@
|
||||||
|
global using StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager<Glamourer.Interop.Material.MaterialValueState>;
|
||||||
|
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.Structs;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Converter))]
|
||||||
|
public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength)
|
||||||
|
{
|
||||||
|
public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0);
|
||||||
|
|
||||||
|
public Vector3 Diffuse = diffuse;
|
||||||
|
public Vector3 Specular = specular;
|
||||||
|
public Vector3 Emissive = emissive;
|
||||||
|
public float SpecularStrength = specularStrength;
|
||||||
|
public float GlossStrength = glossStrength;
|
||||||
|
|
||||||
|
public ColorRow(in MtrlFile.ColorTable.Row row)
|
||||||
|
: this(row.Diffuse, row.Specular, row.Emissive, row.SpecularStrength, row.GlossStrength)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public readonly bool NearEqual(in ColorRow rhs)
|
||||||
|
=> Diffuse.NearEqual(rhs.Diffuse)
|
||||||
|
&& Specular.NearEqual(rhs.Specular)
|
||||||
|
&& Emissive.NearEqual(rhs.Emissive)
|
||||||
|
&& SpecularStrength.NearEqual(rhs.SpecularStrength)
|
||||||
|
&& GlossStrength.NearEqual(rhs.GlossStrength);
|
||||||
|
|
||||||
|
public readonly bool Apply(ref MtrlFile.ColorTable.Row row)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
if (!row.Diffuse.NearEqual(Diffuse))
|
||||||
|
{
|
||||||
|
row.Diffuse = Diffuse;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row.Specular.NearEqual(Specular))
|
||||||
|
{
|
||||||
|
row.Specular = Specular;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row.Emissive.NearEqual(Emissive))
|
||||||
|
{
|
||||||
|
row.Emissive = Emissive;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row.SpecularStrength.NearEqual(SpecularStrength))
|
||||||
|
{
|
||||||
|
row.SpecularStrength = SpecularStrength;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row.GlossStrength.NearEqual(GlossStrength))
|
||||||
|
{
|
||||||
|
row.GlossStrength = GlossStrength;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Converter : JsonConverter<ColorRow>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, ColorRow value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WritePropertyName("DiffuseR");
|
||||||
|
writer.WriteValue(value.Diffuse.X);
|
||||||
|
writer.WritePropertyName("DiffuseG");
|
||||||
|
writer.WriteValue(value.Diffuse.Y);
|
||||||
|
writer.WritePropertyName("DiffuseB");
|
||||||
|
writer.WriteValue(value.Diffuse.Z);
|
||||||
|
writer.WritePropertyName("SpecularR");
|
||||||
|
writer.WriteValue(value.Specular.X);
|
||||||
|
writer.WritePropertyName("SpecularG");
|
||||||
|
writer.WriteValue(value.Specular.Y);
|
||||||
|
writer.WritePropertyName("SpecularB");
|
||||||
|
writer.WriteValue(value.Specular.Z);
|
||||||
|
writer.WritePropertyName("SpecularA");
|
||||||
|
writer.WriteValue(value.SpecularStrength);
|
||||||
|
writer.WritePropertyName("EmissiveR");
|
||||||
|
writer.WriteValue(value.Emissive.X);
|
||||||
|
writer.WritePropertyName("EmissiveG");
|
||||||
|
writer.WriteValue(value.Emissive.Y);
|
||||||
|
writer.WritePropertyName("EmissiveB");
|
||||||
|
writer.WriteValue(value.Emissive.Z);
|
||||||
|
writer.WritePropertyName("Gloss");
|
||||||
|
writer.WriteValue(value.GlossStrength);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ColorRow ReadJson(JsonReader reader, Type objectType, ColorRow existingValue, bool hasExistingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var obj = JObject.Load(reader);
|
||||||
|
Set(ref existingValue.Diffuse.X, obj["DiffuseR"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Diffuse.Y, obj["DiffuseG"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Diffuse.Z, obj["DiffuseB"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Specular.X, obj["SpecularR"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Specular.Y, obj["SpecularG"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Specular.Z, obj["SpecularB"]?.Value<float>());
|
||||||
|
Set(ref existingValue.SpecularStrength, obj["SpecularA"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Emissive.X, obj["EmissiveR"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Emissive.Y, obj["EmissiveG"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Emissive.Z, obj["EmissiveB"]?.Value<float>());
|
||||||
|
Set(ref existingValue.GlossStrength, obj["Gloss"]?.Value<float>());
|
||||||
|
return existingValue;
|
||||||
|
|
||||||
|
static void Set<T>(ref T target, T? value)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
if (value.HasValue)
|
||||||
|
target = value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Converter))]
|
||||||
|
public struct MaterialValueDesign(ColorRow value, bool enabled)
|
||||||
|
{
|
||||||
|
public ColorRow Value = value;
|
||||||
|
public bool Enabled = enabled;
|
||||||
|
|
||||||
|
public readonly bool Apply(ref MaterialValueState state)
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (state.Model.NearEqual(Value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.Model = Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Converter : JsonConverter<MaterialValueDesign>
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, MaterialValueDesign value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WritePropertyName("DiffuseR");
|
||||||
|
writer.WriteValue(value.Value.Diffuse.X);
|
||||||
|
writer.WritePropertyName("DiffuseG");
|
||||||
|
writer.WriteValue(value.Value.Diffuse.Y);
|
||||||
|
writer.WritePropertyName("DiffuseB");
|
||||||
|
writer.WriteValue(value.Value.Diffuse.Z);
|
||||||
|
writer.WritePropertyName("SpecularR");
|
||||||
|
writer.WriteValue(value.Value.Specular.X);
|
||||||
|
writer.WritePropertyName("SpecularG");
|
||||||
|
writer.WriteValue(value.Value.Specular.Y);
|
||||||
|
writer.WritePropertyName("SpecularB");
|
||||||
|
writer.WriteValue(value.Value.Specular.Z);
|
||||||
|
writer.WritePropertyName("SpecularA");
|
||||||
|
writer.WriteValue(value.Value.SpecularStrength);
|
||||||
|
writer.WritePropertyName("EmissiveR");
|
||||||
|
writer.WriteValue(value.Value.Emissive.X);
|
||||||
|
writer.WritePropertyName("EmissiveG");
|
||||||
|
writer.WriteValue(value.Value.Emissive.Y);
|
||||||
|
writer.WritePropertyName("EmissiveB");
|
||||||
|
writer.WriteValue(value.Value.Emissive.Z);
|
||||||
|
writer.WritePropertyName("Gloss");
|
||||||
|
writer.WriteValue(value.Value.GlossStrength);
|
||||||
|
writer.WritePropertyName("Enabled");
|
||||||
|
writer.WriteValue(value.Enabled);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MaterialValueDesign ReadJson(JsonReader reader, Type objectType, MaterialValueDesign existingValue,
|
||||||
|
bool hasExistingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var obj = JObject.Load(reader);
|
||||||
|
Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Specular.X, obj["SpecularR"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Specular.Y, obj["SpecularG"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Specular.Z, obj["SpecularB"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.SpecularStrength, obj["SpecularA"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Emissive.X, obj["EmissiveR"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Emissive.Y, obj["EmissiveG"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.Emissive.Z, obj["EmissiveB"]?.Value<float>());
|
||||||
|
Set(ref existingValue.Value.GlossStrength, obj["Gloss"]?.Value<float>());
|
||||||
|
existingValue.Enabled = obj["Enabled"]?.Value<bool>() ?? false;
|
||||||
|
return existingValue;
|
||||||
|
|
||||||
|
static void Set<T>(ref T target, T? value)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
if (value.HasValue)
|
||||||
|
target = value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct MaterialValueState(
|
||||||
|
in ColorRow game,
|
||||||
|
in ColorRow model,
|
||||||
|
CharacterWeapon drawData,
|
||||||
|
StateSource source)
|
||||||
|
{
|
||||||
|
public MaterialValueState(in ColorRow gameRow, in ColorRow modelRow, CharacterArmor armor, StateSource source)
|
||||||
|
: this(gameRow, modelRow, armor.ToWeapon(0), source)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public ColorRow Game = game;
|
||||||
|
|
||||||
|
[FieldOffset(44)]
|
||||||
|
public ColorRow Model = model;
|
||||||
|
|
||||||
|
[FieldOffset(88)]
|
||||||
|
public readonly CharacterWeapon DrawData = drawData;
|
||||||
|
|
||||||
|
[FieldOffset(95)]
|
||||||
|
public readonly StateSource Source = source;
|
||||||
|
|
||||||
|
public readonly bool EqualGame(in ColorRow rhsRow, CharacterWeapon rhsData)
|
||||||
|
=> DrawData.Skeleton == rhsData.Skeleton
|
||||||
|
&& DrawData.Weapon == rhsData.Weapon
|
||||||
|
&& DrawData.Variant == rhsData.Variant
|
||||||
|
&& DrawData.Stain == rhsData.Stain
|
||||||
|
&& Game.NearEqual(rhsRow);
|
||||||
|
|
||||||
|
public readonly MaterialValueDesign Convert()
|
||||||
|
=> new(Model, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct MaterialValueManager<T>
|
||||||
|
{
|
||||||
|
private readonly List<(uint Key, T Value)> _values = [];
|
||||||
|
|
||||||
|
public MaterialValueManager()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
=> _values.Clear();
|
||||||
|
|
||||||
|
public MaterialValueManager<T> Clone()
|
||||||
|
{
|
||||||
|
var ret = new MaterialValueManager<T>();
|
||||||
|
ret._values.AddRange(_values);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(MaterialValueIndex index, out T value)
|
||||||
|
{
|
||||||
|
if (_values.Count == 0)
|
||||||
|
{
|
||||||
|
value = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = Search(index.Key);
|
||||||
|
if (idx >= 0)
|
||||||
|
{
|
||||||
|
value = _values[idx].Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAddValue(MaterialValueIndex index, in T value)
|
||||||
|
{
|
||||||
|
var key = index.Key;
|
||||||
|
var idx = Search(key);
|
||||||
|
if (idx >= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_values.Insert(~idx, (key, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveValue(MaterialValueIndex index)
|
||||||
|
{
|
||||||
|
if (_values.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var idx = Search(index.Key);
|
||||||
|
if (idx < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_values.RemoveAt(idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateValue(MaterialValueIndex index, in T value)
|
||||||
|
{
|
||||||
|
var key = index.Key;
|
||||||
|
var idx = Search(key);
|
||||||
|
if (idx < 0)
|
||||||
|
_values.Insert(~idx, (key, value));
|
||||||
|
else
|
||||||
|
_values[idx] = (key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UpdateValue(MaterialValueIndex index, in T value, out T oldValue)
|
||||||
|
{
|
||||||
|
if (_values.Count == 0)
|
||||||
|
{
|
||||||
|
oldValue = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = index.Key;
|
||||||
|
var idx = Search(key);
|
||||||
|
if (idx < 0)
|
||||||
|
{
|
||||||
|
oldValue = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldValue = _values[idx].Value;
|
||||||
|
_values[idx] = (key, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<(uint Key, T Value)> Values
|
||||||
|
=> _values;
|
||||||
|
|
||||||
|
public int RemoveValues(MaterialValueIndex min, MaterialValueIndex max)
|
||||||
|
{
|
||||||
|
var (minIdx, maxIdx) = MaterialValueManager.GetMinMax<T>(CollectionsMarshal.AsSpan(_values), min.Key, max.Key);
|
||||||
|
if (minIdx < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var count = maxIdx - minIdx;
|
||||||
|
_values.RemoveRange(minIdx, count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<(uint Key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max)
|
||||||
|
=> MaterialValueManager.Filter<T>(CollectionsMarshal.AsSpan(_values), min, max);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private int Search(uint key)
|
||||||
|
=> _values.BinarySearch((key, default!), MaterialValueManager.Comparer<T>.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MaterialValueManager
|
||||||
|
{
|
||||||
|
internal class Comparer<T> : IComparer<(uint Key, T Value)>
|
||||||
|
{
|
||||||
|
public static readonly Comparer<T> Instance = new();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
int IComparer<(uint Key, T Value)>.Compare((uint Key, T Value) x, (uint Key, T Value) y)
|
||||||
|
=> x.Key.CompareTo(y.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetSpecific<T>(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex index, out T ret)
|
||||||
|
{
|
||||||
|
var idx = values.BinarySearch((index.Key, default!), Comparer<T>.Instance);
|
||||||
|
if (idx < 0)
|
||||||
|
{
|
||||||
|
ret = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = values[idx].Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReadOnlySpan<(uint Key, T Value)> Filter<T>(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex min,
|
||||||
|
MaterialValueIndex max)
|
||||||
|
{
|
||||||
|
var (minIdx, maxIdx) = GetMinMax(values, min.Key, max.Key);
|
||||||
|
return minIdx < 0 ? [] : values[minIdx..(maxIdx + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Obtain the minimum index and maximum index for a minimum and maximum key. </summary>
|
||||||
|
internal static (int MinIdx, int MaxIdx) GetMinMax<T>(ReadOnlySpan<(uint Key, T Value)> values, uint minKey, uint maxKey)
|
||||||
|
{
|
||||||
|
// Find the minimum index by binary search.
|
||||||
|
var idx = values.BinarySearch((minKey, default!), Comparer<T>.Instance);
|
||||||
|
var minIdx = idx;
|
||||||
|
|
||||||
|
// If the key does not exist, check if it is an invalid range or set it correctly.
|
||||||
|
if (minIdx < 0)
|
||||||
|
{
|
||||||
|
minIdx = ~minIdx;
|
||||||
|
if (minIdx == values.Length || values[minIdx].Key > maxKey)
|
||||||
|
return (-1, -1);
|
||||||
|
|
||||||
|
idx = minIdx;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If it does exist, go upwards until the first key is reached that is actually smaller.
|
||||||
|
while (minIdx > 0 && values[minIdx - 1].Key >= minKey)
|
||||||
|
--minIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the range can be valid.
|
||||||
|
if (values[minIdx].Key < minKey || values[minIdx].Key > maxKey)
|
||||||
|
return (-1, -1);
|
||||||
|
|
||||||
|
|
||||||
|
// Do pretty much the same but in the other direction with the maximum key.
|
||||||
|
var maxIdx = values[idx..].BinarySearch((maxKey, default!), Comparer<T>.Instance);
|
||||||
|
if (maxIdx < 0)
|
||||||
|
{
|
||||||
|
maxIdx = ~maxIdx + idx;
|
||||||
|
return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIdx += idx;
|
||||||
|
|
||||||
|
while (maxIdx < values.Length - 1 && values[maxIdx + 1].Key <= maxKey)
|
||||||
|
++maxIdx;
|
||||||
|
|
||||||
|
if (values[maxIdx].Key < minKey || values[maxIdx].Key > maxKey)
|
||||||
|
return (-1, -1);
|
||||||
|
|
||||||
|
return (minIdx, maxIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Glamourer/Interop/Material/PrepareColorSet.cs
Normal file
103
Glamourer/Interop/Material/PrepareColorSet.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
public sealed unsafe class PrepareColorSet
|
||||||
|
: EventWrapperPtr12Ref34<CharacterBase, MaterialResourceHandle, StainId, nint, PrepareColorSet.Priority>, IHookService
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="MaterialManager.OnPrepareColorSet"/>
|
||||||
|
MaterialManager = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrepareColorSet(HookManager hooks)
|
||||||
|
: base("Prepare Color Set ")
|
||||||
|
=> _task = hooks.CreateHook<Delegate>(Name, "40 55 56 41 56 48 83 EC ?? 80 BA", Detour, true);
|
||||||
|
|
||||||
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
public nint Address
|
||||||
|
=> (nint)CharacterBase.MemberFunctionPointers.Destroy;
|
||||||
|
|
||||||
|
public void Enable()
|
||||||
|
=> _task.Result.Enable();
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
=> _task.Result.Disable();
|
||||||
|
|
||||||
|
public Task Awaiter
|
||||||
|
=> _task;
|
||||||
|
|
||||||
|
public bool Finished
|
||||||
|
=> _task.IsCompletedSuccessfully;
|
||||||
|
|
||||||
|
private delegate Texture* Delegate(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId);
|
||||||
|
|
||||||
|
private Texture* Detour(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stainId.Id}.");
|
||||||
|
var ret = nint.Zero;
|
||||||
|
Invoke(characterBase, material, ref stainId, ref ret);
|
||||||
|
if (ret != nint.Zero)
|
||||||
|
return (Texture*)ret;
|
||||||
|
|
||||||
|
return _task.Result.Original(characterBase, material, stainId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId,
|
||||||
|
out MtrlFile.ColorTable table)
|
||||||
|
{
|
||||||
|
if (material->ColorTable == null)
|
||||||
|
{
|
||||||
|
table = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newTable = *(MtrlFile.ColorTable*)material->ColorTable;
|
||||||
|
if (stainId.Id != 0)
|
||||||
|
characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable));
|
||||||
|
table = newTable;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Assumes the actor is valid. </summary>
|
||||||
|
public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out MtrlFile.ColorTable table)
|
||||||
|
{
|
||||||
|
var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex;
|
||||||
|
var model = actor.Model.AsCharacterBase;
|
||||||
|
var handle = (MaterialResourceHandle*)model->Materials[idx];
|
||||||
|
if (handle == null)
|
||||||
|
{
|
||||||
|
table = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryGetColorTable(model, handle, GetStain(), out table);
|
||||||
|
|
||||||
|
StainId GetStain()
|
||||||
|
{
|
||||||
|
switch (index.DrawObject)
|
||||||
|
{
|
||||||
|
case MaterialValueIndex.DrawObjectType.Human:
|
||||||
|
return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stain : 0;
|
||||||
|
case MaterialValueIndex.DrawObjectType.Mainhand:
|
||||||
|
var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
|
||||||
|
return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0;
|
||||||
|
case MaterialValueIndex.DrawObjectType.Offhand:
|
||||||
|
var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject;
|
||||||
|
return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Glamourer/Interop/Material/SafeTextureHandle.cs
Normal file
49
Glamourer/Interop/Material/SafeTextureHandle.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Material;
|
||||||
|
|
||||||
|
public unsafe class SafeTextureHandle : SafeHandle
|
||||||
|
{
|
||||||
|
public Texture* Texture
|
||||||
|
=> (Texture*)handle;
|
||||||
|
|
||||||
|
public override bool IsInvalid
|
||||||
|
=> handle == 0;
|
||||||
|
|
||||||
|
public SafeTextureHandle(Texture* handle, bool incRef, bool ownsHandle = true)
|
||||||
|
: base(0, ownsHandle)
|
||||||
|
{
|
||||||
|
if (incRef && !ownsHandle)
|
||||||
|
throw new ArgumentException("Non-owning SafeTextureHandle with IncRef is unsupported");
|
||||||
|
|
||||||
|
if (incRef && handle != null)
|
||||||
|
handle->IncRef();
|
||||||
|
SetHandle((nint)handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Exchange(ref nint ppTexture)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
handle = Interlocked.Exchange(ref ppTexture, handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SafeTextureHandle CreateInvalid()
|
||||||
|
=> new(null, false);
|
||||||
|
|
||||||
|
protected override bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
nint handle;
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
handle = this.handle;
|
||||||
|
this.handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle != 0)
|
||||||
|
((Texture*)handle)->DecRef();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Glamourer/Interop/Penumbra/ModSettingApplier.cs
Normal file
67
Glamourer/Interop/Penumbra/ModSettingApplier.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
using Glamourer.Designs.Links;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using OtterGui.Services;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop.Penumbra;
|
||||||
|
|
||||||
|
public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects) : IService
|
||||||
|
{
|
||||||
|
public void HandleStateApplication(ActorState state, MergedDesign design)
|
||||||
|
{
|
||||||
|
if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
objects.Update();
|
||||||
|
if (!objects.TryGetValue(state.Identifier, out var data))
|
||||||
|
{
|
||||||
|
Glamourer.Log.Verbose(
|
||||||
|
$"[Mod Applier] No mod settings applied because no actor for {state.Identifier} could be found to associate collection.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var collections = new HashSet<string>();
|
||||||
|
|
||||||
|
foreach (var actor in data.Objects)
|
||||||
|
{
|
||||||
|
var collection = penumbra.GetActorCollection(actor);
|
||||||
|
if (collection.Length == 0)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collections.Add(collection))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var (mod, setting) in design.AssociatedMods)
|
||||||
|
{
|
||||||
|
var message = penumbra.SetMod(mod, setting, collection);
|
||||||
|
if (message.Length > 0)
|
||||||
|
Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}");
|
||||||
|
else
|
||||||
|
Glamourer.Log.Verbose($"[Mod Applier] Set mod settings for {mod.DirectoryName} in {collection}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (List<string> Messages, int Applied, string Collection) ApplyModSettings(IReadOnlyDictionary<Mod, ModSettings> settings, Actor actor)
|
||||||
|
{
|
||||||
|
var collection = penumbra.GetActorCollection(actor);
|
||||||
|
if (collection.Length <= 0)
|
||||||
|
return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty);
|
||||||
|
|
||||||
|
var messages = new List<string>();
|
||||||
|
var appliedMods = 0;
|
||||||
|
foreach (var (mod, setting) in settings)
|
||||||
|
{
|
||||||
|
var message = penumbra.SetMod(mod, setting, collection);
|
||||||
|
if (message.Length > 0)
|
||||||
|
messages.Add($"Error applying mod settings: {message}");
|
||||||
|
else
|
||||||
|
++appliedMods;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (messages, appliedMods, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,10 +29,11 @@ public class CodeService
|
||||||
World = 0x010000,
|
World = 0x010000,
|
||||||
Elephants = 0x020000,
|
Elephants = 0x020000,
|
||||||
Crown = 0x040000,
|
Crown = 0x040000,
|
||||||
|
Dolphins = 0x080000,
|
||||||
}
|
}
|
||||||
|
|
||||||
public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants;
|
public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins;
|
||||||
public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants;
|
public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins;
|
||||||
|
|
||||||
public const CodeFlag RaceCodes = CodeFlag.OopsHyur
|
public const CodeFlag RaceCodes = CodeFlag.OopsHyur
|
||||||
| CodeFlag.OopsElezen
|
| CodeFlag.OopsElezen
|
||||||
|
|
@ -114,7 +115,9 @@ public class CodeService
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var badFlags = ~GetMutuallyExclusive(flag);
|
var badFlags = ~GetMutuallyExclusive(flag);
|
||||||
return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag;;
|
return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag;
|
||||||
|
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeFlag GetCode(string name)
|
public CodeFlag GetCode(string name)
|
||||||
|
|
@ -173,6 +176,7 @@ public class CodeService
|
||||||
CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World,
|
CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World,
|
||||||
CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants,
|
CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants,
|
||||||
CodeFlag.Crown => 0,
|
CodeFlag.Crown => 0,
|
||||||
|
CodeFlag.Dolphins => (DyeCodes | GearCodes) & ~CodeFlag.Dolphins,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -198,6 +202,7 @@ public class CodeService
|
||||||
CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ],
|
CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ],
|
||||||
CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ],
|
CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ],
|
||||||
CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ],
|
CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ],
|
||||||
|
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 ],
|
||||||
_ => [],
|
_ => [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Events;
|
|
||||||
using Glamourer.GameData;
|
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Interop.Penumbra;
|
using Glamourer.Interop.Penumbra;
|
||||||
|
|
@ -35,11 +33,11 @@ public class CommandService : IDisposable
|
||||||
private readonly DesignConverter _converter;
|
private readonly DesignConverter _converter;
|
||||||
private readonly DesignFileSystem _designFileSystem;
|
private readonly DesignFileSystem _designFileSystem;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly PenumbraService _penumbra;
|
private readonly ModSettingApplier _modApplier;
|
||||||
|
|
||||||
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
|
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
|
||||||
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
|
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
|
||||||
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, PenumbraService penumbra)
|
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier)
|
||||||
{
|
{
|
||||||
_commands = commands;
|
_commands = commands;
|
||||||
_mainWindow = mainWindow;
|
_mainWindow = mainWindow;
|
||||||
|
|
@ -53,7 +51,7 @@ public class CommandService : IDisposable
|
||||||
_designFileSystem = designFileSystem;
|
_designFileSystem = designFileSystem;
|
||||||
_autoDesignManager = autoDesignManager;
|
_autoDesignManager = autoDesignManager;
|
||||||
_config = config;
|
_config = config;
|
||||||
_penumbra = penumbra;
|
_modApplier = modApplier;
|
||||||
|
|
||||||
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
||||||
_commands.AddHandler(ApplyCommandString,
|
_commands.AddHandler(ApplyCommandString,
|
||||||
|
|
@ -442,19 +440,10 @@ public class CommandService : IDisposable
|
||||||
if (!applyMods || design is not Design d)
|
if (!applyMods || design is not Design d)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var collection = _penumbra.GetActorCollection(actor);
|
var (messages, appliedMods, collection) = _modApplier.ApplyModSettings(d.AssociatedMods, actor);
|
||||||
if (collection.Length <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var appliedMods = 0;
|
foreach (var message in messages)
|
||||||
foreach (var (mod, setting) in d.AssociatedMods)
|
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
|
||||||
{
|
|
||||||
var message = _penumbra.SetMod(mod, setting, collection);
|
|
||||||
if (message.Length > 0)
|
|
||||||
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
|
|
||||||
else
|
|
||||||
++appliedMods;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appliedMods > 0)
|
if (appliedMods > 0)
|
||||||
Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}.");
|
Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}.");
|
||||||
|
|
@ -509,7 +498,7 @@ public class CommandService : IDisposable
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var text = _converter.ShareBase64(state);
|
var text = _converter.ShareBase64(state, ApplicationRules.AllButParameters(state));
|
||||||
ImGui.SetClipboardText(text);
|
ImGui.SetClipboardText(text);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -548,8 +537,7 @@ public class CommandService : IDisposable
|
||||||
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
|
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All,
|
var design = _converter.Convert(state, ApplicationRules.FromModifiers(state));
|
||||||
CustomizeParameterExtensions.All);
|
|
||||||
_designManager.CreateClone(design, split[0], true);
|
_designManager.CreateClone(design, split[0], true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using Glamourer.Unlocks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using OtterGui.Raii;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
|
|
@ -47,7 +48,7 @@ public static class ServiceManagerA
|
||||||
DalamudServices.AddServices(services, pi);
|
DalamudServices.AddServices(services, pi);
|
||||||
services.AddIServices(typeof(EquipItem).Assembly);
|
services.AddIServices(typeof(EquipItem).Assembly);
|
||||||
services.AddIServices(typeof(Glamourer).Assembly);
|
services.AddIServices(typeof(Glamourer).Assembly);
|
||||||
services.AddIServices(typeof(EquipFlag).Assembly);
|
services.AddIServices(typeof(ImRaii).Assembly);
|
||||||
services.CreateProvider();
|
services.CreateProvider();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +146,8 @@ public static class ServiceManagerA
|
||||||
.AddSingleton<MultiDesignPanel>()
|
.AddSingleton<MultiDesignPanel>()
|
||||||
.AddSingleton<DesignPanel>()
|
.AddSingleton<DesignPanel>()
|
||||||
.AddSingleton<DesignTab>()
|
.AddSingleton<DesignTab>()
|
||||||
.AddSingleton<DesignCombo>()
|
.AddSingleton<QuickDesignCombo>()
|
||||||
|
.AddSingleton<LinkDesignCombo>()
|
||||||
.AddSingleton<RevertDesignCombo>()
|
.AddSingleton<RevertDesignCombo>()
|
||||||
.AddSingleton<ModAssociationsTab>()
|
.AddSingleton<ModAssociationsTab>()
|
||||||
.AddSingleton<DesignDetailTab>()
|
.AddSingleton<DesignDetailTab>()
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ public class ActorState
|
||||||
/// <summary> The territory the draw object was created last. </summary>
|
/// <summary> The territory the draw object was created last. </summary>
|
||||||
public ushort LastTerritory;
|
public ushort LastTerritory;
|
||||||
|
|
||||||
|
/// <summary> State for specific material values. </summary>
|
||||||
|
public readonly StateMaterialManager Materials = new();
|
||||||
|
|
||||||
/// <summary> Whether the State is locked at all. </summary>
|
/// <summary> Whether the State is locked at all. </summary>
|
||||||
public bool IsLocked
|
public bool IsLocked
|
||||||
=> Combination != 0;
|
=> Combination != 0;
|
||||||
|
|
@ -84,4 +87,4 @@ public class ActorState
|
||||||
LastTerritory = territory;
|
LastTerritory = territory;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,8 @@ internal class FunEquipSet
|
||||||
new Group(0000, 0, 0137, 2, 0000, 0, 0000, 0, 0000, 0), // Wailing Spirit
|
new Group(0000, 0, 0137, 2, 0000, 0, 0000, 0, 0000, 0), // Wailing Spirit
|
||||||
new Group(0232, 1, 0232, 1, 0279, 1, 0232, 1, 0232, 1), // Eerie Attire
|
new Group(0232, 1, 0232, 1, 0279, 1, 0232, 1, 0232, 1), // Eerie Attire
|
||||||
new Group(0232, 1, 6036, 1, 0279, 1, 0232, 1, 0232, 1), // Vampire
|
new Group(0232, 1, 6036, 1, 0279, 1, 0232, 1, 0232, 1), // Vampire
|
||||||
new Group(0505, 6, 0505, 6, 0505, 6, 0505, 6, 0505, 6) // Manusya Casting
|
new Group(0505, 6, 0505, 6, 0505, 6, 0505, 6, 0505, 6), // Manusya Casting
|
||||||
|
new Group(6147, 1, 6147, 1, 6147, 1, 6147, 1, 6147, 1) // Tonberry
|
||||||
);
|
);
|
||||||
|
|
||||||
public static readonly FunEquipSet AprilFirst = new
|
public static readonly FunEquipSet AprilFirst = new
|
||||||
|
|
@ -94,7 +95,13 @@ internal class FunEquipSet
|
||||||
new Group(0159, 1, 0000, 0, 0000, 0, 0000, 0, 0000, 0), // Slime Crown
|
new Group(0159, 1, 0000, 0, 0000, 0, 0000, 0, 0000, 0), // Slime Crown
|
||||||
new Group(6117, 1, 6117, 1, 6117, 1, 6117, 1, 6117, 1), // Clown
|
new Group(6117, 1, 6117, 1, 6117, 1, 6117, 1, 6117, 1), // Clown
|
||||||
new Group(6169, 3, 6169, 3, 0279, 1, 6169, 3, 6169, 3), // Chocobo Pajama
|
new Group(6169, 3, 6169, 3, 0279, 1, 6169, 3, 6169, 3), // Chocobo Pajama
|
||||||
new Group(6169, 2, 6169, 2, 0279, 2, 6169, 2, 6169, 2) // Cactuar Pajama
|
new Group(6169, 2, 6169, 2, 0279, 2, 6169, 2, 6169, 2), // Cactuar Pajama
|
||||||
|
new Group(6023, 1, 6023, 1, 0000, 0, 0000, 0, 0000, 0), // Swine
|
||||||
|
new Group(5040, 1, 0000, 0, 0000, 0, 0000, 0, 0000, 0), // Namazu only
|
||||||
|
new Group(5040, 1, 6023, 1, 0000, 0, 0000, 0, 0000, 0), // Namazu lean
|
||||||
|
new Group(5040, 1, 6023, 1, 0000, 0, 0000, 0, 0000, 0), // Namazu chonk
|
||||||
|
new Group(6182, 1, 6182, 1, 0000, 0, 0000, 0, 0000, 0), // Imp
|
||||||
|
new Group(6147, 1, 6147, 1, 6147, 1, 6147, 1, 6147, 1) // Tonberry
|
||||||
);
|
);
|
||||||
|
|
||||||
private FunEquipSet(params Group[] groups)
|
private FunEquipSet(params Group[] groups)
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ public unsafe class FunModule : IDisposable
|
||||||
SetRandomItem(slot, ref armor);
|
SetRandomItem(slot, ref armor);
|
||||||
break;
|
break;
|
||||||
case CodeService.CodeFlag.Elephants:
|
case CodeService.CodeFlag.Elephants:
|
||||||
|
case CodeService.CodeFlag.Dolphins:
|
||||||
case CodeService.CodeFlag.World when actor.Index != 0:
|
case CodeService.CodeFlag.World when actor.Index != 0:
|
||||||
KeepOldArmor(actor, slot, ref armor);
|
KeepOldArmor(actor, slot, ref armor);
|
||||||
break;
|
break;
|
||||||
|
|
@ -168,6 +169,10 @@ public unsafe class FunModule : IDisposable
|
||||||
SetElephant(EquipSlot.Body, ref armor[1], stainId);
|
SetElephant(EquipSlot.Body, ref armor[1], stainId);
|
||||||
SetElephant(EquipSlot.Head, ref armor[0], stainId);
|
SetElephant(EquipSlot.Head, ref armor[0], stainId);
|
||||||
break;
|
break;
|
||||||
|
case CodeService.CodeFlag.Dolphins:
|
||||||
|
SetDolphin(EquipSlot.Body, ref armor[1]);
|
||||||
|
SetDolphin(EquipSlot.Head, ref armor[0]);
|
||||||
|
break;
|
||||||
case CodeService.CodeFlag.World when actor.Index != 0:
|
case CodeService.CodeFlag.World when actor.Index != 0:
|
||||||
_worldSets.Apply(actor, _rng, armor);
|
_worldSets.Apply(actor, _rng, armor);
|
||||||
break;
|
break;
|
||||||
|
|
@ -227,6 +232,32 @@ public unsafe class FunModule : IDisposable
|
||||||
7, // Rose Pink
|
7, // Rose Pink
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static IReadOnlyList<CharacterArmor> DolphinBodies
|
||||||
|
=>
|
||||||
|
[
|
||||||
|
new CharacterArmor(6089, 1, 4), // Toad
|
||||||
|
new CharacterArmor(6089, 1, 4), // Toad
|
||||||
|
new CharacterArmor(6089, 1, 4), // Toad
|
||||||
|
new CharacterArmor(6023, 1, 4), // Swine
|
||||||
|
new CharacterArmor(6023, 1, 4), // Swine
|
||||||
|
new CharacterArmor(6023, 1, 4), // Swine
|
||||||
|
new CharacterArmor(6133, 1, 4), // Gaja
|
||||||
|
new CharacterArmor(6182, 1, 3), // Imp
|
||||||
|
new CharacterArmor(6182, 1, 3), // Imp
|
||||||
|
new CharacterArmor(6182, 1, 4), // Imp
|
||||||
|
new CharacterArmor(6182, 1, 4), // Imp
|
||||||
|
];
|
||||||
|
|
||||||
|
private void SetDolphin(EquipSlot slot, ref CharacterArmor armor)
|
||||||
|
{
|
||||||
|
armor = slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)],
|
||||||
|
EquipSlot.Head => new CharacterArmor(5040, 1, 0),
|
||||||
|
_ => armor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void SetElephant(EquipSlot slot, ref CharacterArmor armor, StainId stainId)
|
private void SetElephant(EquipSlot slot, ref CharacterArmor armor, StainId stainId)
|
||||||
{
|
{
|
||||||
armor = slot switch
|
armor = slot switch
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -220,6 +221,39 @@ public class InternalStateEditor(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Change the value of a single material color table entry. </summary>
|
||||||
|
public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, out ColorRow? oldValue,
|
||||||
|
uint key = 0)
|
||||||
|
{
|
||||||
|
// We already have an existing value.
|
||||||
|
if (state.Materials.TryGetValue(index, out var old))
|
||||||
|
{
|
||||||
|
oldValue = old.Model;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Remove if overwritten by a game value.
|
||||||
|
if (source is StateSource.Game)
|
||||||
|
{
|
||||||
|
state.Materials.RemoveValue(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update if edited.
|
||||||
|
state.Materials.UpdateValue(index, newValue, out _);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not have an existing value.
|
||||||
|
oldValue = null;
|
||||||
|
// Do not do anything if locked or if the game value updates, because then we do not need to add an entry.
|
||||||
|
if (!state.CanUnlock(key) || source is StateSource.Game)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Only add an entry if it is different from the game value.
|
||||||
|
return state.Materials.TryAddValue(index, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
|
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
|
||||||
uint key = 0)
|
uint key = 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Interop.Penumbra;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
|
@ -118,8 +119,7 @@ public class StateApplier(
|
||||||
// If the source is not IPC we do not want to apply restrictions.
|
// If the source is not IPC we do not want to apply restrictions.
|
||||||
var data = GetData(state);
|
var data = GetData(state);
|
||||||
if (apply)
|
if (apply)
|
||||||
ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
|
ChangeArmor(data, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible());
|
||||||
state.ModelData.IsHatVisible());
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +267,7 @@ public class StateApplier(
|
||||||
actor.Model.ApplyParameterData(flags, values);
|
actor.Model.ApplyParameterData(flags, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ChangeParameters(ActorData,CustomizeParameterFlag,in CustomizeParameterData)"/>
|
/// <inheritdoc cref="ChangeParameters(ActorData,CustomizeParameterFlag,in CustomizeParameterData,bool)"/>
|
||||||
public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply)
|
public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply)
|
||||||
{
|
{
|
||||||
var data = GetData(state);
|
var data = GetData(state);
|
||||||
|
|
@ -276,6 +276,38 @@ public class StateApplier(
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force)
|
||||||
|
{
|
||||||
|
if (!force && !_config.UseAdvancedParameters)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true }))
|
||||||
|
{
|
||||||
|
if (!index.TryGetTexture(actor, out var texture))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!index.TryGetColorTable(texture, out var table))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (value.HasValue)
|
||||||
|
value.Value.Apply(ref table[index.RowIndex]);
|
||||||
|
else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable))
|
||||||
|
table[index.RowIndex] = baseTable[index.RowIndex];
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
MaterialService.ReplaceColorTable(texture, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorData ChangeMaterialValue(ActorState state, MaterialValueIndex index, bool apply)
|
||||||
|
{
|
||||||
|
var data = GetData(state);
|
||||||
|
if (apply)
|
||||||
|
ChangeMaterialValue(data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. </summary>
|
/// <summary> Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. </summary>
|
||||||
/// <param name="state"> The state to apply. </param>
|
/// <param name="state"> The state to apply. </param>
|
||||||
/// <param name="redraw"> Whether a redraw should be forced. </param>
|
/// <param name="redraw"> Whether a redraw should be forced. </param>
|
||||||
|
|
@ -294,10 +326,7 @@ public class StateApplier(
|
||||||
{
|
{
|
||||||
ChangeCustomize(actors, state.ModelData.Customize);
|
ChangeCustomize(actors, state.ModelData.Customize);
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible());
|
||||||
ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
|
|
||||||
state.ModelData.IsHatVisible());
|
|
||||||
}
|
|
||||||
|
|
||||||
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
|
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
|
||||||
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
|
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ using Glamourer.Designs;
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -16,7 +18,8 @@ public class StateEditor(
|
||||||
JobChangeState jobChange,
|
JobChangeState jobChange,
|
||||||
Configuration config,
|
Configuration config,
|
||||||
ItemManager items,
|
ItemManager items,
|
||||||
DesignMerger merger) : IDesignEditor
|
DesignMerger merger,
|
||||||
|
ModSettingApplier modApplier) : IDesignEditor
|
||||||
{
|
{
|
||||||
protected readonly InternalStateEditor Editor = editor;
|
protected readonly InternalStateEditor Editor = editor;
|
||||||
protected readonly StateApplier Applier = applier;
|
protected readonly StateApplier Applier = applier;
|
||||||
|
|
@ -31,7 +34,7 @@ public class StateEditor(
|
||||||
if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
|
if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ForceRedraw(state, source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
|
StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
|
||||||
|
|
@ -44,7 +47,7 @@ public class StateEditor(
|
||||||
if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key))
|
if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx));
|
StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx));
|
||||||
|
|
@ -57,7 +60,7 @@ public class StateEditor(
|
||||||
if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key))
|
if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied));
|
StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied));
|
||||||
|
|
@ -72,8 +75,8 @@ public class StateEditor(
|
||||||
|
|
||||||
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
||||||
var actors = type is StateChanged.Type.Equip
|
var actors = type is StateChanged.Type.Equip
|
||||||
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
|
? Applier.ChangeArmor(state, slot, settings.Source.RequiresChange())
|
||||||
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
|
: Applier.ChangeWeapon(state, slot, settings.Source.RequiresChange(),
|
||||||
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||||
|
|
||||||
if (slot is EquipSlot.MainHand)
|
if (slot is EquipSlot.MainHand)
|
||||||
|
|
@ -105,8 +108,8 @@ public class StateEditor(
|
||||||
|
|
||||||
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
||||||
var actors = type is StateChanged.Type.Equip
|
var actors = type is StateChanged.Type.Equip
|
||||||
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
|
? Applier.ChangeArmor(state, slot, settings.Source.RequiresChange())
|
||||||
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
|
: Applier.ChangeWeapon(state, slot, settings.Source.RequiresChange(),
|
||||||
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||||
|
|
||||||
if (slot is EquipSlot.MainHand)
|
if (slot is EquipSlot.MainHand)
|
||||||
|
|
@ -125,7 +128,7 @@ public class StateEditor(
|
||||||
if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key))
|
if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ChangeStain(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot));
|
StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot));
|
||||||
|
|
@ -138,7 +141,7 @@ public class StateEditor(
|
||||||
if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key))
|
if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ChangeCrests(state, settings.Source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ChangeCrests(state, settings.Source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot));
|
StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot));
|
||||||
|
|
@ -147,9 +150,7 @@ public class StateEditor(
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings)
|
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings)
|
||||||
{
|
{
|
||||||
if (data is not ActorState state)
|
var state = (ActorState)data;
|
||||||
return;
|
|
||||||
|
|
||||||
// Also apply main color to highlights when highlights is off.
|
// Also apply main color to highlights when highlights is off.
|
||||||
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
|
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
|
||||||
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings);
|
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings);
|
||||||
|
|
@ -158,12 +159,23 @@ public class StateEditor(
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var @new = state.ModelData.Parameters[flag];
|
var @new = state.ModelData.Parameters[flag];
|
||||||
var actors = Applier.ChangeParameters(state, flag, settings.Source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
|
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings)
|
||||||
|
{
|
||||||
|
var state = (ActorState)data;
|
||||||
|
if (!Editor.ChangeMaterialValue(state, index, newValue, settings.Source, out var oldValue, settings.Key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange());
|
||||||
|
Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
|
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index));
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
|
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
|
||||||
{
|
{
|
||||||
|
|
@ -171,7 +183,7 @@ public class StateEditor(
|
||||||
if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key))
|
if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = Applier.ChangeMetaState(state, index, settings.Source is StateSource.Manual or StateSource.Ipc);
|
var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange());
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState));
|
StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState));
|
||||||
|
|
@ -181,6 +193,7 @@ public class StateEditor(
|
||||||
public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings)
|
public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings)
|
||||||
{
|
{
|
||||||
var state = (ActorState)data;
|
var state = (ActorState)data;
|
||||||
|
modApplier.HandleStateApplication(state, mergedDesign);
|
||||||
if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize,
|
if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize,
|
||||||
mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key))
|
mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key))
|
||||||
return;
|
return;
|
||||||
|
|
@ -191,7 +204,7 @@ public class StateEditor(
|
||||||
{
|
{
|
||||||
foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest))
|
foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest))
|
||||||
{
|
{
|
||||||
if (!settings.RespectManual || state.Sources[slot] is not StateSource.Manual)
|
if (!settings.RespectManual || !state.Sources[slot].IsManual())
|
||||||
Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot),
|
Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot),
|
||||||
out _, settings.Key);
|
out _, settings.Key);
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +214,7 @@ public class StateEditor(
|
||||||
customizeFlags |= CustomizeFlag.Race;
|
customizeFlags |= CustomizeFlag.Race;
|
||||||
|
|
||||||
Func<CustomizeIndex, bool> applyWhich = settings.RespectManual
|
Func<CustomizeIndex, bool> applyWhich = settings.RespectManual
|
||||||
? i => customizeFlags.HasFlag(i.ToFlag()) && state.Sources[i] is not StateSource.Manual
|
? i => customizeFlags.HasFlag(i.ToFlag()) && !state.Sources[i].IsManual()
|
||||||
: i => customizeFlags.HasFlag(i.ToFlag());
|
: i => customizeFlags.HasFlag(i.ToFlag());
|
||||||
|
|
||||||
if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed,
|
if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed,
|
||||||
|
|
@ -210,12 +223,10 @@ public class StateEditor(
|
||||||
|
|
||||||
foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate())
|
foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate())
|
||||||
{
|
{
|
||||||
if (settings.RespectManual && state.Sources[parameter] is StateSource.Manual or StateSource.Pending)
|
if (settings.RespectManual && state.Sources[parameter].IsManual())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var source = Source(parameter);
|
var source = Source(parameter).SetPending();
|
||||||
if (source is StateSource.Manual)
|
|
||||||
source = StateSource.Pending;
|
|
||||||
Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key);
|
Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,12 +239,12 @@ public class StateEditor(
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
if (mergedDesign.Design.DoApplyEquip(slot))
|
if (mergedDesign.Design.DoApplyEquip(slot))
|
||||||
if (!settings.RespectManual || state.Sources[slot, false] is not StateSource.Manual)
|
if (!settings.RespectManual || !state.Sources[slot, false].IsManual())
|
||||||
Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot),
|
Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot),
|
||||||
Source(slot.ToState()), out _, settings.Key);
|
Source(slot.ToState()), out _, settings.Key);
|
||||||
|
|
||||||
if (mergedDesign.Design.DoApplyStain(slot))
|
if (mergedDesign.Design.DoApplyStain(slot))
|
||||||
if (!settings.RespectManual || state.Sources[slot, true] is not StateSource.Manual)
|
if (!settings.RespectManual || !state.Sources[slot, true].IsManual())
|
||||||
Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot),
|
Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot),
|
||||||
Source(slot.ToState(true)), out _, settings.Key);
|
Source(slot.ToState(true)), out _, settings.Key);
|
||||||
}
|
}
|
||||||
|
|
@ -241,14 +252,14 @@ public class StateEditor(
|
||||||
foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots)
|
foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots)
|
||||||
{
|
{
|
||||||
if (mergedDesign.Design.DoApplyStain(weaponSlot))
|
if (mergedDesign.Design.DoApplyStain(weaponSlot))
|
||||||
if (!settings.RespectManual || state.Sources[weaponSlot, true] is not StateSource.Manual)
|
if (!settings.RespectManual || !state.Sources[weaponSlot, true].IsManual())
|
||||||
Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
|
Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
|
||||||
Source(weaponSlot.ToState(true)), out _, settings.Key);
|
Source(weaponSlot.ToState(true)), out _, settings.Key);
|
||||||
|
|
||||||
if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
|
if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (settings.RespectManual && state.Sources[weaponSlot, false] is StateSource.Manual)
|
if (settings.RespectManual && !state.Sources[weaponSlot, false].IsManual())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var currentType = state.ModelData.Item(weaponSlot).Type;
|
var currentType = state.ModelData.Item(weaponSlot).Type;
|
||||||
|
|
@ -268,12 +279,26 @@ public class StateEditor(
|
||||||
|
|
||||||
foreach (var meta in MetaExtensions.AllRelevant)
|
foreach (var meta in MetaExtensions.AllRelevant)
|
||||||
{
|
{
|
||||||
if (!settings.RespectManual || state.Sources[meta] is not StateSource.Manual)
|
if (!settings.RespectManual || !state.Sources[meta].IsManual())
|
||||||
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
|
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var (key, value) in mergedDesign.Design.Materials)
|
||||||
|
{
|
||||||
|
if (!value.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var idx = MaterialValueIndex.FromKey(key);
|
||||||
|
// TODO
|
||||||
|
//if (state.Materials.TryGetValue(idx, out var materialState))
|
||||||
|
//{
|
||||||
|
// if (!settings.RespectManual || materialState.Source.IsManual())
|
||||||
|
// Editor.ChangeMaterialValue(state, idx, new MaterialValueState(materialState.Game, value.Value, materialState.DrawData));
|
||||||
|
//}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var actors = settings.Source is StateSource.Manual or StateSource.Ipc
|
var actors = settings.Source.RequiresChange()
|
||||||
? Applier.ApplyAll(state, requiresRedraw, false)
|
? Applier.ApplyAll(state, requiresRedraw, false)
|
||||||
: ActorData.Invalid;
|
: ActorData.Invalid;
|
||||||
|
|
||||||
|
|
@ -296,7 +321,7 @@ public class StateEditor(
|
||||||
public void ApplyDesign(object data, DesignBase design, ApplySettings settings)
|
public void ApplyDesign(object data, DesignBase design, ApplySettings settings)
|
||||||
{
|
{
|
||||||
var merged = settings.MergeLinks && design is Design d
|
var merged = settings.MergeLinks && design is Design d
|
||||||
? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, false)
|
? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, Config.AlwaysApplyAssociatedMods)
|
||||||
: new MergedDesign(design);
|
: new MergedDesign(design);
|
||||||
|
|
||||||
ApplyDesign(data, merged, settings with
|
ApplyDesign(data, merged, settings with
|
||||||
|
|
@ -311,7 +336,7 @@ public class StateEditor(
|
||||||
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
|
/// <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, ApplySettings settings)
|
||||||
{
|
{
|
||||||
if (!Config.ChangeEntireItem || settings.Source is not StateSource.Manual)
|
if (!Config.ChangeEntireItem || !settings.Source.IsManual())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
|
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ public class StateListener : IDisposable
|
||||||
var set = _customizations.Manager.GetSet(model.Clan, model.Gender);
|
var set = _customizations.Manager.GetSet(model.Clan, model.Gender);
|
||||||
foreach (var index in CustomizationExtensions.AllBasic)
|
foreach (var index in CustomizationExtensions.AllBasic)
|
||||||
{
|
{
|
||||||
if (state.Sources[index] is not StateSource.Fixed)
|
if (!state.Sources[index].IsFixed())
|
||||||
{
|
{
|
||||||
var newValue = customize[index];
|
var newValue = customize[index];
|
||||||
var oldValue = model[index];
|
var oldValue = model[index];
|
||||||
|
|
@ -214,7 +214,7 @@ public class StateListener : IDisposable
|
||||||
&& _manager.TryGetValue(identifier, out var state))
|
&& _manager.TryGetValue(identifier, out var state))
|
||||||
{
|
{
|
||||||
HandleEquipSlot(actor, state, slot, ref armor);
|
HandleEquipSlot(actor, state, slot, ref armor);
|
||||||
locked = state.Sources[slot, false] is StateSource.Ipc;
|
locked = state.Sources[slot, false] is StateSource.IpcFixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
_funModule.ApplyFunToSlot(actor, ref armor, slot);
|
_funModule.ApplyFunToSlot(actor, ref armor, slot);
|
||||||
|
|
@ -241,7 +241,7 @@ public class StateListener : IDisposable
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var changed = changedItem.Weapon(stain);
|
var changed = changedItem.Weapon(stain);
|
||||||
if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
|
if (current.Value == changed.Value && !state.Sources[slot, false].IsFixed())
|
||||||
{
|
{
|
||||||
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
|
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
|
||||||
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game);
|
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game);
|
||||||
|
|
@ -252,7 +252,7 @@ public class StateListener : IDisposable
|
||||||
_applier.ChangeWeapon(objects, slot, currentItem, stain);
|
_applier.ChangeWeapon(objects, slot, currentItem, stain);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_applier.ChangeArmor(objects, slot, current.ToArmor(), state.Sources[slot, false] is not StateSource.Ipc,
|
_applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(),
|
||||||
state.ModelData.IsHatVisible());
|
state.ModelData.IsHatVisible());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -278,20 +278,19 @@ public class StateListener : IDisposable
|
||||||
|| !_manager.TryGetValue(identifier, out var state))
|
|| !_manager.TryGetValue(identifier, out var state))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ref var actorWeapon = ref weapon;
|
var baseType = state.BaseData.Item(slot).Type;
|
||||||
var baseType = state.BaseData.Item(slot).Type;
|
var apply = false;
|
||||||
var apply = false;
|
switch (UpdateBaseData(actor, state, slot, weapon))
|
||||||
switch (UpdateBaseData(actor, state, slot, actorWeapon))
|
|
||||||
{
|
{
|
||||||
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
|
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
|
||||||
case UpdateState.Transformed: break;
|
case UpdateState.Transformed: break;
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
|
if (!state.Sources[slot, false].IsFixed())
|
||||||
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
|
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
|
||||||
else
|
else
|
||||||
apply = true;
|
apply = true;
|
||||||
|
|
||||||
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
|
if (!state.Sources[slot, true].IsFixed())
|
||||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
|
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
|
||||||
else
|
else
|
||||||
apply = true;
|
apply = true;
|
||||||
|
|
@ -306,9 +305,9 @@ public class StateListener : IDisposable
|
||||||
// Only allow overwriting identical weapons
|
// Only allow overwriting identical weapons
|
||||||
var newWeapon = state.ModelData.Weapon(slot);
|
var newWeapon = state.ModelData.Weapon(slot);
|
||||||
if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene)
|
if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene)
|
||||||
actorWeapon = newWeapon;
|
weapon = newWeapon;
|
||||||
else if (actorWeapon.Skeleton.Id != 0)
|
else if (weapon.Skeleton.Id != 0)
|
||||||
actorWeapon = actorWeapon.With(newWeapon.Stain);
|
weapon = weapon.With(newWeapon.Stain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fist Weapon Offhand hack.
|
// Fist Weapon Offhand hack.
|
||||||
|
|
@ -385,12 +384,12 @@ public class StateListener : IDisposable
|
||||||
// Update model state if not on fixed design.
|
// Update model state if not on fixed design.
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
var apply = false;
|
var apply = false;
|
||||||
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
|
if (!state.Sources[slot, false].IsFixed())
|
||||||
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
|
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
|
||||||
else
|
else
|
||||||
apply = true;
|
apply = true;
|
||||||
|
|
||||||
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
|
if (!state.Sources[slot, true].IsFixed())
|
||||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
|
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
|
||||||
else
|
else
|
||||||
apply = true;
|
apply = true;
|
||||||
|
|
@ -419,7 +418,7 @@ public class StateListener : IDisposable
|
||||||
switch (UpdateBaseCrest(actor, state, slot, value))
|
switch (UpdateBaseCrest(actor, state, slot, value))
|
||||||
{
|
{
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc)
|
if (!state.Sources[slot].IsFixed())
|
||||||
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game);
|
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game);
|
||||||
else
|
else
|
||||||
value = state.ModelData.Crest(slot);
|
value = state.ModelData.Crest(slot);
|
||||||
|
|
@ -565,7 +564,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||||
// or overwrite the stored model state with the new one.
|
// or overwrite the stored model state with the new one.
|
||||||
if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc)
|
if (!state.Sources[MetaIndex.VisorState].IsFixed())
|
||||||
value = state.ModelData.IsVisorToggled();
|
value = state.ModelData.IsVisorToggled();
|
||||||
else
|
else
|
||||||
_manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game);
|
_manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game);
|
||||||
|
|
@ -598,7 +597,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||||
// or overwrite the stored model state with the new one.
|
// or overwrite the stored model state with the new one.
|
||||||
if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc)
|
if (!state.Sources[MetaIndex.HatState].IsFixed())
|
||||||
value = state.ModelData.IsHatVisible();
|
value = state.ModelData.IsHatVisible();
|
||||||
else
|
else
|
||||||
_manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game);
|
_manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game);
|
||||||
|
|
@ -631,7 +630,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||||
// or overwrite the stored model state with the new one.
|
// or overwrite the stored model state with the new one.
|
||||||
if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc)
|
if (!state.Sources[MetaIndex.WeaponState].IsFixed())
|
||||||
value = state.ModelData.IsWeaponVisible();
|
value = state.ModelData.IsWeaponVisible();
|
||||||
else
|
else
|
||||||
_manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game);
|
_manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game);
|
||||||
|
|
@ -700,8 +699,8 @@ public class StateListener : IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var data = new ActorData(gameObject, _creatingIdentifier.ToName());
|
var data = new ActorData(gameObject, _creatingIdentifier.ToName());
|
||||||
_applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible());
|
_applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible());
|
||||||
_applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet());
|
_applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet());
|
||||||
_applier.ChangeMetaState(data, MetaIndex.WeaponState, _creatingState.ModelData.IsWeaponVisible());
|
_applier.ChangeMetaState(data, MetaIndex.WeaponState, _creatingState.ModelData.IsWeaponVisible());
|
||||||
|
|
||||||
ApplyParameters(_creatingState, drawObject);
|
ApplyParameters(_creatingState, drawObject);
|
||||||
|
|
@ -745,12 +744,18 @@ public class StateListener : IDisposable
|
||||||
else if (_config.UseAdvancedParameters)
|
else if (_config.UseAdvancedParameters)
|
||||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||||
break;
|
break;
|
||||||
|
case StateSource.IpcManual:
|
||||||
|
if (state.BaseData.Parameters.Set(flag, newValue))
|
||||||
|
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
|
||||||
|
else
|
||||||
|
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||||
|
break;
|
||||||
case StateSource.Fixed:
|
case StateSource.Fixed:
|
||||||
state.BaseData.Parameters.Set(flag, newValue);
|
state.BaseData.Parameters.Set(flag, newValue);
|
||||||
if (_config.UseAdvancedParameters)
|
if (_config.UseAdvancedParameters)
|
||||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||||
break;
|
break;
|
||||||
case StateSource.Ipc:
|
case StateSource.IpcFixed:
|
||||||
state.BaseData.Parameters.Set(flag, newValue);
|
state.BaseData.Parameters.Set(flag, newValue);
|
||||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using Glamourer.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
|
@ -23,8 +24,9 @@ public sealed class StateManager(
|
||||||
IClientState _clientState,
|
IClientState _clientState,
|
||||||
Configuration config,
|
Configuration config,
|
||||||
JobChangeState jobChange,
|
JobChangeState jobChange,
|
||||||
DesignMerger merger)
|
DesignMerger merger,
|
||||||
: StateEditor(editor, applier, @event, jobChange, config, items, merger), IReadOnlyDictionary<ActorIdentifier, ActorState>
|
ModSettingApplier modApplier)
|
||||||
|
: StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier), IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||||
{
|
{
|
||||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
|
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
|
||||||
|
|
||||||
|
|
@ -240,8 +242,10 @@ public sealed class StateManager(
|
||||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||||
state.Sources[flag] = StateSource.Game;
|
state.Sources[flag] = StateSource.Game;
|
||||||
|
|
||||||
|
state.Materials.Clear();
|
||||||
|
|
||||||
var actors = ActorData.Invalid;
|
var actors = ActorData.Invalid;
|
||||||
if (source is StateSource.Manual or StateSource.Ipc)
|
if (source is not StateSource.Game)
|
||||||
actors = Applier.ApplyAll(state, redraw, true);
|
actors = Applier.ApplyAll(state, redraw, true);
|
||||||
|
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
|
|
@ -260,7 +264,7 @@ public sealed class StateManager(
|
||||||
state.Sources[flag] = StateSource.Game;
|
state.Sources[flag] = StateSource.Game;
|
||||||
|
|
||||||
var actors = ActorData.Invalid;
|
var actors = ActorData.Invalid;
|
||||||
if (source is StateSource.Manual or StateSource.Ipc)
|
if (source is not StateSource.Game)
|
||||||
actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
|
actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
|
$"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
|
|
@ -314,28 +318,10 @@ public sealed class StateManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.Sources[MetaIndex.HatState] is StateSource.Fixed)
|
foreach (var meta in MetaExtensions.AllRelevant.Where(f => state.Sources[f] is StateSource.Fixed))
|
||||||
{
|
{
|
||||||
state.Sources[MetaIndex.HatState] = StateSource.Game;
|
state.Sources[meta] = StateSource.Game;
|
||||||
state.ModelData.SetHatVisible(state.BaseData.IsHatVisible());
|
state.ModelData.SetMeta(meta, state.BaseData.GetMeta(meta));
|
||||||
}
|
|
||||||
|
|
||||||
if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed)
|
|
||||||
{
|
|
||||||
state.Sources[MetaIndex.VisorState] = StateSource.Game;
|
|
||||||
state.ModelData.SetVisor(state.BaseData.IsVisorToggled());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed)
|
|
||||||
{
|
|
||||||
state.Sources[MetaIndex.WeaponState] = StateSource.Game;
|
|
||||||
state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.Sources[MetaIndex.Wetness] is StateSource.Fixed)
|
|
||||||
{
|
|
||||||
state.Sources[MetaIndex.Wetness] = StateSource.Game;
|
|
||||||
state.ModelData.SetIsWet(state.BaseData.IsWet());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,48 @@ public enum StateSource : byte
|
||||||
Game,
|
Game,
|
||||||
Manual,
|
Manual,
|
||||||
Fixed,
|
Fixed,
|
||||||
Ipc,
|
IpcFixed,
|
||||||
|
IpcManual,
|
||||||
|
|
||||||
// Only used for CustomizeParameters.
|
// Only used for CustomizeParameters.
|
||||||
Pending,
|
Pending,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class StateSourceExtensions
|
||||||
|
{
|
||||||
|
public static StateSource Base(this StateSource source)
|
||||||
|
=> source switch
|
||||||
|
{
|
||||||
|
StateSource.Manual or StateSource.IpcManual or StateSource.Pending => StateSource.Manual,
|
||||||
|
StateSource.Fixed or StateSource.IpcFixed => StateSource.Fixed,
|
||||||
|
_ => StateSource.Game,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsGame(this StateSource source)
|
||||||
|
=> source.Base() is StateSource.Game;
|
||||||
|
|
||||||
|
public static bool IsManual(this StateSource source)
|
||||||
|
=> source.Base() is StateSource.Manual;
|
||||||
|
|
||||||
|
public static bool IsFixed(this StateSource source)
|
||||||
|
=> source.Base() is StateSource.Fixed;
|
||||||
|
|
||||||
|
public static StateSource SetPending(this StateSource source)
|
||||||
|
=> source is StateSource.Manual ? StateSource.Pending : source;
|
||||||
|
|
||||||
|
public static bool RequiresChange(this StateSource source)
|
||||||
|
=> source switch
|
||||||
|
{
|
||||||
|
StateSource.Manual => true,
|
||||||
|
StateSource.IpcFixed => true,
|
||||||
|
StateSource.IpcManual => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsIpc(this StateSource source)
|
||||||
|
=> source is StateSource.IpcManual or StateSource.IpcFixed;
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe struct StateSources
|
public unsafe struct StateSources
|
||||||
{
|
{
|
||||||
public const int Size = (StateIndex.Size + 1) / 2;
|
public const int Size = (StateIndex.Size + 1) / 2;
|
||||||
|
|
@ -59,14 +95,16 @@ public unsafe struct StateSources
|
||||||
|
|
||||||
case (byte)StateSource.Game | ((byte)StateSource.Fixed << 4):
|
case (byte)StateSource.Game | ((byte)StateSource.Fixed << 4):
|
||||||
case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4):
|
case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4):
|
||||||
case (byte)StateSource.Ipc | ((byte)StateSource.Fixed << 4):
|
case (byte)StateSource.IpcFixed | ((byte)StateSource.Fixed << 4):
|
||||||
case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4):
|
case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4):
|
||||||
|
case (byte)StateSource.IpcManual | ((byte)StateSource.Fixed << 4):
|
||||||
_data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4));
|
_data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4));
|
||||||
break;
|
break;
|
||||||
case (byte)StateSource.Fixed:
|
case (byte)StateSource.Fixed:
|
||||||
case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed:
|
case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed:
|
||||||
case ((byte)StateSource.Ipc << 4) | (byte)StateSource.Fixed:
|
case ((byte)StateSource.IpcFixed << 4) | (byte)StateSource.Fixed:
|
||||||
case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed:
|
case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed:
|
||||||
|
case ((byte)StateSource.IpcManual << 4) | (byte)StateSource.Fixed:
|
||||||
_data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual);
|
_data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 04eb0b5ed3930e9cb87ad00dffa9c8be90b58bb3
|
Subproject commit 2d8a03eebd80e19c6936a28ab2e3a8c164cc17f3
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 260ac69cd6f17050eaf9b7e0b5ce9a8843edfee4
|
Subproject commit fb18c80551203a1cf6cd01ec2b0850fbc8e44240
|
||||||
Loading…
Add table
Add a link
Reference in a new issue