diff --git a/Penumbra.Api b/Penumbra.Api index 2e26d911..0a970295 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 2e26d9119249e67f03f415f8ebe1dcb7c28d5cf2 +Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs index 78c62953..55f1e259 100644 --- a/Penumbra/Api/Api/ModsApi.cs +++ b/Penumbra/Api/Api/ModsApi.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json.Linq; using OtterGui.Compression; using OtterGui.Services; using Penumbra.Api.Enums; @@ -33,12 +34,8 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable { switch (type) { - case ModPathChangeType.Deleted when oldDirectory != null: - ModDeleted?.Invoke(oldDirectory.Name); - break; - case ModPathChangeType.Added when newDirectory != null: - ModAdded?.Invoke(newDirectory.Name); - break; + case ModPathChangeType.Deleted when oldDirectory != null: ModDeleted?.Invoke(oldDirectory.Name); break; + case ModPathChangeType.Added when newDirectory != null: ModAdded?.Invoke(newDirectory.Name); break; case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null: ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name); break; @@ -46,7 +43,9 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable } public void Dispose() - => _communicator.ModPathChanged.Unsubscribe(OnModPathChanged); + { + _communicator.ModPathChanged.Unsubscribe(OnModPathChanged); + } public Dictionary GetModList() => _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text); @@ -109,6 +108,18 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable public event Action? ModAdded; public event Action? ModMoved; + public event Action? CreatingPcp + { + add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi); + remove => _communicator.PcpCreation.Unsubscribe(value!); + } + + public event Action? ParsingPcp + { + add => _communicator.PcpParsing.Subscribe(value!, PcpParsing.Priority.ModsApi); + remove => _communicator.PcpParsing.Unsubscribe(value!); + } + public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName) { if (!_modManager.TryGetMod(modDirectory, modName, out var mod) diff --git a/Penumbra/Api/Api/PenumbraApi.cs b/Penumbra/Api/Api/PenumbraApi.cs index 7ca41324..9e7eb964 100644 --- a/Penumbra/Api/Api/PenumbraApi.cs +++ b/Penumbra/Api/Api/PenumbraApi.cs @@ -17,7 +17,7 @@ public class PenumbraApi( UiApi ui) : IDisposable, IApiService, IPenumbraApi { public const int BreakingVersion = 5; - public const int FeatureVersion = 10; + public const int FeatureVersion = 11; public void Dispose() { diff --git a/Penumbra/Api/IpcProviders.cs b/Penumbra/Api/IpcProviders.cs index 7dcee375..0c80626f 100644 --- a/Penumbra/Api/IpcProviders.cs +++ b/Penumbra/Api/IpcProviders.cs @@ -54,6 +54,8 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.ModDeleted.Provider(pi, api.Mods), IpcSubscribers.ModAdded.Provider(pi, api.Mods), IpcSubscribers.ModMoved.Provider(pi, api.Mods), + IpcSubscribers.CreatingPcp.Provider(pi, api.Mods), + IpcSubscribers.ParsingPcp.Provider(pi, api.Mods), IpcSubscribers.GetModPath.Provider(pi, api.Mods), IpcSubscribers.SetModPath.Provider(pi, api.Mods), IpcSubscribers.GetChangedItems.Provider(pi, api.Mods), diff --git a/Penumbra/Communication/PcpCreation.cs b/Penumbra/Communication/PcpCreation.cs new file mode 100644 index 00000000..cb11b3c3 --- /dev/null +++ b/Penumbra/Communication/PcpCreation.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json.Linq; +using OtterGui.Classes; + +namespace Penumbra.Communication; + +/// +/// Triggered when the character.json file for a .pcp file is written. +/// +/// Parameter is the JObject that gets written to file. +/// Parameter is the object index of the game object this is written for. +/// +/// +public sealed class PcpCreation() : EventWrapper(nameof(PcpCreation)) +{ + public enum Priority + { + /// + ModsApi = int.MinValue, + } +} diff --git a/Penumbra/Communication/PcpParsing.cs b/Penumbra/Communication/PcpParsing.cs new file mode 100644 index 00000000..95b78951 --- /dev/null +++ b/Penumbra/Communication/PcpParsing.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json.Linq; +using OtterGui.Classes; + +namespace Penumbra.Communication; + +/// +/// Triggered when the character.json file for a .pcp file is parsed and applied. +/// +/// Parameter is parsed JObject that contains the data. +/// Parameter is the identifier of the created mod. +/// Parameter is the GUID of the created collection. +/// +/// +public sealed class PcpParsing() : EventWrapper(nameof(PcpParsing)) +{ + public enum Priority + { + /// + ModsApi = int.MinValue, + } +} diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index b9a0d9ce..d9a9f5fe 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -69,6 +69,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool DefaultTemporaryMode { get; set; } = false; public bool EnableCustomShapes { get; set; } = true; public bool DisablePcpHandling { get; set; } = false; + public bool AllowPcpIpc { get; set; } = true; public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; public ChangedItemMode ChangedItemDisplay { get; set; } = ChangedItemMode.GroupedCollapsed; public int OptionGroupCollapsibleMin { get; set; } = 5; diff --git a/Penumbra/Services/CommunicatorService.cs b/Penumbra/Services/CommunicatorService.cs index 5d745419..35f15e9e 100644 --- a/Penumbra/Services/CommunicatorService.cs +++ b/Penumbra/Services/CommunicatorService.cs @@ -81,6 +81,12 @@ public class CommunicatorService : IDisposable, IService /// public readonly ResolvedFileChanged ResolvedFileChanged = new(); + /// + public readonly PcpCreation PcpCreation = new(); + + /// + public readonly PcpParsing PcpParsing = new(); + public void Dispose() { CollectionChange.Dispose(); @@ -105,5 +111,7 @@ public class CommunicatorService : IDisposable, IService ChangedItemClick.Dispose(); SelectTab.Dispose(); ResolvedFileChanged.Dispose(); + PcpCreation.Dispose(); + PcpParsing.Dispose(); } } diff --git a/Penumbra/Services/PcpService.cs b/Penumbra/Services/PcpService.cs index 5f4a844d..32eca652 100644 --- a/Penumbra/Services/PcpService.cs +++ b/Penumbra/Services/PcpService.cs @@ -1,4 +1,3 @@ -using System.Buffers.Text; using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Newtonsoft.Json; @@ -76,9 +75,16 @@ public class PcpService : IApiService, IDisposable try { - var file = Path.Combine(newDirectory.FullName, "collection.json"); + var file = Path.Combine(newDirectory.FullName, "character.json"); if (!File.Exists(file)) - return; + { + // First version had collection.json, changed. + var oldFile = Path.Combine(newDirectory.FullName, "collection.json"); + if (File.Exists(oldFile)) + File.Move(oldFile, file, true); + else + return; + } var text = File.ReadAllText(file); var jObj = JObject.Parse(text); @@ -110,10 +116,12 @@ public class PcpService : IApiService, IDisposable // ignored. } } + if (_config.AllowPcpIpc) + _communicator.PcpParsing.Invoke(jObj, mod.Identifier, collection.Identity.Id); } catch (Exception ex) { - Penumbra.Log.Error($"Error reading the collection.json file from {mod.Identifier}:\n{ex}"); + Penumbra.Log.Error($"Error reading the character.json file from {mod.Identifier}:\n{ex}"); } } @@ -145,7 +153,7 @@ public class PcpService : IApiService, IDisposable var time = DateTime.Now; var modDirectory = CreateMod(identifier, note, time); await CreateDefaultMod(modDirectory, collection.ModCollection, tree, cancel); - await CreateCollectionInfo(modDirectory, identifier, note, time, cancel); + await CreateCollectionInfo(modDirectory, objectIndex, identifier, note, time, cancel); var file = ZipUp(modDirectory); return (true, file); } @@ -163,7 +171,7 @@ public class PcpService : IApiService, IDisposable return fileName; } - private static async Task CreateCollectionInfo(DirectoryInfo directory, ActorIdentifier actor, string note, DateTime time, + private async Task CreateCollectionInfo(DirectoryInfo directory, ObjectIndex index, ActorIdentifier actor, string note, DateTime time, CancellationToken cancel = default) { var jObj = new JObject @@ -176,7 +184,9 @@ public class PcpService : IApiService, IDisposable }; if (note.Length > 0) cancel.ThrowIfCancellationRequested(); - var filePath = Path.Combine(directory.FullName, "collection.json"); + if (_config.AllowPcpIpc) + await _framework.Framework.RunOnFrameworkThread(() => _communicator.PcpCreation.Invoke(jObj, index.Index)); + var filePath = Path.Combine(directory.FullName, "character.json"); await using var file = File.Open(filePath, File.Exists(filePath) ? FileMode.Truncate : FileMode.CreateNew); await using var stream = new StreamWriter(file); await using var json = new JsonTextWriter(stream);