Add Pcp Events.

This commit is contained in:
Ottermandias 2025-08-22 18:08:22 +02:00
parent fb34238530
commit 10894d451a
9 changed files with 89 additions and 16 deletions

@ -1 +1 @@
Subproject commit 2e26d9119249e67f03f415f8ebe1dcb7c28d5cf2 Subproject commit 0a970295b2398683b1e49c46fd613541e2486210

View file

@ -1,3 +1,4 @@
using Newtonsoft.Json.Linq;
using OtterGui.Compression; using OtterGui.Compression;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
@ -33,12 +34,8 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
{ {
switch (type) switch (type)
{ {
case ModPathChangeType.Deleted when oldDirectory != null: case ModPathChangeType.Deleted when oldDirectory != null: ModDeleted?.Invoke(oldDirectory.Name); break;
ModDeleted?.Invoke(oldDirectory.Name); case ModPathChangeType.Added when newDirectory != null: ModAdded?.Invoke(newDirectory.Name); break;
break;
case ModPathChangeType.Added when newDirectory != null:
ModAdded?.Invoke(newDirectory.Name);
break;
case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null: case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null:
ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name); ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name);
break; break;
@ -46,7 +43,9 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
} }
public void Dispose() public void Dispose()
=> _communicator.ModPathChanged.Unsubscribe(OnModPathChanged); {
_communicator.ModPathChanged.Unsubscribe(OnModPathChanged);
}
public Dictionary<string, string> GetModList() public Dictionary<string, string> GetModList()
=> _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text); => _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text);
@ -109,6 +108,18 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
public event Action<string>? ModAdded; public event Action<string>? ModAdded;
public event Action<string, string>? ModMoved; public event Action<string, string>? ModMoved;
public event Action<JObject, ushort>? CreatingPcp
{
add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi);
remove => _communicator.PcpCreation.Unsubscribe(value!);
}
public event Action<JObject, string, Guid>? ParsingPcp
{
add => _communicator.PcpParsing.Subscribe(value!, PcpParsing.Priority.ModsApi);
remove => _communicator.PcpParsing.Unsubscribe(value!);
}
public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName) public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName)
{ {
if (!_modManager.TryGetMod(modDirectory, modName, out var mod) if (!_modManager.TryGetMod(modDirectory, modName, out var mod)

View file

@ -17,7 +17,7 @@ public class PenumbraApi(
UiApi ui) : IDisposable, IApiService, IPenumbraApi UiApi ui) : IDisposable, IApiService, IPenumbraApi
{ {
public const int BreakingVersion = 5; public const int BreakingVersion = 5;
public const int FeatureVersion = 10; public const int FeatureVersion = 11;
public void Dispose() public void Dispose()
{ {

View file

@ -54,6 +54,8 @@ public sealed class IpcProviders : IDisposable, IApiService
IpcSubscribers.ModDeleted.Provider(pi, api.Mods), IpcSubscribers.ModDeleted.Provider(pi, api.Mods),
IpcSubscribers.ModAdded.Provider(pi, api.Mods), IpcSubscribers.ModAdded.Provider(pi, api.Mods),
IpcSubscribers.ModMoved.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.GetModPath.Provider(pi, api.Mods),
IpcSubscribers.SetModPath.Provider(pi, api.Mods), IpcSubscribers.SetModPath.Provider(pi, api.Mods),
IpcSubscribers.GetChangedItems.Provider(pi, api.Mods), IpcSubscribers.GetChangedItems.Provider(pi, api.Mods),

View file

@ -0,0 +1,20 @@
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
namespace Penumbra.Communication;
/// <summary>
/// Triggered when the character.json file for a .pcp file is written.
/// <list type="number">
/// <item>Parameter is the JObject that gets written to file. </item>
/// <item>Parameter is the object index of the game object this is written for. </item>
/// </list>
/// </summary>
public sealed class PcpCreation() : EventWrapper<JObject, ushort, PcpCreation.Priority>(nameof(PcpCreation))
{
public enum Priority
{
/// <seealso cref="Api.Api.ModsApi"/>
ModsApi = int.MinValue,
}
}

View file

@ -0,0 +1,21 @@
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
namespace Penumbra.Communication;
/// <summary>
/// Triggered when the character.json file for a .pcp file is parsed and applied.
/// <list type="number">
/// <item>Parameter is parsed JObject that contains the data. </item>
/// <item>Parameter is the identifier of the created mod. </item>
/// <item>Parameter is the GUID of the created collection. </item>
/// </list>
/// </summary>
public sealed class PcpParsing() : EventWrapper<JObject, string, Guid, PcpParsing.Priority>(nameof(PcpParsing))
{
public enum Priority
{
/// <seealso cref="Api.Api.ModsApi"/>
ModsApi = int.MinValue,
}
}

View file

@ -69,6 +69,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService
public bool DefaultTemporaryMode { get; set; } = false; public bool DefaultTemporaryMode { get; set; } = false;
public bool EnableCustomShapes { get; set; } = true; public bool EnableCustomShapes { get; set; } = true;
public bool DisablePcpHandling { get; set; } = false; public bool DisablePcpHandling { get; set; } = false;
public bool AllowPcpIpc { get; set; } = true;
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
public ChangedItemMode ChangedItemDisplay { get; set; } = ChangedItemMode.GroupedCollapsed; public ChangedItemMode ChangedItemDisplay { get; set; } = ChangedItemMode.GroupedCollapsed;
public int OptionGroupCollapsibleMin { get; set; } = 5; public int OptionGroupCollapsibleMin { get; set; } = 5;

View file

@ -81,6 +81,12 @@ public class CommunicatorService : IDisposable, IService
/// <inheritdoc cref="Communication.ResolvedFileChanged"/> /// <inheritdoc cref="Communication.ResolvedFileChanged"/>
public readonly ResolvedFileChanged ResolvedFileChanged = new(); public readonly ResolvedFileChanged ResolvedFileChanged = new();
/// <inheritdoc cref="Communication.PcpCreation"/>
public readonly PcpCreation PcpCreation = new();
/// <inheritdoc cref="Communication.PcpParsing"/>
public readonly PcpParsing PcpParsing = new();
public void Dispose() public void Dispose()
{ {
CollectionChange.Dispose(); CollectionChange.Dispose();
@ -105,5 +111,7 @@ public class CommunicatorService : IDisposable, IService
ChangedItemClick.Dispose(); ChangedItemClick.Dispose();
SelectTab.Dispose(); SelectTab.Dispose();
ResolvedFileChanged.Dispose(); ResolvedFileChanged.Dispose();
PcpCreation.Dispose();
PcpParsing.Dispose();
} }
} }

View file

@ -1,4 +1,3 @@
using System.Buffers.Text;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -76,9 +75,16 @@ public class PcpService : IApiService, IDisposable
try try
{ {
var file = Path.Combine(newDirectory.FullName, "collection.json"); var file = Path.Combine(newDirectory.FullName, "character.json");
if (!File.Exists(file)) 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 text = File.ReadAllText(file);
var jObj = JObject.Parse(text); var jObj = JObject.Parse(text);
@ -110,10 +116,12 @@ public class PcpService : IApiService, IDisposable
// ignored. // ignored.
} }
} }
if (_config.AllowPcpIpc)
_communicator.PcpParsing.Invoke(jObj, mod.Identifier, collection.Identity.Id);
} }
catch (Exception ex) 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 time = DateTime.Now;
var modDirectory = CreateMod(identifier, note, time); var modDirectory = CreateMod(identifier, note, time);
await CreateDefaultMod(modDirectory, collection.ModCollection, tree, cancel); 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); var file = ZipUp(modDirectory);
return (true, file); return (true, file);
} }
@ -163,7 +171,7 @@ public class PcpService : IApiService, IDisposable
return fileName; 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) CancellationToken cancel = default)
{ {
var jObj = new JObject var jObj = new JObject
@ -176,7 +184,9 @@ public class PcpService : IApiService, IDisposable
}; };
if (note.Length > 0) if (note.Length > 0)
cancel.ThrowIfCancellationRequested(); 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 file = File.Open(filePath, File.Exists(filePath) ? FileMode.Truncate : FileMode.CreateNew);
await using var stream = new StreamWriter(file); await using var stream = new StreamWriter(file);
await using var json = new JsonTextWriter(stream); await using var json = new JsonTextWriter(stream);