Add collection PCP settings.
Some checks are pending
.NET Build / build (push) Waiting to run

This commit is contained in:
Ottermandias 2025-08-23 14:46:19 +02:00
parent d302a17f1f
commit 6079103505
6 changed files with 61 additions and 35 deletions

@ -1 +1 @@
Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 Subproject commit 297941bc22300f4a8368f4d0177f62943eb69727

View file

@ -108,7 +108,7 @@ 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 public event Action<JObject, ushort, string>? CreatingPcp
{ {
add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi); add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi);
remove => _communicator.PcpCreation.Unsubscribe(value!); remove => _communicator.PcpCreation.Unsubscribe(value!);

View file

@ -8,9 +8,10 @@ namespace Penumbra.Communication;
/// <list type="number"> /// <list type="number">
/// <item>Parameter is the JObject that gets written to file. </item> /// <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> /// <item>Parameter is the object index of the game object this is written for. </item>
/// <item>Parameter is the full path to the directory being set up for the PCP creation. </item>
/// </list> /// </list>
/// </summary> /// </summary>
public sealed class PcpCreation() : EventWrapper<JObject, ushort, PcpCreation.Priority>(nameof(PcpCreation)) public sealed class PcpCreation() : EventWrapper<JObject, ushort, string, PcpCreation.Priority>(nameof(PcpCreation))
{ {
public enum Priority public enum Priority
{ {

View file

@ -18,6 +18,15 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
namespace Penumbra; namespace Penumbra;
public record PcpSettings
{
public bool CreateCollection { get; set; } = true;
public bool AssignCollection { get; set; } = true;
public bool AllowIpc { get; set; } = true;
public bool DisableHandling { get; set; } = false;
public string FolderName { get; set; } = "PCP";
}
[Serializable] [Serializable]
public class Configuration : IPluginConfiguration, ISavable, IService public class Configuration : IPluginConfiguration, ISavable, IService
{ {
@ -68,11 +77,10 @@ public class Configuration : IPluginConfiguration, ISavable, IService
public bool HideMachinistOffhandFromChangedItems { get; set; } = true; public bool HideMachinistOffhandFromChangedItems { get; set; } = true;
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 PcpSettings PcpSettings = new();
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;
public Vector2 MinimumSize = new(Constants.MinimumSizeX, Constants.MinimumSizeY); public Vector2 MinimumSize = new(Constants.MinimumSizeX, Constants.MinimumSizeY);
@ -90,7 +98,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService
public bool OpenFoldersByDefault { get; set; } = false; public bool OpenFoldersByDefault { get; set; } = false;
public int SingleGroupRadioMax { get; set; } = 2; public int SingleGroupRadioMax { get; set; } = 2;
public string DefaultImportFolder { get; set; } = string.Empty; public string DefaultImportFolder { get; set; } = string.Empty;
public string PcpFolderName { get; set; } = "PCP";
public string QuickMoveFolder1 { get; set; } = string.Empty; public string QuickMoveFolder1 { get; set; } = string.Empty;
public string QuickMoveFolder2 { get; set; } = string.Empty; public string QuickMoveFolder2 { get; set; } = string.Empty;
public string QuickMoveFolder3 { get; set; } = string.Empty; public string QuickMoveFolder3 { get; set; } = string.Empty;

View file

@ -89,7 +89,7 @@ public class PcpService : IApiService, IDisposable
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory) private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory)
{ {
if (type is not ModPathChangeType.Added || _config.DisablePcpHandling || newDirectory is null) if (type is not ModPathChangeType.Added || _config.PcpSettings.DisableHandling || newDirectory is null)
return; return;
try try
@ -107,29 +107,37 @@ public class PcpService : IApiService, IDisposable
} }
Penumbra.Log.Information($"[PCPService] Found a PCP file for {mod.Name}, applying."); Penumbra.Log.Information($"[PCPService] Found a PCP file for {mod.Name}, applying.");
var text = File.ReadAllText(file); var text = File.ReadAllText(file);
var jObj = JObject.Parse(text); var jObj = JObject.Parse(text);
var identifier = _actors.FromJson(jObj["Actor"] as JObject); var collection = ModCollection.Empty;
if (!identifier.IsValid) // Create collection.
return; if (_config.PcpSettings.CreateCollection)
{
var identifier = _actors.FromJson(jObj["Actor"] as JObject);
if (identifier.IsValid && jObj["Collection"]?.ToObject<string>() is { } collectionName)
{
var name = $"PCP/{collectionName}";
if (_collections.Storage.AddCollection(name, null))
{
collection = _collections.Storage[^1];
_collections.Editor.SetModState(collection, mod, true);
if (jObj["Collection"]?.ToObject<string>() is not { } collectionName) // Assign collection.
return; if (_config.PcpSettings.AssignCollection)
{
var identifierGroup = _collections.Active.Individuals.GetGroup(identifier);
_collections.Active.SetCollection(collection, CollectionType.Individual, identifierGroup);
}
}
}
}
var name = $"PCP/{collectionName}"; // Move to folder.
if (!_collections.Storage.AddCollection(name, null))
return;
var collection = _collections.Storage[^1];
_collections.Editor.SetModState(collection, mod, true);
var identifierGroup = _collections.Active.Individuals.GetGroup(identifier);
_collections.Active.SetCollection(collection, CollectionType.Individual, identifierGroup);
if (_fileSystem.TryGetValue(mod, out var leaf)) if (_fileSystem.TryGetValue(mod, out var leaf))
{ {
try try
{ {
var folder = _fileSystem.FindOrCreateAllFolders(_config.PcpFolderName); var folder = _fileSystem.FindOrCreateAllFolders(_config.PcpSettings.FolderName);
_fileSystem.Move(leaf, folder); _fileSystem.Move(leaf, folder);
} }
catch catch
@ -138,7 +146,8 @@ public class PcpService : IApiService, IDisposable
} }
} }
if (_config.AllowPcpIpc) // Invoke IPC.
if (_config.PcpSettings.AllowIpc)
_communicator.PcpParsing.Invoke(jObj, mod.Identifier, collection.Identity.Id); _communicator.PcpParsing.Invoke(jObj, mod.Identifier, collection.Identity.Id);
} }
catch (Exception ex) catch (Exception ex)
@ -208,8 +217,8 @@ public class PcpService : IApiService, IDisposable
}; };
if (note.Length > 0) if (note.Length > 0)
cancel.ThrowIfCancellationRequested(); cancel.ThrowIfCancellationRequested();
if (_config.AllowPcpIpc) if (_config.PcpSettings.AllowIpc)
await _framework.Framework.RunOnFrameworkThread(() => _communicator.PcpCreation.Invoke(jObj, index.Index)); await _framework.Framework.RunOnFrameworkThread(() => _communicator.PcpCreation.Invoke(jObj, index.Index, directory.FullName));
var filePath = Path.Combine(directory.FullName, "character.json"); 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);

View file

@ -602,7 +602,7 @@ public class SettingsTab : ITab, IUiService
_config.AlwaysOpenDefaultImport, v => _config.AlwaysOpenDefaultImport = v); _config.AlwaysOpenDefaultImport, v => _config.AlwaysOpenDefaultImport = v);
Checkbox("Handle PCP Files", Checkbox("Handle PCP Files",
"When encountering specific mods, usually but not necessarily denoted by a .pcp file ending, Penumbra will automatically try to create an associated collection and assign it to a specific character for this mod package. This can turn this behaviour off if unwanted.", "When encountering specific mods, usually but not necessarily denoted by a .pcp file ending, Penumbra will automatically try to create an associated collection and assign it to a specific character for this mod package. This can turn this behaviour off if unwanted.",
!_config.DisablePcpHandling, v => _config.DisablePcpHandling = !v); !_config.PcpSettings.DisableHandling, v => _config.PcpSettings.DisableHandling = !v);
var active = _config.DeleteModModifier.IsActive(); var active = _config.DeleteModModifier.IsActive();
ImGui.SameLine(); ImGui.SameLine();
@ -612,14 +612,23 @@ public class SettingsTab : ITab, IUiService
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking."); ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking.");
ImGui.SameLine(); ImGui.SameLine();
if (ImUtf8.ButtonEx("Delete all PCP Collections"u8, "Deletes all collections whose name starts with 'PCP/' from the collection list."u8, disabled: !active)) if (ImUtf8.ButtonEx("Delete all PCP Collections"u8, "Deletes all collections whose name starts with 'PCP/' from the collection list."u8,
disabled: !active))
_pcpService.CleanPcpCollections(); _pcpService.CleanPcpCollections();
if (!active) if (!active)
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking."); ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking.");
Checkbox("Allow Other Plugins Access to PCP Handling", Checkbox("Allow Other Plugins Access to PCP Handling",
"When creating or importing PCP files, other plugins can add and interpret their own data to the character.json file.", "When creating or importing PCP files, other plugins can add and interpret their own data to the character.json file.",
_config.AllowPcpIpc, v => _config.AllowPcpIpc = v); _config.PcpSettings.AllowIpc, v => _config.PcpSettings.AllowIpc = v);
Checkbox("Create PCP Collections",
"When importing PCP files, create the associated collection.",
_config.PcpSettings.CreateCollection, v => _config.PcpSettings.CreateCollection = v);
Checkbox("Assign PCP Collections",
"When importing PCP files and creating the associated collection, assign it to the associated character.",
_config.PcpSettings.AssignCollection, v => _config.PcpSettings.AssignCollection = v);
DrawDefaultModImportPath(); DrawDefaultModImportPath();
DrawDefaultModAuthor(); DrawDefaultModAuthor();
DrawDefaultModImportFolder(); DrawDefaultModImportFolder();
@ -736,10 +745,10 @@ public class SettingsTab : ITab, IUiService
/// <summary> Draw input for the default folder to sort put newly imported mods into. </summary> /// <summary> Draw input for the default folder to sort put newly imported mods into. </summary>
private void DrawPcpFolder() private void DrawPcpFolder()
{ {
var tmp = _config.PcpFolderName; var tmp = _config.PcpSettings.FolderName;
ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X);
if (ImUtf8.InputText("##pcpFolder"u8, ref tmp)) if (ImUtf8.InputText("##pcpFolder"u8, ref tmp))
_config.PcpFolderName = tmp; _config.PcpSettings.FolderName = tmp;
if (ImGui.IsItemDeactivatedAfterEdit()) if (ImGui.IsItemDeactivatedAfterEdit())
_config.Save(); _config.Save();