diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327b75b..feecf19 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,13 +9,15 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 with: submodules: recursive - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.x.x' + dotnet-version: | + 10.x.x + 9.x.x - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index 6316776..5639c7b 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -9,13 +9,15 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 with: submodules: recursive - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.x.x' + dotnet-version: | + 10.x.x + 9.x.x - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/Glamourer.Api b/Glamourer.Api index 54c1944..5b6730d 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e +Subproject commit 5b6730d46f17bdd02a441e23e2141576cf7acf53 diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index 45d84b9..14cff3b 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -1,13 +1,13 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; -using OtterGui; using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Api; @@ -15,14 +15,23 @@ namespace Glamourer.Api; public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal IEnumerable FindExistingStates(string actorName) + internal IEnumerable FindExistingStates(string actorName, ushort worldId = ushort.MaxValue) { if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) yield break; - foreach (var state in stateManager.Values.Where(state - => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) - yield return state; + if (worldId == WorldId.AnyWorld.Id) + { + foreach (var state in stateManager.Values.Where(state + => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) + yield return state; + } + else + { + var identifier = actors.CreatePlayer(byteString, worldId); + if (stateManager.TryGetValue(identifier, out var state)) + yield return state; + } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 14c0512..85f873a 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -3,14 +3,17 @@ using OtterGui.Services; namespace Glamourer.Api; -public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService +public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 6; + public const int CurrentApiVersionMinor = 7; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); + public bool AutoReloadGearEnabled + => config.AutoRedrawEquipOnChanges; + public IGlamourerApiDesigns Designs => designs; diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 2701f18..f120db3 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -22,6 +22,7 @@ public sealed class IpcProviders : IDisposable, IApiService new FuncProvider<(int Major, int Minor)>(pi, "Glamourer.ApiVersions", () => api.ApiVersion), // backward compatibility new FuncProvider(pi, "Glamourer.ApiVersion", () => api.ApiVersion.Major), // backward compatibility IpcSubscribers.ApiVersion.Provider(pi, api), + IpcSubscribers.AutoReloadGearEnabled.Provider(pi, api), IpcSubscribers.GetDesignList.Provider(pi, api.Designs), IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs), @@ -50,13 +51,18 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetStateBase64Name.Provider(pi, api.State), IpcSubscribers.ApplyState.Provider(pi, api.State), IpcSubscribers.ApplyStateName.Provider(pi, api.State), + IpcSubscribers.ReapplyState.Provider(pi, api.State), + IpcSubscribers.ReapplyStateName.Provider(pi, api.State), IpcSubscribers.RevertState.Provider(pi, api.State), IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State), + IpcSubscribers.CanUnlock.Provider(pi, api.State), IpcSubscribers.UnlockStateName.Provider(pi, api.State), + IpcSubscribers.DeletePlayerState.Provider(pi, api.State), IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State), IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), + IpcSubscribers.AutoReloadGearChanged.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), IpcSubscribers.StateChangedWithType.Provider(pi, api.State), IpcSubscribers.StateFinalized.Provider(pi, api.State), @@ -75,3 +81,5 @@ public sealed class IpcProviders : IDisposable, IApiService _disposedProvider.Dispose(); } } + + diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 68c593b..4ce9c01 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -8,6 +8,7 @@ using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; using StateChanged = Glamourer.Events.StateChanged; namespace Glamourer.Api; @@ -17,9 +18,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly ApiHelpers _helpers; private readonly StateManager _stateManager; private readonly DesignConverter _converter; - private readonly Configuration _config; private readonly AutoDesignApplier _autoDesigns; private readonly ActorObjectManager _objects; + private readonly AutoRedrawChanged _autoRedraw; private readonly StateChanged _stateChanged; private readonly StateFinalized _stateFinalized; private readonly GPoseService _gPose; @@ -27,9 +28,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public StateApi(ApiHelpers helpers, StateManager stateManager, DesignConverter converter, - Configuration config, AutoDesignApplier autoDesigns, ActorObjectManager objects, + AutoRedrawChanged autoRedraw, StateChanged stateChanged, StateFinalized stateFinalized, GPoseService gPose) @@ -37,12 +38,13 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _helpers = helpers; _stateManager = stateManager; _converter = converter; - _config = config; _autoDesigns = autoDesigns; _objects = objects; + _autoRedraw = autoRedraw; _stateChanged = stateChanged; _stateFinalized = stateFinalized; _gPose = gPose; + _autoRedraw.Subscribe(OnAutoRedrawChange, AutoRedrawChanged.Priority.StateApi); _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); _stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi); _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi); @@ -50,6 +52,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public void Dispose() { + _autoRedraw.Unsubscribe(OnAutoRedrawChange); _stateChanged.Unsubscribe(OnStateChanged); _stateFinalized.Unsubscribe(OnStateFinalized); _gPose.Unsubscribe(OnGPoseChange); @@ -123,6 +126,48 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public GlamourerApiEc ReapplyState(int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state is null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + Reapply(_objects.Objects[objectIndex], state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc ReapplyStateName(string playerName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(playerName); + + var any = false; + var anyReapplied = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyReapplied = true; + anyReapplied |= Reapply(state, key, flags) is GlamourerApiEc.Success; + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyReapplied) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags) { var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); @@ -180,6 +225,20 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key); + isLocked = false; + canUnlock = true; + if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + if (state is null) + return ApiHelpers.Return(GlamourerApiEc.Success, args); + isLocked = state.IsLocked; + canUnlock = state.CanUnlock(key); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + public GlamourerApiEc UnlockStateName(string playerName, uint key) { var args = ApiHelpers.Args("Name", playerName, "Key", key); @@ -202,6 +261,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public GlamourerApiEc DeletePlayerState(string playerName, ushort worldId, uint key) + { + var args = ApiHelpers.Args("Name", playerName, "World", worldId, "Key", key); + var states = _helpers.FindExistingStates(playerName).ToList(); + if (states.Count is 0) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + var anyLocked = false; + foreach (var state in states) + { + if (state.CanUnlock(key)) + _stateManager.DeleteState(state.Identifier); + else + anyLocked = true; + } + + return ApiHelpers.Return(anyLocked + ? GlamourerApiEc.InvalidKey + : GlamourerApiEc.Success, args); + } + public int UnlockAll(uint key) => _stateManager.Values.Count(state => state.Unlock(key)); @@ -251,6 +331,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public event Action? AutoReloadGearChanged; public event Action? StateChanged; public event Action? StateChangedWithType; public event Action? StateFinalized; @@ -265,9 +346,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable ApiHelpers.Lock(state, key, flags); } + private GlamourerApiEc Reapply(ActorState state, uint key, ApplyFlag flags) + { + if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid) + return GlamourerApiEc.ActorNotFound; + + foreach (var actor in actors.Objects) + Reapply(actor, state, key, flags); + + return GlamourerApiEc.Success; + } + + private void Reapply(Actor actor, ActorState state, uint key, ApplyFlag flags) + { + var source = flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcFixed : StateSource.IpcManual; + _stateManager.ReapplyState(actor, state, false, source, true); + ApiHelpers.Lock(state, key, flags); + } + private void Revert(ActorState state, uint key, ApplyFlag flags) { - var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; + var source = flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcFixed : StateSource.IpcManual; switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) { case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break; @@ -325,8 +424,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable }; } - private void OnGPoseChange(bool gPose) - => GPoseChanged?.Invoke(gPose); + private void OnAutoRedrawChange(bool autoReload) + => AutoReloadGearChanged?.Invoke(autoReload); private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { @@ -347,4 +446,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable foreach (var actor in actors.Objects) StateFinalized.Invoke(actor.Address, type); } + + private void OnGPoseChange(bool gPose) + => GPoseChanged?.Invoke(gPose); } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 35ee3aa..848e7d6 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -100,7 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public new JObject JsonSerialize() { - var ret = new JObject() + var ret = new JObject { ["FileVersion"] = FileVersion, ["Identifier"] = Identifier, @@ -131,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn var ret = new JArray(); foreach (var (mod, settings) in AssociatedMods) { - var obj = new JObject() + var obj = new JObject { ["Name"] = mod.Name, ["Directory"] = mod.DirectoryName, - ["Enabled"] = settings.Enabled, }; + if (settings.Remove) + obj["Remove"] = true; + else if (settings.ForceInherit) + obj["Inherit"] = true; + else + obj["Enabled"] = settings.Enabled; if (settings.Enabled) { obj["Priority"] = settings.Priority; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 3ddfefd..92f8398 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -6,12 +6,13 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; +using OtterGui.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; + namespace Glamourer.Designs; public sealed class DesignManager : DesignEditor @@ -228,7 +229,7 @@ public sealed class DesignManager : DesignEditor design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; - var idx = design.Tags.IndexOf(tag); + var idx = design.Tags.AsEnumerable().IndexOf(tag); SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); @@ -261,7 +262,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, - new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag))); + new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag))); } /// Add an associated mod to a design. @@ -556,7 +557,7 @@ public sealed class DesignManager : DesignEditor try { File.Move(SaveService.FileNames.MigrationDesignFile, - Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak")); + Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"), true); Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file."); } catch (Exception ex) diff --git a/Glamourer/Designs/History/DesignTransaction.cs b/Glamourer/Designs/History/DesignTransaction.cs index 98e3eec..65086db 100644 --- a/Glamourer/Designs/History/DesignTransaction.cs +++ b/Glamourer/Designs/History/DesignTransaction.cs @@ -1,6 +1,7 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; +using Glamourer.State; using Penumbra.GameData.Enums; namespace Glamourer.Designs.History; @@ -125,7 +126,10 @@ public readonly record struct MaterialTransaction(MaterialValueIndex Index, Colo => older is MaterialTransaction other && Index == other.Index ? new MaterialTransaction(Index, other.Old, New) : null; public void Revert(IDesignEditor editor, object data) - => ((DesignManager)editor).ChangeMaterialValue((Design)data, Index, Old); + { + if (editor is DesignManager e) + e.ChangeMaterialValue((Design)data, Index, Old); + } } /// Only Designs. diff --git a/Glamourer/Events/AutoRedrawChanged.cs b/Glamourer/Events/AutoRedrawChanged.cs new file mode 100644 index 0000000..a8dd03a --- /dev/null +++ b/Glamourer/Events/AutoRedrawChanged.cs @@ -0,0 +1,16 @@ +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when the auto-reload gear setting is changed in glamourer configuration. +/// +public sealed class AutoRedrawChanged() + : EventWrapper(nameof(AutoRedrawChanged)) +{ + public enum Priority + { + /// + StateApi = int.MinValue, + } +} \ No newline at end of file diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 86ae713..560621d 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 2daff91..e2dbf8b 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 13, + "DalamudApiLevel": 14, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index a39db2e..abde603 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -102,6 +102,8 @@ public class MainWindow : Window, IDisposable SelectTab = _config.Ephemeral.SelectedTab; _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); IsOpen = _config.OpenWindowAtStart; + + _penumbra.DrawSettingsSection += Settings.DrawPenumbraIntegrationSettings; } public void OpenSettings() @@ -120,7 +122,10 @@ public class MainWindow : Window, IDisposable } public void Dispose() - => _event.Unsubscribe(OnTabSelected); + { + _event.Unsubscribe(OnTabSelected); + _penumbra.DrawSettingsSection -= Settings.DrawPenumbraIntegrationSettings; + } public override void Draw() { diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ed6018e..dff9a6e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -22,11 +22,12 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private readonly CustomizeService _customize; private readonly GPoseService _gpose; - private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; + private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2 + BonusExtensions.AllFlags.Count]; - public IEnumerable> LastItems - => EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems) - .Select(p => new KeyValuePair(p.First, p.Second)); + public IEnumerable> LastItems + => EquipSlotExtensions.EqdpSlots.Cast().Append(EquipSlot.MainHand).Append(EquipSlot.OffHand) + .Concat(BonusExtensions.AllFlags.Cast()).Zip(_lastItems) + .Select(p => new KeyValuePair(p.First, p.Second)); public ChangedItemType LastType { get; private set; } = ChangedItemType.None; public uint LastId { get; private set; } @@ -72,6 +73,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable if (!Player()) return; + var bonusSlot = item.Type.ToBonus(); + if (bonusSlot is not BonusItemFlag.Unknown) + { + // + 2 due to weapons. + var glasses = _lastItems[bonusSlot.ToSlot() + 2]; + using (_ = !openTooltip ? null : ImRaii.Tooltip()) + { + ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor."); + if (glasses.Valid) + ImGui.TextUnformatted($"{prefix}Control + Right-Click to re-apply {glasses.Name} to current actor."); + } + + return; + } + var slot = item.Type.ToSlot(); var last = _lastItems[slot.ToIndex()]; switch (slot) @@ -109,6 +125,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public void ApplyItem(ActorState state, EquipItem item) { + var bonusSlot = item.Type.ToBonus(); + if (bonusSlot is not BonusItemFlag.Unknown) + { + // + 2 due to weapons. + var glasses = _lastItems[bonusSlot.ToSlot() + 2]; + if (ImGui.GetIO().KeyCtrl && glasses.Valid) + { + Glamourer.Log.Debug($"Re-Applying {glasses.Name} to {bonusSlot.ToName()}."); + SetLastItem(bonusSlot, default, state); + _stateManager.ChangeBonusItem(state, bonusSlot, glasses, ApplySettings.Manual); + } + else + { + Glamourer.Log.Debug($"Applying {item.Name} to {bonusSlot.ToName()}."); + SetLastItem(bonusSlot, item, state); + _stateManager.ChangeBonusItem(state, bonusSlot, item, ApplySettings.Manual); + } + + return; + } + var slot = item.Type.ToSlot(); var last = _lastItems[slot.ToIndex()]; switch (slot) @@ -265,7 +302,22 @@ public sealed class PenumbraChangedItemTooltip : IDisposable { var oldItem = state.ModelData.Item(slot); if (oldItem.Id != item.Id) - _lastItems[slot.ToIndex()] = oldItem; + last = oldItem; + } + } + + private void SetLastItem(BonusItemFlag slot, EquipItem item, ActorState state) + { + ref var last = ref _lastItems[slot.ToSlot() + 2]; + if (!item.Valid) + { + last = default; + } + else + { + var oldItem = state.ModelData.BonusItem(slot); + if (oldItem.Id != item.Id) + last = oldItem; } } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs index dbcb30c..61dad53 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs @@ -1,8 +1,5 @@ using Glamourer.Api.Enums; -using Glamourer.Designs; using Dalamud.Bindings.ImGui; -using OtterGui; -using static Penumbra.GameData.Files.ShpkFile; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index f4e6925..22c7597 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -33,6 +33,11 @@ public class IpcTesterPanel( ImGui.SameLine(); ImGui.TextUnformatted($"({major}.{minor:D4})"); + ImGui.TextUnformatted(AutoReloadGearEnabled.Label); + var autoRedraw = new AutoReloadGearEnabled(pluginInterface).Invoke(); + ImGui.SameLine(); + ImGui.TextUnformatted(autoRedraw ? "Enabled" : "Disabled"); + designs.Draw(); items.Draw(); state.Draw(); @@ -49,6 +54,7 @@ public class IpcTesterPanel( return; Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); + state.AutoRedrawChanged.Enable(); state.GPoseChanged.Enable(); state.StateChanged.Enable(); state.StateFinalized.Enable(); @@ -72,6 +78,7 @@ public class IpcTesterPanel( Glamourer.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester."); _subscribed = false; + state.AutoRedrawChanged.Disable(); state.GPoseChanged.Disable(); state.StateChanged.Disable(); state.StateFinalized.Disable(); diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index e97d337..6fb9d68 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -1,11 +1,11 @@ -using Dalamud.Interface; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api.Enums; using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using Glamourer.Designs; -using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -31,6 +31,10 @@ public class StateIpcTester : IUiService, IDisposable private string _base64State = string.Empty; private string? _getStateString; + public readonly EventSubscriber AutoRedrawChanged; + private bool _lastAutoRedrawChangeValue; + private DateTime _lastAutoRedrawChangeTime; + public readonly EventSubscriber StateChanged; private nint _lastStateChangeActor; private ByteString _lastStateChangeName = ByteString.Empty; @@ -51,10 +55,12 @@ public class StateIpcTester : IUiService, IDisposable public StateIpcTester(IDalamudPluginInterface pluginInterface) { - _pluginInterface = pluginInterface; - StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); - StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); - GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + _pluginInterface = pluginInterface; + AutoRedrawChanged = AutoReloadGearChanged.Subscriber(_pluginInterface, OnAutoRedrawChanged); + StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); + GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + AutoRedrawChanged.Disable(); StateChanged.Disable(); StateFinalized.Disable(); GPoseChanged.Disable(); @@ -62,6 +68,7 @@ public class StateIpcTester : IUiService, IDisposable public void Dispose() { + AutoRedrawChanged.Dispose(); StateChanged.Dispose(); StateFinalized.Dispose(); GPoseChanged.Dispose(); @@ -83,6 +90,8 @@ public class StateIpcTester : IUiService, IDisposable IpcTesterHelpers.DrawIntro("Last Error"); ImGui.TextUnformatted(_lastError.ToString()); + IpcTesterHelpers.DrawIntro("Last Auto Redraw Change"); + ImGui.TextUnformatted($"{_lastAutoRedrawChangeValue} at {_lastAutoRedrawChangeTime.ToLocalTime().TimeOfDay}"); IpcTesterHelpers.DrawIntro("Last State Change"); PrintChangeName(); IpcTesterHelpers.DrawIntro("Last State Finalization"); @@ -138,6 +147,14 @@ public class StateIpcTester : IUiService, IDisposable if (ImUtf8.Button("Apply Base64##Name"u8)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); + IpcTesterHelpers.DrawIntro(ReapplyState.Label); + if (ImUtf8.Button("Reapply##Idx"u8)) + _lastError = new ReapplyState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(ReapplyStateName.Label); + if (ImUtf8.Button("Reapply##Name"u8)) + _lastError = new ReapplyStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); + IpcTesterHelpers.DrawIntro(RevertState.Label); if (ImUtf8.Button("Revert##Idx"u8)) _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); @@ -225,6 +242,12 @@ public class StateIpcTester : IUiService, IDisposable ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}"); } + private void OnAutoRedrawChanged(bool value) + { + _lastAutoRedrawChangeValue = value; + _lastAutoRedrawChangeTime = DateTime.UtcNow; + } + private void OnStateChanged(nint actor, StateChangeType type) { _lastStateChangeActor = actor; diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index aa94a23..833ebe4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -89,7 +89,13 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.Separator(); foreach (var (slot, item) in _penumbraTooltip.LastItems) { - ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item"); + switch (slot) + { + case EquipSlot e: ImGuiUtil.DrawTableColumn($"{e.ToName()} Revert-Item"); break; + case BonusItemFlag f: ImGuiUtil.DrawTableColumn($"{f.ToName()} Revert-Item"); break; + default: ImGuiUtil.DrawTableColumn("Unk Revert-Item"); break; + } + ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None"); ImGui.TableNextColumn(); } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 02f00db..587fe65 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -71,6 +71,8 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawApplyAllButton() { var (id, name) = penumbra.CurrentCollection; + if (config.Ephemeral.IncognitoMode) + name = id.ShortGuid(); if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty)) ApplyAll(); diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1cdb171..a68c191 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -490,7 +490,7 @@ public class MultiDesignPanel( foreach (var leaf in selector.SelectedPaths.OfType()) { - var index = leaf.Value.Tags.IndexOf(_tag); + var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag); if (index >= 0) _removeDesigns.Add((leaf.Value, index)); else diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 0a84adc..e559841 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -6,9 +6,11 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Events; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using OtterGui; using OtterGui.Raii; @@ -30,6 +32,7 @@ public class SettingsTab( CodeDrawer codeDrawer, Glamourer glamourer, AutoDesignApplier autoDesignApplier, + AutoRedrawChanged autoRedraw, PcpService pcpService) : ITab { @@ -69,6 +72,12 @@ public class SettingsTab( MainWindow.DrawSupportButtons(glamourer, changelog.Changelog); } + public void DrawPenumbraIntegrationSettings() + { + DrawPenumbraIntegrationSettings1(); + DrawPenumbraIntegrationSettings2(); + } + private void DrawBehaviorSettings() { if (!ImUtf8.CollapsingHeader("Glamourer Behavior"u8)) @@ -89,10 +98,28 @@ public class SettingsTab( Checkbox("Enable Festival Easter-Eggs"u8, "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this."u8, config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); + DrawPenumbraIntegrationSettings1(); + Checkbox("Revert Manual Changes on Zone Change"u8, + "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, + config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); + PaletteImportButton(); + DrawPenumbraIntegrationSettings2(); + Checkbox("Prevent Random Design Repeats"u8, + "When using random designs, prevent the same design from being chosen twice in a row."u8, + config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); + ImGui.NewLine(); + } + + private void DrawPenumbraIntegrationSettings1() + { Checkbox("Auto-Reload Gear"u8, "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, - config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); - Checkbox("Attach to PCP-Handling"u8, + config.AutoRedrawEquipOnChanges, v => + { + config.AutoRedrawEquipOnChanges = v; + autoRedraw.Invoke(v); + }); + Checkbox("Attach to PCP Handling"u8, "Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8, config.AttachToPcp, pcpService.Set); var active = config.DeleteDesignModifier.IsActive(); @@ -101,10 +128,10 @@ public class SettingsTab( pcpService.CleanPcpDesigns(); if (!active) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.DeleteDesignModifier} while clicking."); - Checkbox("Revert Manual Changes on Zone Change"u8, - "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, - config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); - PaletteImportButton(); + } + + private void DrawPenumbraIntegrationSettings2() + { Checkbox("Always Apply Associated Mods"u8, "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"u8 + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"u8 @@ -114,10 +141,6 @@ public class SettingsTab( "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, v => config.UseTemporarySettings = v); - Checkbox("Prevent Random Design Repeats"u8, - "When using random designs, prevent the same design from being chosen twice in a row."u8, - config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); - ImGui.NewLine(); } private void DrawDesignDefaultSettings() diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index d75f2dc..2323ca2 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -194,7 +194,7 @@ public class UnlockTable : Table, IDisposable ImGui.Dummy(new Vector2(ImGui.GetFrameHeight())); ImGui.SameLine(); ImGui.AlignTextToFramePadding(); - if (ImGui.Selectable(item.Name)) + if (ImGui.Selectable(item.Name) && !item.Id.IsBonusItem) Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString); if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state)) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 5b731b0..43e500b 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -62,7 +62,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var drawData = type switch { - MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), + MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId), _ => GetTempSlot((Weapon*)characterBase), }; var mode = PrepareColorSet.GetMode(material); @@ -192,13 +192,24 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } /// We need to get the temporary set, variant and stain that is currently being set if it is available. - private static CharacterWeapon GetTempSlot(Human* human, byte slotId) + private static CharacterWeapon GetTempSlot(Human* human, HumanSlot slotId) { - if (human->ChangedEquipData == null) - return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); + if (human->ChangedEquipData is null) + return slotId.ToSpecificEnum() switch + { + EquipSlot slot => ((Model)human).GetArmor(slot).ToWeapon(0), + BonusItemFlag bonus => ((Model)human).GetBonus(bonus).ToWeapon(0), + _ => default, + }; - var item = (ChangedEquipData*)human->ChangedEquipData + slotId; - return ((CharacterArmor*)item)->ToWeapon(0); + if (!slotId.ToSlotIndex(out var index)) + return default; + + var item = (ChangedEquipData*)human->ChangedEquipData + index; + if (index < 10) + return ((CharacterArmor*)item)->ToWeapon(0); + + return new CharacterWeapon(item->BonusModel, 0, item->BonusVariant, StainIds.None); } /// diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 4d70a3f..b2813cd 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; +using Dalamud.Plugin.Ipc.Exceptions; using Glamourer.Events; using Glamourer.State; using Newtonsoft.Json.Linq; @@ -36,7 +37,7 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 8; + public const int RequiredPenumbraFeatureVersion = 13; private const int KeyFixed = -1610; private const string NameFixed = "Glamourer (Automation)"; @@ -77,6 +78,8 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer? _queryTemporaryModSettingsPlayer; private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; + private global::Penumbra.Api.IpcSubscribers.RegisterSettingsSection? _registerSettingsSection; + private global::Penumbra.Api.IpcSubscribers.UnregisterSettingsSection? _unregisterSettingsSection; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; private Func? _checkCurrentChangedItems; private Func? _checkCutsceneParent; @@ -152,6 +155,11 @@ public class PenumbraService : IDisposable remove => _pcpParsed.Event -= value; } + public event Action? DrawSettingsSection; + + private void InvokeDrawSettingsSection() + => DrawSettingsSection?.Invoke(); + public Dictionary GetCollections() => Available ? _collections!.Invoke() : []; @@ -565,6 +573,10 @@ public class PenumbraService : IDisposable _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); _checkCurrentChangedItems = new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); + _registerSettingsSection = new global::Penumbra.Api.IpcSubscribers.RegisterSettingsSection(_pluginInterface); + _unregisterSettingsSection = new global::Penumbra.Api.IpcSubscribers.UnregisterSettingsSection(_pluginInterface); + + _registerSettingsSection.Invoke(InvokeDrawSettingsSection); Available = true; _penumbraReloaded.Invoke(); @@ -587,6 +599,15 @@ public class PenumbraService : IDisposable _modSettingChanged.Disable(); _pcpCreated.Disable(); _pcpParsed.Disable(); + try + { + _unregisterSettingsSection?.Invoke(InvokeDrawSettingsSection); + } + catch (IpcNotReadyError) + { + // Ignore. + } + if (Available) { _collectionByIdentifier = null; @@ -617,6 +638,8 @@ public class PenumbraService : IDisposable _getChangedItems = null; _changedItems = null; _checkCurrentChangedItems = null; + _registerSettingsSection = null; + _unregisterSettingsSection = null; Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index 3abf13a..511cca6 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -1,9 +1,10 @@ using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; namespace Glamourer.Services; -public class BackupService +public class BackupService : IAsyncService { private readonly Logger _logger; private readonly DirectoryInfo _configDirectory; @@ -14,7 +15,7 @@ public class BackupService _logger = logger; _fileNames = GlamourerFiles(fileNames); _configDirectory = new DirectoryInfo(fileNames.ConfigDirectory); - Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames); + Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), _fileNames)); } /// Create a permanent backup with a given name for migrations. @@ -40,4 +41,9 @@ public class BackupService return list; } + + public Task Awaiter { get; } + + public bool Finished + => Awaiter.IsCompletedSuccessfully; } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index af2e88b..4a82f0e 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -50,7 +50,8 @@ public class CodeService | CodeFlag.OopsMiqote | CodeFlag.OopsRoegadyn | CodeFlag.OopsAuRa - | CodeFlag.OopsHrothgar; + | CodeFlag.OopsHrothgar + | CodeFlag.OopsViera; public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles; @@ -250,3 +251,4 @@ public class CodeService _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } + diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index 85783b9..e8a9f55 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -1,4 +1,3 @@ -using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.DragDrop; using Dalamud.Plugin; using Dalamud.Plugin.Services; @@ -17,6 +16,7 @@ public class DalamudServices services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); + services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index c346ab1..9800445 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -385,7 +385,7 @@ public class StateApplier( var actors = ChangeMetaState(state, MetaIndex.Wetness, true); if (redraw) { - if (withLock) + if (withLock && actors.Valid) state.TempLock(); ForceRedraw(actors); } diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index 9e36276..b15c917 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -1,18 +1,18 @@ { "version": 1, "dependencies": { - "net9.0-windows7.0": { + "net10.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[13.0.0, )", - "resolved": "13.0.0", - "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" + "requested": "[14.0.1, )", + "resolved": "14.0.1", + "contentHash": "y0WWyUE6dhpGdolK3iKgwys05/nZaVf4ZPtIjpLhJBZvHxkkiE23zYRo7K7uqAgoK/QvK5cqF6l3VG5AbgC6KA==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", - "requested": "[1.2.25, )", - "resolved": "1.2.25", - "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + "requested": "[1.2.39, )", + "resolved": "1.2.39", + "contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg==" }, "Vortice.Direct3D11": { "type": "Direct", @@ -32,10 +32,7 @@ "FlatSharp.Runtime": { "type": "Transitive", "resolved": "7.9.0", - "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==", - "dependencies": { - "System.Memory": "4.5.5" - } + "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==" }, "JetBrains.Annotations": { "type": "Transitive", @@ -68,11 +65,6 @@ "SharpGen.Runtime": "2.1.2-beta" } }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" - }, "Vortice.DirectX": { "type": "Transitive", "resolved": "3.4.2-beta", @@ -116,8 +108,8 @@ "FlatSharp.Compiler": "[7.9.0, )", "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.10.0, )", - "Penumbra.String": "[1.0.6, )" + "Penumbra.Api": "[5.13.0, )", + "Penumbra.String": "[1.0.7, )" } }, "penumbra.string": { diff --git a/OtterGui b/OtterGui index 4a9b71a..ff1e654 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 +Subproject commit ff1e6543845e3b8c53a5f8b240bc38faffb1b3bf diff --git a/Penumbra.Api b/Penumbra.Api index af41b17..52a3216 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit af41b1787acef9df7dc83619fe81e63a36443ee5 +Subproject commit 52a3216a525592205198303df2844435e382cf87 diff --git a/Penumbra.GameData b/Penumbra.GameData index 15e7c8e..0e973ed 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 15e7c8eb41867e6bbd3fe6a8885404df087bc7e7 +Subproject commit 0e973ed6eace6afd31cd298f8c58f76fa8d5ef60 diff --git a/Penumbra.String b/Penumbra.String index 878acce..9bd016f 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd +Subproject commit 9bd016fbef5fb2de467dd42165267fdd93cd9592 diff --git a/repo.json b/repo.json index 78132bf..31b4c6e 100644 --- a/repo.json +++ b/repo.json @@ -17,19 +17,19 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.1", - "TestingAssemblyVersion": "1.5.1.1", + "AssemblyVersion": "1.5.1.7", + "TestingAssemblyVersion": "1.5.1.7", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 13, - "TestingDalamudApiLevel": 13, + "DalamudApiLevel": 14, + "TestingDalamudApiLevel": 14, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.7/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ]