mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-02 14:04:37 +01:00
Merge branch 'TempSettings'
This commit is contained in:
commit
a5d8baebca
68 changed files with 1263 additions and 626 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit d9caded5efb7c9db0a273a43bb5f6d53cf4ace7f
|
||||
Subproject commit fcc96daa02633f673325c14aeea6b6b568924f1e
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 97e9f427406f82a59ddef764b44ecea654a51623
|
||||
Subproject commit 882b778e78bb0806dd7d38e8b3670ff138a84a31
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit dd83f97299ac33cfacb1064bde4f4d1f6a260936
|
||||
Subproject commit 0647fbc5017ef9ced3f3ce1c2496eefd57c5b7a8
|
||||
|
|
@ -8,7 +8,7 @@ namespace Penumbra.Api.Api;
|
|||
public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : IPenumbraApiCollection, IApiService
|
||||
{
|
||||
public Dictionary<Guid, string> GetCollections()
|
||||
=> collections.Storage.ToDictionary(c => c.Id, c => c.Name);
|
||||
=> collections.Storage.ToDictionary(c => c.Identity.Id, c => c.Identity.Name);
|
||||
|
||||
public List<(Guid Id, string Name)> GetCollectionsByIdentifier(string identifier)
|
||||
{
|
||||
|
|
@ -17,14 +17,14 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
|
||||
var list = new List<(Guid Id, string Name)>(4);
|
||||
if (Guid.TryParse(identifier, out var guid) && collections.Storage.ById(guid, out var collection) && collection != ModCollection.Empty)
|
||||
list.Add((collection.Id, collection.Name));
|
||||
list.Add((collection.Identity.Id, collection.Identity.Name));
|
||||
else if (identifier.Length >= 8)
|
||||
list.AddRange(collections.Storage.Where(c => c.Identifier.StartsWith(identifier, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(c => (c.Id, c.Name)));
|
||||
list.AddRange(collections.Storage.Where(c => c.Identity.Identifier.StartsWith(identifier, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(c => (c.Identity.Id, c.Identity.Name)));
|
||||
|
||||
list.AddRange(collections.Storage
|
||||
.Where(c => string.Equals(c.Name, identifier, StringComparison.OrdinalIgnoreCase) && !list.Contains((c.Id, c.Name)))
|
||||
.Select(c => (c.Id, c.Name)));
|
||||
.Where(c => string.Equals(c.Identity.Name, identifier, StringComparison.OrdinalIgnoreCase) && !list.Contains((c.Identity.Id, c.Identity.Name)))
|
||||
.Select(c => (c.Identity.Id, c.Identity.Name)));
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
return null;
|
||||
|
||||
var collection = collections.Active.ByType((CollectionType)type);
|
||||
return collection == null ? null : (collection.Id, collection.Name);
|
||||
return collection == null ? null : (collection.Identity.Id, collection.Identity.Name);
|
||||
}
|
||||
|
||||
internal (Guid Id, string Name)? GetCollection(byte type)
|
||||
|
|
@ -64,17 +64,17 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
{
|
||||
var id = helpers.AssociatedIdentifier(gameObjectIdx);
|
||||
if (!id.IsValid)
|
||||
return (false, false, (collections.Active.Default.Id, collections.Active.Default.Name));
|
||||
return (false, false, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name));
|
||||
|
||||
if (collections.Active.Individuals.TryGetValue(id, out var collection))
|
||||
return (true, true, (collection.Id, collection.Name));
|
||||
return (true, true, (collection.Identity.Id, collection.Identity.Name));
|
||||
|
||||
helpers.AssociatedCollection(gameObjectIdx, out collection);
|
||||
return (true, false, (collection.Id, collection.Name));
|
||||
return (true, false, (collection.Identity.Id, collection.Identity.Name));
|
||||
}
|
||||
|
||||
public Guid[] GetCollectionByName(string name)
|
||||
=> collections.Storage.Where(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Id).ToArray();
|
||||
=> collections.Storage.Where(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Identity.Id).ToArray();
|
||||
|
||||
public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollection(ApiCollectionType type, Guid? collectionId,
|
||||
bool allowCreateNew, bool allowDelete)
|
||||
|
|
@ -83,7 +83,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
return (PenumbraApiEc.InvalidArgument, null);
|
||||
|
||||
var oldCollection = collections.Active.ByType((CollectionType)type);
|
||||
var old = oldCollection != null ? (oldCollection.Id, oldCollection.Name) : new ValueTuple<Guid, string>?();
|
||||
var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple<Guid, string>?();
|
||||
if (collectionId == null)
|
||||
{
|
||||
if (old == null)
|
||||
|
|
@ -106,7 +106,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
|
||||
collections.Active.CreateSpecialCollection((CollectionType)type);
|
||||
}
|
||||
else if (old.Value.Item1 == collection.Id)
|
||||
else if (old.Value.Item1 == collection.Identity.Id)
|
||||
{
|
||||
return (PenumbraApiEc.NothingChanged, old);
|
||||
}
|
||||
|
|
@ -120,10 +120,10 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
{
|
||||
var id = helpers.AssociatedIdentifier(gameObjectIdx);
|
||||
if (!id.IsValid)
|
||||
return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Id, collections.Active.Default.Name));
|
||||
return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name));
|
||||
|
||||
var oldCollection = collections.Active.Individuals.TryGetValue(id, out var c) ? c : null;
|
||||
var old = oldCollection != null ? (oldCollection.Id, oldCollection.Name) : new ValueTuple<Guid, string>?();
|
||||
var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple<Guid, string>?();
|
||||
if (collectionId == null)
|
||||
{
|
||||
if (old == null)
|
||||
|
|
@ -148,7 +148,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) :
|
|||
var ids = collections.Active.Individuals.GetGroup(id);
|
||||
collections.Active.CreateIndividualCollection(ids);
|
||||
}
|
||||
else if (old.Value.Item1 == collection.Id)
|
||||
else if (old.Value.Item1 == collection.Identity.Id)
|
||||
{
|
||||
return (PenumbraApiEc.NothingChanged, old);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
|
|||
public unsafe (nint GameObject, (Guid Id, string Name) Collection) GetDrawObjectInfo(nint drawObject)
|
||||
{
|
||||
var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||
return (data.AssociatedGameObject, (data.ModCollection.Id, data.ModCollection.Name));
|
||||
return (data.AssociatedGameObject, (Id: data.ModCollection.Identity.Id, Name: data.ModCollection.Identity.Name));
|
||||
}
|
||||
|
||||
public int GetCutsceneParentIndex(int actorIdx)
|
||||
|
|
@ -93,5 +93,5 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
|
|||
}
|
||||
|
||||
private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject)
|
||||
=> CreatedCharacterBase?.Invoke(gameObject, collection.Id, drawObject);
|
||||
=> CreatedCharacterBase?.Invoke(gameObject, collection.Identity.Id, drawObject);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,11 +63,6 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
return new AvailableModSettings(dict);
|
||||
}
|
||||
|
||||
public Dictionary<string, (string[], int)>? GetAvailableModSettingsBase(string modDirectory, string modName)
|
||||
=> _modManager.TryGetMod(modDirectory, modName, out var mod)
|
||||
? mod.Groups.ToDictionary(g => g.Name, g => (g.Options.Select(o => o.Name).ToArray(), (int)g.Type))
|
||||
: null;
|
||||
|
||||
public (PenumbraApiEc, (bool, int, Dictionary<string, List<string>>, bool)?) GetCurrentModSettings(Guid collectionId, string modDirectory,
|
||||
string modName, bool ignoreInheritance)
|
||||
{
|
||||
|
|
@ -77,17 +72,17 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return (PenumbraApiEc.CollectionMissing, null);
|
||||
|
||||
var settings = collection.Id == Guid.Empty
|
||||
var settings = collection.Identity.Id == Guid.Empty
|
||||
? null
|
||||
: ignoreInheritance
|
||||
? collection.Settings[mod.Index]
|
||||
: collection[mod.Index].Settings;
|
||||
? collection.GetOwnSettings(mod.Index)
|
||||
: collection.GetInheritedSettings(mod.Index).Settings;
|
||||
if (settings == null)
|
||||
return (PenumbraApiEc.Success, null);
|
||||
|
||||
var (enabled, priority, dict) = settings.ConvertToShareable(mod);
|
||||
return (PenumbraApiEc.Success,
|
||||
(enabled, priority.Value, dict, collection.Settings[mod.Index] == null));
|
||||
(enabled, priority.Value, dict, collection.GetOwnSettings(mod.Index) is null));
|
||||
}
|
||||
|
||||
public PenumbraApiEc TryInheritMod(Guid collectionId, string modDirectory, string modName, bool inherit)
|
||||
|
|
@ -215,9 +210,9 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
private void TriggerSettingEdited(Mod mod)
|
||||
{
|
||||
var collection = _collectionResolver.PlayerCollection();
|
||||
var (settings, parent) = collection[mod.Index];
|
||||
var (settings, parent) = collection.GetActualSettings(mod.Index);
|
||||
if (settings is { Enabled: true })
|
||||
ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Id, mod.Identifier, parent != collection);
|
||||
ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Identity.Id, mod.Identifier, parent != collection);
|
||||
}
|
||||
|
||||
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
|
||||
|
|
@ -227,7 +222,7 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
}
|
||||
|
||||
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting _1, int _2, bool inherited)
|
||||
=> ModSettingChanged?.Invoke(type, collection.Id, mod?.ModPath.Name ?? string.Empty, inherited);
|
||||
=> ModSettingChanged?.Invoke(type, collection.Identity.Id, mod?.ModPath.Name ?? string.Empty, inherited);
|
||||
|
||||
private void OnModOptionEdited(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container,
|
||||
int moveIndex)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class PenumbraApi(
|
|||
}
|
||||
|
||||
public (int Breaking, int Feature) ApiVersion
|
||||
=> (5, 3);
|
||||
=> (5, 4);
|
||||
|
||||
public bool Valid { get; private set; } = true;
|
||||
public IPenumbraApiCollection Collection { get; } = collection;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Mods.Settings;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
|
|
@ -13,7 +16,9 @@ public class TemporaryApi(
|
|||
ObjectManager objects,
|
||||
ActorManager actors,
|
||||
CollectionManager collectionManager,
|
||||
TempModManager tempMods) : IPenumbraApiTemporary, IApiService
|
||||
TempModManager tempMods,
|
||||
ApiHelpers apiHelpers,
|
||||
ModManager modManager) : IPenumbraApiTemporary, IApiService
|
||||
{
|
||||
public Guid CreateTemporaryCollection(string name)
|
||||
=> tempCollections.CreateTemporaryCollection(name);
|
||||
|
|
@ -125,6 +130,139 @@ public class TemporaryApi(
|
|||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
|
||||
public PenumbraApiEc SetTemporaryModSettings(Guid collectionId, string modDirectory, string modName, bool inherit, bool enabled, int priority,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<string>> options, string source, int key)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit, "Enabled", enabled,
|
||||
"Priority", priority, "Options", options, "Source", source, "Key", key);
|
||||
if (!collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key);
|
||||
}
|
||||
|
||||
public PenumbraApiEc SetTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, bool inherit, bool enabled, int priority,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<string>> options, string source, int key)
|
||||
{
|
||||
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit, "Enabled", enabled,
|
||||
"Priority", priority, "Options", options, "Source", source, "Key", key);
|
||||
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key);
|
||||
}
|
||||
|
||||
private PenumbraApiEc SetTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName,
|
||||
bool inherit, bool enabled, int priority, IReadOnlyDictionary<string, IReadOnlyList<string>> options, string source, int key)
|
||||
{
|
||||
if (collection.Identity.Index <= 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.TemporarySettingImpossible, args);
|
||||
|
||||
if (!modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
if (!collectionManager.Editor.CanSetTemporarySettings(collection, mod, key))
|
||||
if (collection.GetTempSettings(mod.Index) is { Lock: > 0 } oldSettings && oldSettings.Lock != key)
|
||||
return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args);
|
||||
|
||||
var newSettings = new TemporaryModSettings()
|
||||
{
|
||||
ForceInherit = inherit,
|
||||
Enabled = enabled,
|
||||
Priority = new ModPriority(priority),
|
||||
Lock = key,
|
||||
Source = source,
|
||||
Settings = SettingList.Default(mod),
|
||||
};
|
||||
|
||||
|
||||
foreach (var (groupName, optionNames) in options)
|
||||
{
|
||||
var ec = ModSettingsApi.ConvertModSetting(mod, groupName, optionNames, out var groupIdx, out var setting);
|
||||
if (ec != PenumbraApiEc.Success)
|
||||
return ApiHelpers.Return(ec, args);
|
||||
|
||||
newSettings.Settings[groupIdx] = setting;
|
||||
}
|
||||
|
||||
if (collectionManager.Editor.SetTemporarySettings(collection, mod, newSettings, key))
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, args);
|
||||
|
||||
// This should not happen since all error cases had been checked before.
|
||||
return ApiHelpers.Return(PenumbraApiEc.UnknownError, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveTemporaryModSettings(Guid collectionId, string modDirectory, string modName, int key)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Key", key);
|
||||
if (!collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key);
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, int key)
|
||||
{
|
||||
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Key", key);
|
||||
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key);
|
||||
}
|
||||
|
||||
private PenumbraApiEc RemoveTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName, int key)
|
||||
{
|
||||
if (collection.Identity.Index <= 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
|
||||
|
||||
if (!modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
if (collection.GetTempSettings(mod.Index) is null)
|
||||
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
|
||||
|
||||
if (!collectionManager.Editor.SetTemporarySettings(collection, mod, null, key))
|
||||
return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args);
|
||||
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveAllTemporaryModSettings(Guid collectionId, int key)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "Key", key);
|
||||
if (!collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
return RemoveAllTemporaryModSettings(args, collection, key);
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveAllTemporaryModSettingsPlayer(int objectIndex, int key)
|
||||
{
|
||||
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "Key", key);
|
||||
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
return RemoveAllTemporaryModSettings(args, collection, key);
|
||||
}
|
||||
|
||||
private PenumbraApiEc RemoveAllTemporaryModSettings(in LazyString args, ModCollection collection, int key)
|
||||
{
|
||||
if (collection.Identity.Index <= 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
|
||||
|
||||
var numRemoved = 0;
|
||||
for (var i = 0; i < collection.Settings.Count; ++i)
|
||||
{
|
||||
if (collection.GetTempSettings(i) is {} tempSettings && tempSettings.Lock == key
|
||||
&& collectionManager.Editor.SetTemporarySettings(collection, modManager[i], null, key))
|
||||
++numRemoved;
|
||||
}
|
||||
|
||||
return ApiHelpers.Return(numRemoved > 0 ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, args);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert a dictionary of strings to a dictionary of game paths to full paths.
|
||||
/// Only returns true if all paths can successfully be converted and added.
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public sealed class IpcProviders : IDisposable, IApiService
|
|||
|
||||
IpcSubscribers.ApiVersion.Provider(pi, api),
|
||||
new FuncProvider<(int Major, int Minor)>(pi, "Penumbra.ApiVersions", () => api.ApiVersion), // backward compatibility
|
||||
new FuncProvider<int>(pi, "Penumbra.ApiVersion", () => api.ApiVersion.Breaking), // backward compatibility
|
||||
new FuncProvider<int>(pi, "Penumbra.ApiVersion", () => api.ApiVersion.Breaking), // backward compatibility
|
||||
IpcSubscribers.GetModDirectory.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.GetConfiguration.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
|
||||
|
|
@ -97,6 +97,12 @@ public sealed class IpcProviders : IDisposable, IApiService
|
|||
IpcSubscribers.AddTemporaryMod.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveTemporaryModAll.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveTemporaryMod.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.SetTemporaryModSettings.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.SetTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveTemporaryModSettings.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveAllTemporaryModSettings.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveAllTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
|
||||
|
||||
IpcSubscribers.ChangedItemTooltip.Provider(pi, api.Ui),
|
||||
IpcSubscribers.ChangedItemClicked.Provider(pi, api.Ui),
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public class TemporaryIpcTester(
|
|||
private string _tempCollectionName = string.Empty;
|
||||
private string _tempCollectionGuidName = string.Empty;
|
||||
private string _tempModName = string.Empty;
|
||||
private string _modDirectory = string.Empty;
|
||||
private string _tempGamePath = "test/game/path.mtrl";
|
||||
private string _tempFilePath = "test/success.mtrl";
|
||||
private string _tempManipulation = string.Empty;
|
||||
|
|
@ -50,6 +51,7 @@ public class TemporaryIpcTester(
|
|||
ImGuiUtil.GuidInput("##guid", "Collection GUID...", string.Empty, ref _tempGuid, ref _tempCollectionGuidName);
|
||||
ImGui.InputInt("##tempActorIndex", ref _tempActorIndex, 0, 0);
|
||||
ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32);
|
||||
ImGui.InputTextWithHint("##mod", "Existing Mod Name...", ref _modDirectory, 256);
|
||||
ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256);
|
||||
ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256);
|
||||
ImUtf8.InputText("##tempManip"u8, ref _tempManipulation, "Manipulation Base64 String..."u8);
|
||||
|
|
@ -121,6 +123,44 @@ public class TemporaryIpcTester(
|
|||
IpcTester.DrawIntro(RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections");
|
||||
if (ImGui.Button("Remove##ModAll"))
|
||||
_lastTempError = new RemoveTemporaryModAll(pi).Invoke(_tempModName, int.MaxValue);
|
||||
|
||||
IpcTester.DrawIntro(SetTemporaryModSettings.Label, "Set Temporary Mod Settings (to default) in specific Collection");
|
||||
if (ImUtf8.Button("Set##SetTemporary"u8))
|
||||
_lastTempError = new SetTemporaryModSettings(pi).Invoke(guid, _modDirectory, string.Empty, false, true, 1337, new Dictionary<string, IReadOnlyList<string>>(),
|
||||
"IPC Tester", 1337);
|
||||
|
||||
IpcTester.DrawIntro(SetTemporaryModSettingsPlayer.Label, "Set Temporary Mod Settings (to default) in game object collection");
|
||||
if (ImUtf8.Button("Set##SetTemporaryPlayer"u8))
|
||||
_lastTempError = new SetTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, string.Empty, false, true, 1337, new Dictionary<string, IReadOnlyList<string>>(),
|
||||
"IPC Tester", 1337);
|
||||
|
||||
IpcTester.DrawIntro(RemoveTemporaryModSettings.Label, "Remove Temporary Mod Settings from specific Collection");
|
||||
if (ImUtf8.Button("Remove##RemoveTemporary"u8))
|
||||
_lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, string.Empty, 1337);
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporary"u8))
|
||||
_lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, string.Empty, 1338);
|
||||
|
||||
IpcTester.DrawIntro(RemoveTemporaryModSettingsPlayer.Label, "Remove Temporary Mod Settings from game object Collection");
|
||||
if (ImUtf8.Button("Remove##RemoveTemporaryPlayer"u8))
|
||||
_lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, string.Empty, 1337);
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporaryPlayer"u8))
|
||||
_lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, string.Empty, 1338);
|
||||
|
||||
IpcTester.DrawIntro(RemoveAllTemporaryModSettings.Label, "Remove All Temporary Mod Settings from specific Collection");
|
||||
if (ImUtf8.Button("Remove##RemoveAllTemporary"u8))
|
||||
_lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1337);
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporary"u8))
|
||||
_lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1338);
|
||||
|
||||
IpcTester.DrawIntro(RemoveAllTemporaryModSettingsPlayer.Label, "Remove All Temporary Mod Settings from game object Collection");
|
||||
if (ImUtf8.Button("Remove##RemoveAllTemporaryPlayer"u8))
|
||||
_lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1337);
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporaryPlayer"u8))
|
||||
_lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1338);
|
||||
}
|
||||
|
||||
public void DrawCollections()
|
||||
|
|
@ -146,10 +186,10 @@ public class TemporaryIpcTester(
|
|||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.CopyOnClickSelectable(collection.Identifier);
|
||||
ImGuiUtil.CopyOnClickSelectable(collection.Identity.Identifier);
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn(collection.Name);
|
||||
ImGuiUtil.DrawTableColumn(collection.Identity.Name);
|
||||
ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
|
||||
ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
|
||||
ImGuiUtil.DrawTableColumn(string.Join(", ",
|
||||
|
|
@ -199,7 +239,7 @@ public class TemporaryIpcTester(
|
|||
{
|
||||
PrintList("All", tempMods.ModsForAllCollections);
|
||||
foreach (var (collection, list) in tempMods.Mods)
|
||||
PrintList(collection.Name, list);
|
||||
PrintList(collection.Identity.Name, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,13 +85,13 @@ public class TempModManager : IDisposable, IService
|
|||
{
|
||||
if (removed)
|
||||
{
|
||||
Penumbra.Log.Verbose($"Removing temporary Mod {mod.Name} from {collection.AnonymizedName}.");
|
||||
Penumbra.Log.Verbose($"Removing temporary Mod {mod.Name} from {collection.Identity.AnonymizedName}.");
|
||||
collection.Remove(mod);
|
||||
_communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.False, 0, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Log.Verbose($"Adding {(created ? "new " : string.Empty)}temporary Mod {mod.Name} to {collection.AnonymizedName}.");
|
||||
Penumbra.Log.Verbose($"Adding {(created ? "new " : string.Empty)}temporary Mod {mod.Name} to {collection.Identity.AnonymizedName}.");
|
||||
collection.Apply(mod, created);
|
||||
_communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.True, 0, false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Collections.Cache;
|
||||
|
|
@ -37,7 +36,7 @@ public sealed class AtchCache(MetaFileManager manager, ModCollection collection)
|
|||
|
||||
protected override void ApplyModInternal(AtchIdentifier identifier, AtchEntry entry)
|
||||
{
|
||||
++Collection.AtchChangeCounter;
|
||||
Collection.Counters.IncrementAtch();
|
||||
ApplyFile(identifier, entry);
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ public sealed class AtchCache(MetaFileManager manager, ModCollection collection)
|
|||
|
||||
protected override void RevertModInternal(AtchIdentifier identifier)
|
||||
{
|
||||
++Collection.AtchChangeCounter;
|
||||
Collection.Counters.IncrementAtch();
|
||||
if (!_atchFiles.TryGetValue(identifier.GenderRace, out var pair))
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public sealed class CollectionCache : IDisposable
|
|||
public int Calculating = -1;
|
||||
|
||||
public string AnonymizedName
|
||||
=> _collection.AnonymizedName;
|
||||
=> _collection.Identity.AnonymizedName;
|
||||
|
||||
public IEnumerable<SingleArray<ModConflicts>> AllConflicts
|
||||
=> ConflictDict.Values;
|
||||
|
|
@ -177,7 +177,7 @@ public sealed class CollectionCache : IDisposable
|
|||
var (paths, manipulations) = ModData.RemoveMod(mod);
|
||||
|
||||
if (addMetaChanges)
|
||||
_collection.IncrementCounter();
|
||||
_collection.Counters.IncrementChange();
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
|
|
@ -250,7 +250,7 @@ public sealed class CollectionCache : IDisposable
|
|||
|
||||
if (addMetaChanges)
|
||||
{
|
||||
_collection.IncrementCounter();
|
||||
_collection.Counters.IncrementChange();
|
||||
_manager.MetaFileManager.ApplyDefaultFiles(_collection);
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ public sealed class CollectionCache : IDisposable
|
|||
if (mod.Index < 0)
|
||||
return mod.GetData();
|
||||
|
||||
var settings = _collection[mod.Index].Settings;
|
||||
var settings = _collection.GetActualSettings(mod.Index).Settings;
|
||||
return settings is not { Enabled: true }
|
||||
? AppliedModData.Empty
|
||||
: mod.GetData(settings);
|
||||
|
|
@ -342,8 +342,8 @@ public sealed class CollectionCache : IDisposable
|
|||
// Returns if the added mod takes priority before the existing mod.
|
||||
private bool AddConflict(object data, IMod addedMod, IMod existingMod)
|
||||
{
|
||||
var addedPriority = addedMod.Index >= 0 ? _collection[addedMod.Index].Settings!.Priority : addedMod.Priority;
|
||||
var existingPriority = existingMod.Index >= 0 ? _collection[existingMod.Index].Settings!.Priority : existingMod.Priority;
|
||||
var addedPriority = addedMod.Index >= 0 ? _collection.GetActualSettings(addedMod.Index).Settings!.Priority : addedMod.Priority;
|
||||
var existingPriority = existingMod.Index >= 0 ? _collection.GetActualSettings(existingMod.Index).Settings!.Priority : existingMod.Priority;
|
||||
|
||||
if (existingPriority < addedPriority)
|
||||
{
|
||||
|
|
@ -408,12 +408,12 @@ public sealed class CollectionCache : IDisposable
|
|||
// Identify and record all manipulated objects for this entire collection.
|
||||
private void SetChangedItems()
|
||||
{
|
||||
if (_changedItemsSaveCounter == _collection.ChangeCounter)
|
||||
if (_changedItemsSaveCounter == _collection.Counters.Change)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_changedItemsSaveCounter = _collection.ChangeCounter;
|
||||
_changedItemsSaveCounter = _collection.Counters.Change;
|
||||
_changedItems.Clear();
|
||||
// Skip IMCs because they would result in far too many false-positive items,
|
||||
// since they are per set instead of per item-slot/item/variant.
|
||||
|
|
|
|||
|
|
@ -114,16 +114,16 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
/// <summary> Only creates a new cache, does not update an existing one. </summary>
|
||||
public bool CreateCache(ModCollection collection)
|
||||
{
|
||||
if (collection.Index == ModCollection.Empty.Index)
|
||||
if (collection.Identity.Index == ModCollection.Empty.Identity.Index)
|
||||
return false;
|
||||
|
||||
if (collection._cache != null)
|
||||
return false;
|
||||
|
||||
collection._cache = new CollectionCache(this, collection);
|
||||
if (collection.Index > 0)
|
||||
if (collection.Identity.Index > 0)
|
||||
Interlocked.Increment(ref _count);
|
||||
Penumbra.Log.Verbose($"Created new cache for collection {collection.AnonymizedName}.");
|
||||
Penumbra.Log.Verbose($"Created new cache for collection {collection.Identity.AnonymizedName}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -132,32 +132,32 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
/// Does not create caches.
|
||||
/// </summary>
|
||||
public void CalculateEffectiveFileList(ModCollection collection)
|
||||
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identifier,
|
||||
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identity.Identifier,
|
||||
() => CalculateEffectiveFileListInternal(collection));
|
||||
|
||||
private void CalculateEffectiveFileListInternal(ModCollection collection)
|
||||
{
|
||||
// Skip the empty collection.
|
||||
if (collection.Index == 0)
|
||||
if (collection.Identity.Index == 0)
|
||||
return;
|
||||
|
||||
Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}");
|
||||
Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName}");
|
||||
if (!collection.HasCache)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists.");
|
||||
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, no cache exists.");
|
||||
}
|
||||
else if (collection._cache!.Calculating != -1)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}].");
|
||||
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}].");
|
||||
}
|
||||
else
|
||||
{
|
||||
FullRecalculation(collection);
|
||||
|
||||
Penumbra.Log.Debug(
|
||||
$"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished.");
|
||||
$"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.Identity.AnonymizedName} finished.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
foreach (var mod in _modStorage)
|
||||
cache.AddModSync(mod, false);
|
||||
|
||||
collection.IncrementCounter();
|
||||
collection.Counters.IncrementChange();
|
||||
|
||||
MetaFileManager.ApplyDefaultFiles(collection);
|
||||
ResolvedFileChanged.Invoke(collection, ResolvedFileChanged.Type.FullRecomputeFinished, Utf8GamePath.Empty, FullPath.Empty,
|
||||
|
|
@ -213,7 +213,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
else
|
||||
{
|
||||
RemoveCache(old);
|
||||
if (type is not CollectionType.Inactive && newCollection != null && newCollection.Index != 0 && CreateCache(newCollection))
|
||||
if (type is not CollectionType.Inactive && newCollection != null && newCollection.Identity.Index != 0 && CreateCache(newCollection))
|
||||
CalculateEffectiveFileList(newCollection);
|
||||
|
||||
if (type is CollectionType.Default)
|
||||
|
|
@ -231,11 +231,11 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
{
|
||||
case ModPathChangeType.Deleted:
|
||||
case ModPathChangeType.StartingReload:
|
||||
foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
||||
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
|
||||
collection._cache!.RemoveMod(mod, true);
|
||||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
||||
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
|
||||
collection._cache!.ReloadMod(mod, true);
|
||||
break;
|
||||
}
|
||||
|
|
@ -246,7 +246,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded))
|
||||
return;
|
||||
|
||||
foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
|
||||
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
|
||||
collection._cache!.AddMod(mod, true);
|
||||
}
|
||||
|
||||
|
|
@ -258,12 +258,12 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
private void RemoveCache(ModCollection? collection)
|
||||
{
|
||||
if (collection != null
|
||||
&& collection.Index > ModCollection.Empty.Index
|
||||
&& collection.Index != _active.Default.Index
|
||||
&& collection.Index != _active.Interface.Index
|
||||
&& collection.Index != _active.Current.Index
|
||||
&& _active.SpecialAssignments.All(c => c.Value.Index != collection.Index)
|
||||
&& _active.Individuals.All(c => c.Collection.Index != collection.Index))
|
||||
&& collection.Identity.Index > ModCollection.Empty.Identity.Index
|
||||
&& collection.Identity.Index != _active.Default.Identity.Index
|
||||
&& collection.Identity.Index != _active.Interface.Identity.Index
|
||||
&& collection.Identity.Index != _active.Current.Identity.Index
|
||||
&& _active.SpecialAssignments.All(c => c.Value.Identity.Index != collection.Identity.Index)
|
||||
&& _active.Individuals.All(c => c.Collection.Identity.Index != collection.Identity.Index))
|
||||
ClearCache(collection);
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
{
|
||||
if (type is ModOptionChangeType.PrepareChange)
|
||||
{
|
||||
foreach (var collection in _storage.Where(collection => collection.HasCache && collection[mod.Index].Settings is { Enabled: true }))
|
||||
foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true }))
|
||||
collection._cache!.RemoveMod(mod, false);
|
||||
|
||||
return;
|
||||
|
|
@ -284,7 +284,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
if (!recomputeList)
|
||||
return;
|
||||
|
||||
foreach (var collection in _storage.Where(collection => collection.HasCache && collection[mod.Index].Settings is { Enabled: true }))
|
||||
foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true }))
|
||||
{
|
||||
if (justAdd)
|
||||
collection._cache!.AddMod(mod, true);
|
||||
|
|
@ -297,7 +297,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
private void IncrementCounters()
|
||||
{
|
||||
foreach (var collection in _storage.Where(c => c.HasCache))
|
||||
collection.IncrementCounter();
|
||||
collection.Counters.IncrementChange();
|
||||
MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters;
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +317,7 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
cache.AddMod(mod!, true);
|
||||
else if (oldValue == Setting.True)
|
||||
cache.RemoveMod(mod!, true);
|
||||
else if (collection[mod!.Index].Settings?.Enabled == true)
|
||||
else if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true)
|
||||
cache.ReloadMod(mod!, true);
|
||||
else
|
||||
cache.RemoveMod(mod!, true);
|
||||
|
|
@ -329,10 +329,13 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
|
||||
break;
|
||||
case ModSettingChange.Setting:
|
||||
if (collection[mod!.Index].Settings?.Enabled == true)
|
||||
cache.ReloadMod(mod!, true);
|
||||
if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true)
|
||||
cache.ReloadMod(mod, true);
|
||||
|
||||
break;
|
||||
case ModSettingChange.TemporarySetting:
|
||||
cache.ReloadMod(mod!, true);
|
||||
break;
|
||||
case ModSettingChange.MultiInheritance:
|
||||
case ModSettingChange.MultiEnableState:
|
||||
FullRecalculation(collection);
|
||||
|
|
@ -359,9 +362,9 @@ public class CollectionCacheManager : IDisposable, IService
|
|||
|
||||
collection._cache!.Dispose();
|
||||
collection._cache = null;
|
||||
if (collection.Index > 0)
|
||||
if (collection.Identity.Index > 0)
|
||||
Interlocked.Decrement(ref _count);
|
||||
Penumbra.Log.Verbose($"Cleared cache of collection {collection.AnonymizedName}.");
|
||||
Penumbra.Log.Verbose($"Cleared cache of collection {collection.Identity.AnonymizedName}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection)
|
|||
|
||||
protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry)
|
||||
{
|
||||
++Collection.ImcChangeCounter;
|
||||
Collection.Counters.IncrementImc();
|
||||
ApplyFile(identifier, entry);
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection)
|
|||
|
||||
protected override void RevertModInternal(ImcIdentifier identifier)
|
||||
{
|
||||
++Collection.ImcChangeCounter;
|
||||
Collection.Counters.IncrementImc();
|
||||
var path = identifier.GamePath().Path;
|
||||
if (!_imcFiles.TryGetValue(path, out var pair))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public sealed class CollectionAutoSelector : IService, IDisposable
|
|||
return;
|
||||
|
||||
var collection = _resolver.PlayerCollection();
|
||||
Penumbra.Log.Debug($"Setting current collection to {collection.Identifier} through automatic collection selection.");
|
||||
Penumbra.Log.Debug($"Setting current collection to {collection.Identity.Identifier} through automatic collection selection.");
|
||||
_collections.SetCollection(collection, CollectionType.Current);
|
||||
}
|
||||
|
||||
|
|
|
|||
28
Penumbra/Collections/CollectionCounters.cs
Normal file
28
Penumbra/Collections/CollectionCounters.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
namespace Penumbra.Collections;
|
||||
|
||||
public struct CollectionCounters(int changeCounter)
|
||||
{
|
||||
/// <summary> Count the number of changes of the effective file list. </summary>
|
||||
public int Change { get; private set; } = changeCounter;
|
||||
|
||||
/// <summary> Count the number of IMC-relevant changes of the effective file list. </summary>
|
||||
public int Imc { get; private set; }
|
||||
|
||||
/// <summary> Count the number of ATCH-relevant changes of the effective file list. </summary>
|
||||
public int Atch { get; private set; }
|
||||
|
||||
/// <summary> Increment the number of changes in the effective file list. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int IncrementChange()
|
||||
=> ++Change;
|
||||
|
||||
/// <summary> Increment the number of IMC-relevant changes in the effective file list. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int IncrementImc()
|
||||
=> ++Imc;
|
||||
|
||||
/// <summary> Increment the number of ATCH-relevant changes in the effective file list. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int IncrementAtch()
|
||||
=> ++Imc;
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public static class ActiveCollectionMigration
|
|||
if (!storage.ByName(collectionName, out var collection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning);
|
||||
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Identity.Name}.", NotificationType.Warning);
|
||||
dict.Add(player, ModCollection.Empty);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
_ => null,
|
||||
};
|
||||
|
||||
if (oldCollection == null || collection == oldCollection || collection.Index >= _storage.Count)
|
||||
if (oldCollection == null || collection == oldCollection || collection.Identity.Index >= _storage.Count)
|
||||
return;
|
||||
|
||||
switch (collectionType)
|
||||
|
|
@ -262,13 +262,13 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
var jObj = new JObject
|
||||
{
|
||||
{ nameof(Version), Version },
|
||||
{ nameof(Default), Default.Id },
|
||||
{ nameof(Interface), Interface.Id },
|
||||
{ nameof(Current), Current.Id },
|
||||
{ nameof(Default), Default.Identity.Id },
|
||||
{ nameof(Interface), Interface.Identity.Id },
|
||||
{ nameof(Current), Current.Identity.Id },
|
||||
};
|
||||
foreach (var (type, collection) in SpecialCollections.WithIndex().Where(p => p.Value != null)
|
||||
.Select(p => ((CollectionType)p.Index, p.Value!)))
|
||||
jObj.Add(type.ToString(), collection.Id);
|
||||
jObj.Add(type.ToString(), collection.Identity.Id);
|
||||
|
||||
jObj.Add(nameof(Individuals), Individuals.ToJObject());
|
||||
using var j = new JsonTextWriter(writer);
|
||||
|
|
@ -282,7 +282,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
.Prepend(Interface)
|
||||
.Prepend(Default)
|
||||
.Concat(Individuals.Assignments.Select(kvp => kvp.Collection))
|
||||
.SelectMany(c => c.GetFlattenedInheritance()).Contains(Current);
|
||||
.SelectMany(c => c.Inheritance.FlatHierarchy).Contains(Current);
|
||||
|
||||
/// <summary> Save if any of the active collections is changed and set new collections to Current. </summary>
|
||||
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _3)
|
||||
|
|
@ -300,7 +300,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
if (oldCollection == Interface)
|
||||
SetCollection(ModCollection.Empty, CollectionType.Interface);
|
||||
if (oldCollection == Current)
|
||||
SetCollection(Default.Index > ModCollection.Empty.Index ? Default : _storage.DefaultNamed, CollectionType.Current);
|
||||
SetCollection(Default.Identity.Index > ModCollection.Empty.Identity.Index ? Default : _storage.DefaultNamed, CollectionType.Current);
|
||||
|
||||
for (var i = 0; i < SpecialCollections.Length; ++i)
|
||||
{
|
||||
|
|
@ -325,11 +325,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
{
|
||||
var configChanged = false;
|
||||
// Load the default collection. If the name does not exist take the empty collection.
|
||||
var defaultName = jObject[nameof(Default)]?.ToObject<string>() ?? ModCollection.Empty.Name;
|
||||
var defaultName = jObject[nameof(Default)]?.ToObject<string>() ?? ModCollection.Empty.Identity.Name;
|
||||
if (!_storage.ByName(defaultName, out var defaultCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Identity.Name}.",
|
||||
NotificationType.Warning);
|
||||
Default = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
|
|
@ -340,11 +340,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
}
|
||||
|
||||
// Load the interface collection. If no string is set, use the name of whatever was set as Default.
|
||||
var interfaceName = jObject[nameof(Interface)]?.ToObject<string>() ?? Default.Name;
|
||||
var interfaceName = jObject[nameof(Interface)]?.ToObject<string>() ?? Default.Identity.Name;
|
||||
if (!_storage.ByName(interfaceName, out var interfaceCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Identity.Name}.",
|
||||
NotificationType.Warning);
|
||||
Interface = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
|
|
@ -355,11 +355,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
}
|
||||
|
||||
// Load the current collection.
|
||||
var currentName = jObject[nameof(Current)]?.ToObject<string>() ?? Default.Name;
|
||||
var currentName = jObject[nameof(Current)]?.ToObject<string>() ?? Default.Identity.Name;
|
||||
if (!_storage.ByName(currentName, out var currentCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollection.DefaultCollectionName}.",
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollectionIdentity.DefaultCollectionName}.",
|
||||
NotificationType.Warning);
|
||||
Current = _storage.DefaultNamed;
|
||||
configChanged = true;
|
||||
|
|
@ -404,7 +404,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
if (!_storage.ById(defaultId, out var defaultCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Identity.Name}.",
|
||||
NotificationType.Warning);
|
||||
Default = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
|
|
@ -415,11 +415,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
}
|
||||
|
||||
// Load the interface collection. If no string is set, use the name of whatever was set as Default.
|
||||
var interfaceId = jObject[nameof(Interface)]?.ToObject<Guid>() ?? Default.Id;
|
||||
var interfaceId = jObject[nameof(Interface)]?.ToObject<Guid>() ?? Default.Identity.Id;
|
||||
if (!_storage.ById(interfaceId, out var interfaceCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Identity.Name}.",
|
||||
NotificationType.Warning);
|
||||
Interface = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
|
|
@ -430,11 +430,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
}
|
||||
|
||||
// Load the current collection.
|
||||
var currentId = jObject[nameof(Current)]?.ToObject<Guid>() ?? _storage.DefaultNamed.Id;
|
||||
var currentId = jObject[nameof(Current)]?.ToObject<Guid>() ?? _storage.DefaultNamed.Identity.Id;
|
||||
if (!_storage.ById(currentId, out var currentCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollection.DefaultCollectionName}.",
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollectionIdentity.DefaultCollectionName}.",
|
||||
NotificationType.Warning);
|
||||
Current = _storage.DefaultNamed;
|
||||
configChanged = true;
|
||||
|
|
@ -587,7 +587,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
case IdentifierType.Player when id.HomeWorld != ushort.MaxValue:
|
||||
{
|
||||
var global = ByType(CollectionType.Individual, _actors.CreatePlayer(id.PlayerName, ushort.MaxValue));
|
||||
return global?.Index == checkAssignment.Index
|
||||
return (global != null ? global.Identity.Index : null) == checkAssignment.Identity.Index
|
||||
? "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it."
|
||||
: string.Empty;
|
||||
}
|
||||
|
|
@ -596,12 +596,12 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
{
|
||||
var global = ByType(CollectionType.Individual,
|
||||
_actors.CreateOwned(id.PlayerName, ushort.MaxValue, id.Kind, id.DataId));
|
||||
if (global?.Index == checkAssignment.Index)
|
||||
if ((global != null ? global.Identity.Index : null) == checkAssignment.Identity.Index)
|
||||
return "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it.";
|
||||
}
|
||||
|
||||
var unowned = ByType(CollectionType.Individual, _actors.CreateNpc(id.Kind, id.DataId));
|
||||
return unowned?.Index == checkAssignment.Index
|
||||
return (unowned != null ? unowned.Identity.Index : null) == checkAssignment.Identity.Index
|
||||
? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it."
|
||||
: string.Empty;
|
||||
}
|
||||
|
|
@ -617,7 +617,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
if (maleNpc == null)
|
||||
{
|
||||
maleNpc = Default;
|
||||
if (maleNpc.Index != checkAssignment.Index)
|
||||
if (maleNpc.Identity.Index != checkAssignment.Identity.Index)
|
||||
return string.Empty;
|
||||
|
||||
collection1 = CollectionType.Default;
|
||||
|
|
@ -626,7 +626,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
if (femaleNpc == null)
|
||||
{
|
||||
femaleNpc = Default;
|
||||
if (femaleNpc.Index != checkAssignment.Index)
|
||||
if (femaleNpc.Identity.Index != checkAssignment.Identity.Index)
|
||||
return string.Empty;
|
||||
|
||||
collection2 = CollectionType.Default;
|
||||
|
|
@ -646,7 +646,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
|
|||
if (assignment == null)
|
||||
continue;
|
||||
|
||||
if (assignment.Index == checkAssignment.Index)
|
||||
if (assignment.Identity.Index == checkAssignment.Identity.Index)
|
||||
return
|
||||
$"Assignment is currently redundant due to overwriting {parentType.ToName()} with an identical collection.\nYou can remove it.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
/// </summary>
|
||||
public bool SetModState(ModCollection collection, Mod mod, bool newValue)
|
||||
{
|
||||
var oldValue = collection.Settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false;
|
||||
var oldValue = collection.GetInheritedSettings(mod.Index).Settings?.Enabled ?? false;
|
||||
if (newValue == oldValue)
|
||||
return false;
|
||||
|
||||
var inheritance = FixInheritance(collection, mod, false);
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.Enabled = newValue;
|
||||
collection.GetOwnSettings(mod.Index)!.Enabled = newValue;
|
||||
InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? Setting.Indefinite : newValue ? Setting.False : Setting.True,
|
||||
0);
|
||||
return true;
|
||||
|
|
@ -55,13 +55,13 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
var changes = false;
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
var oldValue = collection.Settings[mod.Index]?.Enabled;
|
||||
var oldValue = collection.GetOwnSettings(mod.Index)?.Enabled;
|
||||
if (newValue == oldValue)
|
||||
continue;
|
||||
|
||||
FixInheritance(collection, mod, false);
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.Enabled = newValue;
|
||||
changes = true;
|
||||
collection.GetOwnSettings(mod.Index)!.Enabled = newValue;
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (!changes)
|
||||
|
|
@ -76,35 +76,50 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
/// </summary>
|
||||
public bool SetModPriority(ModCollection collection, Mod mod, ModPriority newValue)
|
||||
{
|
||||
var oldValue = collection.Settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? ModPriority.Default;
|
||||
var oldValue = collection.GetInheritedSettings(mod.Index).Settings?.Priority ?? ModPriority.Default;
|
||||
if (newValue == oldValue)
|
||||
return false;
|
||||
|
||||
var inheritance = FixInheritance(collection, mod, false);
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.Priority = newValue;
|
||||
collection.GetOwnSettings(mod.Index)!.Priority = newValue;
|
||||
InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? Setting.Indefinite : oldValue.AsSetting, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
|
||||
/// /// If the mod is currently inherited, stop the inheritance.
|
||||
/// If the mod is currently inherited, stop the inheritance.
|
||||
/// </summary>
|
||||
public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, Setting newValue)
|
||||
{
|
||||
var settings = collection.Settings[mod.Index] != null
|
||||
? collection.Settings[mod.Index]!.Settings
|
||||
: collection[mod.Index].Settings?.Settings;
|
||||
var settings = collection.GetInheritedSettings(mod.Index).Settings?.Settings;
|
||||
var oldValue = settings?[groupIdx] ?? mod.Groups[groupIdx].DefaultSettings;
|
||||
if (oldValue == newValue)
|
||||
return false;
|
||||
|
||||
var inheritance = FixInheritance(collection, mod, false);
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index]!.SetValue(mod, groupIdx, newValue);
|
||||
collection.GetOwnSettings(mod.Index)!.SetValue(mod, groupIdx, newValue);
|
||||
InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? Setting.Indefinite : oldValue, groupIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetTemporarySettings(ModCollection collection, Mod mod, TemporaryModSettings? settings, int key = 0)
|
||||
{
|
||||
key = settings?.Lock ?? key;
|
||||
if (!CanSetTemporarySettings(collection, mod, key))
|
||||
return false;
|
||||
|
||||
collection.Settings.SetTemporary(mod.Index, settings);
|
||||
InvokeChange(collection, ModSettingChange.TemporarySetting, mod, Setting.Indefinite, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanSetTemporarySettings(ModCollection collection, Mod mod, int key)
|
||||
{
|
||||
var old = collection.GetTempSettings(mod.Index);
|
||||
return old is not { Lock: > 0 } || old.Lock == key;
|
||||
}
|
||||
|
||||
/// <summary> Copy the settings of an existing (sourceMod != null) or stored (sourceName) mod to another mod, if they exist. </summary>
|
||||
public bool CopyModSettings(ModCollection collection, Mod? sourceMod, string sourceName, Mod? targetMod, string targetName)
|
||||
{
|
||||
|
|
@ -115,10 +130,10 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
// If it does not exist, check unused settings.
|
||||
// If it does not exist and has no unused settings, also use null.
|
||||
ModSettings.SavedSettings? savedSettings = sourceMod != null
|
||||
? collection.Settings[sourceMod.Index] != null
|
||||
? new ModSettings.SavedSettings(collection.Settings[sourceMod.Index]!, sourceMod)
|
||||
? collection.GetOwnSettings(sourceMod.Index) is { } ownSettings
|
||||
? new ModSettings.SavedSettings(ownSettings, sourceMod)
|
||||
: null
|
||||
: collection.UnusedSettings.TryGetValue(sourceName, out var s)
|
||||
: collection.Settings.Unused.TryGetValue(sourceName, out var s)
|
||||
? s
|
||||
: null;
|
||||
|
||||
|
|
@ -148,10 +163,10 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
// or remove any unused settings for the target if they are inheriting.
|
||||
if (savedSettings != null)
|
||||
{
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings)[targetName] = savedSettings.Value;
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused)[targetName] = savedSettings.Value;
|
||||
saveService.QueueSave(new ModCollectionSave(modStorage, collection));
|
||||
}
|
||||
else if (((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(targetName))
|
||||
else if (((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused).Remove(targetName))
|
||||
{
|
||||
saveService.QueueSave(new ModCollectionSave(modStorage, collection));
|
||||
}
|
||||
|
|
@ -166,12 +181,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
/// </summary>
|
||||
private static bool FixInheritance(ModCollection collection, Mod mod, bool inherit)
|
||||
{
|
||||
var settings = collection.Settings[mod.Index];
|
||||
var settings = collection.GetOwnSettings(mod.Index);
|
||||
if (inherit == (settings == null))
|
||||
return false;
|
||||
|
||||
((List<ModSettings?>)collection.Settings)[mod.Index] =
|
||||
inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
|
||||
var settings1 = inherit ? null : collection.GetInheritedSettings(mod.Index).Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
|
||||
collection.Settings.Set(mod.Index, settings1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -181,14 +196,15 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
{
|
||||
saveService.QueueSave(new ModCollectionSave(modStorage, changedCollection));
|
||||
communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false);
|
||||
RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx);
|
||||
if (type is not ModSettingChange.TemporarySetting)
|
||||
RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx);
|
||||
}
|
||||
|
||||
/// <summary> Trigger changes in all inherited collections. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void RecurseInheritors(ModCollection directParent, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx)
|
||||
{
|
||||
foreach (var directInheritor in directParent.DirectParentOf)
|
||||
foreach (var directInheritor in directParent.Inheritance.DirectlyInheritedBy)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
|
@ -197,7 +213,7 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
|
|||
communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true);
|
||||
break;
|
||||
default:
|
||||
if (directInheritor.Settings[mod!.Index] == null)
|
||||
if (directInheritor.GetOwnSettings(mod!.Index) == null)
|
||||
communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
public ModCollection CreateFromData(Guid id, string name, int version, Dictionary<string, ModSettings.SavedSettings> allSettings,
|
||||
IReadOnlyList<string> inheritances)
|
||||
{
|
||||
var newCollection = ModCollection.CreateFromData(_saveService, _modStorage, id, name, CurrentCollectionId, version, Count, allSettings,
|
||||
inheritances);
|
||||
var newCollection = ModCollection.CreateFromData(_saveService, _modStorage,
|
||||
new ModCollectionIdentity(id, CurrentCollectionId, name, Count), version, allSettings, inheritances);
|
||||
_collectionsByLocal[CurrentCollectionId] = newCollection;
|
||||
CurrentCollectionId += 1;
|
||||
return newCollection;
|
||||
|
|
@ -57,7 +57,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
}
|
||||
|
||||
public void Delete(ModCollection collection)
|
||||
=> _collectionsByLocal.Remove(collection.LocalId);
|
||||
=> _collectionsByLocal.Remove(collection.Identity.LocalId);
|
||||
|
||||
/// <remarks> The empty collection is always available at Index 0. </remarks>
|
||||
private readonly List<ModCollection> _collections =
|
||||
|
|
@ -92,7 +92,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
public bool ByName(string name, [NotNullWhen(true)] out ModCollection? collection)
|
||||
{
|
||||
if (name.Length != 0)
|
||||
return _collections.FindFirst(c => string.Equals(c.Name, name, StringComparison.OrdinalIgnoreCase), out collection);
|
||||
return _collections.FindFirst(c => string.Equals(c.Identity.Name, name, StringComparison.OrdinalIgnoreCase), out collection);
|
||||
|
||||
collection = ModCollection.Empty;
|
||||
return true;
|
||||
|
|
@ -102,7 +102,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
public bool ById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
|
||||
{
|
||||
if (id != Guid.Empty)
|
||||
return _collections.FindFirst(c => c.Id == id, out collection);
|
||||
return _collections.FindFirst(c => c.Identity.Id == id, out collection);
|
||||
|
||||
collection = ModCollection.Empty;
|
||||
return true;
|
||||
|
|
@ -158,7 +158,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
var newCollection = Create(name, _collections.Count, duplicate);
|
||||
_collections.Add(newCollection);
|
||||
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, newCollection));
|
||||
Penumbra.Messager.NotificationMessage($"Created new collection {newCollection.AnonymizedName}.", NotificationType.Success, false);
|
||||
Penumbra.Messager.NotificationMessage($"Created new collection {newCollection.Identity.AnonymizedName}.", NotificationType.Success, false);
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Inactive, null, newCollection, string.Empty);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -168,13 +168,13 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
/// </summary>
|
||||
public bool RemoveCollection(ModCollection collection)
|
||||
{
|
||||
if (collection.Index <= ModCollection.Empty.Index || collection.Index >= _collections.Count)
|
||||
if (collection.Identity.Index <= ModCollection.Empty.Identity.Index || collection.Identity.Index >= _collections.Count)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage("Can not remove the empty collection.", NotificationType.Error, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (collection.Index == DefaultNamed.Index)
|
||||
if (collection.Identity.Index == DefaultNamed.Identity.Index)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage("Can not remove the default collection.", NotificationType.Error, false);
|
||||
return false;
|
||||
|
|
@ -182,13 +182,13 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
|
||||
Delete(collection);
|
||||
_saveService.ImmediateDelete(new ModCollectionSave(_modStorage, collection));
|
||||
_collections.RemoveAt(collection.Index);
|
||||
_collections.RemoveAt(collection.Identity.Index);
|
||||
// Update indices.
|
||||
for (var i = collection.Index; i < Count; ++i)
|
||||
_collections[i].Index = i;
|
||||
_collectionsByLocal.Remove(collection.LocalId);
|
||||
for (var i = collection.Identity.Index; i < Count; ++i)
|
||||
_collections[i].Identity.Index = i;
|
||||
_collectionsByLocal.Remove(collection.Identity.LocalId);
|
||||
|
||||
Penumbra.Messager.NotificationMessage($"Deleted collection {collection.AnonymizedName}.", NotificationType.Success, false);
|
||||
Penumbra.Messager.NotificationMessage($"Deleted collection {collection.Identity.AnonymizedName}.", NotificationType.Success, false);
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Inactive, collection, null, string.Empty);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -196,8 +196,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
/// <summary> Remove all settings for not currently-installed mods from the given collection. </summary>
|
||||
public void CleanUnavailableSettings(ModCollection collection)
|
||||
{
|
||||
var any = collection.UnusedSettings.Count > 0;
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Clear();
|
||||
var any = collection.Settings.Unused.Count > 0;
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused).Clear();
|
||||
if (any)
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
|
|
@ -205,7 +205,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
/// <summary> Remove a specific setting for not currently-installed mods from the given collection. </summary>
|
||||
public void CleanUnavailableSetting(ModCollection collection, string? setting)
|
||||
{
|
||||
if (setting != null && ((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(setting))
|
||||
if (setting != null && ((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused).Remove(setting))
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
|
||||
|
|
@ -246,13 +246,13 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
{
|
||||
File.Move(file.FullName, correctName, false);
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Collection {file.Name} does not correspond to {collection.Identifier}, renamed.",
|
||||
$"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, renamed.",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Collection {file.Name} does not correspond to {collection.Identifier}, rename failed:\n{ex}",
|
||||
$"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, rename failed:\n{ex}",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
}
|
||||
|
|
@ -273,7 +273,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(e,
|
||||
$"Collection {file.Name} does not correspond to {collection.Identifier}, but could not rename.",
|
||||
$"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, but could not rename.",
|
||||
NotificationType.Error);
|
||||
}
|
||||
|
||||
|
|
@ -291,14 +291,14 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
/// </summary>
|
||||
private ModCollection SetDefaultNamedCollection()
|
||||
{
|
||||
if (ByName(ModCollection.DefaultCollectionName, out var collection))
|
||||
if (ByName(ModCollectionIdentity.DefaultCollectionName, out var collection))
|
||||
return collection;
|
||||
|
||||
if (AddCollection(ModCollection.DefaultCollectionName, null))
|
||||
if (AddCollection(ModCollectionIdentity.DefaultCollectionName, null))
|
||||
return _collections[^1];
|
||||
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Unknown problem creating a collection with the name {ModCollection.DefaultCollectionName}, which is required to exist.",
|
||||
$"Unknown problem creating a collection with the name {ModCollectionIdentity.DefaultCollectionName}, which is required to exist.",
|
||||
NotificationType.Error);
|
||||
return Count > 1 ? _collections[1] : _collections[0];
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
private void OnModDiscoveryStarted()
|
||||
{
|
||||
foreach (var collection in this)
|
||||
collection.PrepareModDiscovery(_modStorage);
|
||||
collection.Settings.PrepareModDiscovery(_modStorage);
|
||||
}
|
||||
|
||||
/// <summary> Restore all settings in all collections to mods. </summary>
|
||||
|
|
@ -315,7 +315,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
{
|
||||
// Re-apply all mod settings.
|
||||
foreach (var collection in this)
|
||||
collection.ApplyModSettings(_saveService, _modStorage);
|
||||
collection.Settings.ApplyModSettings(collection, _saveService, _modStorage);
|
||||
}
|
||||
|
||||
/// <summary> Add or remove a mod from all collections, or re-save all collections where the mod has settings. </summary>
|
||||
|
|
@ -326,21 +326,22 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
{
|
||||
case ModPathChangeType.Added:
|
||||
foreach (var collection in this)
|
||||
collection.AddMod(mod);
|
||||
collection.Settings.AddMod(mod);
|
||||
break;
|
||||
case ModPathChangeType.Deleted:
|
||||
foreach (var collection in this)
|
||||
collection.RemoveMod(mod);
|
||||
collection.Settings.RemoveMod(mod);
|
||||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null))
|
||||
foreach (var collection in this.Where(collection => collection.GetOwnSettings(mod.Index) != null))
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
break;
|
||||
case ModPathChangeType.Reloaded:
|
||||
foreach (var collection in this)
|
||||
{
|
||||
if (collection.Settings[mod.Index]?.Settings.FixAll(mod) ?? false)
|
||||
if (collection.GetOwnSettings(mod.Index)?.Settings.FixAll(mod) ?? false)
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
collection.Settings.SetTemporary(mod.Index, null);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -357,8 +358,9 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
|
||||
foreach (var collection in this)
|
||||
{
|
||||
if (collection.Settings[mod.Index]?.HandleChanges(type, mod, group, option, movedToIdx) ?? false)
|
||||
if (collection.GetOwnSettings(mod.Index)?.HandleChanges(type, mod, group, option, movedToIdx) ?? false)
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
collection.Settings.SetTemporary(mod.Index, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,9 +372,9 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
|
|||
|
||||
foreach (var collection in this)
|
||||
{
|
||||
var (settings, _) = collection[mod.Index];
|
||||
var (settings, _) = collection.GetActualSettings(mod.Index);
|
||||
if (settings is { Enabled: true })
|
||||
collection.IncrementCounter();
|
||||
collection.Counters.IncrementChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public partial class IndividualCollections
|
|||
foreach (var (name, identifiers, collection) in Assignments)
|
||||
{
|
||||
var tmp = identifiers[0].ToJson();
|
||||
tmp.Add("Collection", collection.Id);
|
||||
tmp.Add("Collection", collection.Identity.Id);
|
||||
tmp.Add("Display", name);
|
||||
ret.Add(tmp);
|
||||
}
|
||||
|
|
@ -182,7 +182,7 @@ public partial class IndividualCollections
|
|||
Penumbra.Log.Information($"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}].");
|
||||
else
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Could not migrate {name} ({collection.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.",
|
||||
$"Could not migrate {name} ({collection.Identity.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.",
|
||||
NotificationType.Error);
|
||||
}
|
||||
// If it is not a valid NPC name, check if it can be a player name.
|
||||
|
|
@ -192,16 +192,16 @@ public partial class IndividualCollections
|
|||
var shortName = string.Join(" ", name.Split().Select(n => $"{n[0]}."));
|
||||
// Try to migrate the player name without logging full names.
|
||||
if (Add($"{name} ({_actors.Data.ToWorldName(identifier.HomeWorld)})", [identifier], collection))
|
||||
Penumbra.Log.Information($"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier.");
|
||||
Penumbra.Log.Information($"Migrated {shortName} ({collection.Identity.AnonymizedName}) to Player Identifier.");
|
||||
else
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Could not migrate {shortName} ({collection.AnonymizedName}), please look through your individual collections.",
|
||||
$"Could not migrate {shortName} ({collection.Identity.AnonymizedName}), please look through your individual collections.",
|
||||
NotificationType.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Could not migrate {name} ({collection.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.",
|
||||
$"Could not migrate {name} ({collection.Identity.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.",
|
||||
NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Dalamud.Interface.ImGuiNotification;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
|
@ -63,10 +62,10 @@ public class InheritanceManager : IDisposable, IService
|
|||
if (ReferenceEquals(potentialParent, potentialInheritor))
|
||||
return ValidInheritance.Self;
|
||||
|
||||
if (potentialInheritor.DirectlyInheritsFrom.Contains(potentialParent))
|
||||
if (potentialInheritor.Inheritance.DirectlyInheritsFrom.Contains(potentialParent))
|
||||
return ValidInheritance.Contained;
|
||||
|
||||
if (ModCollection.InheritedCollections(potentialParent).Any(c => ReferenceEquals(c, potentialInheritor)))
|
||||
if (potentialParent.Inheritance.FlatHierarchy.Any(c => ReferenceEquals(c, potentialInheritor)))
|
||||
return ValidInheritance.Circle;
|
||||
|
||||
return ValidInheritance.Valid;
|
||||
|
|
@ -83,25 +82,23 @@ public class InheritanceManager : IDisposable, IService
|
|||
/// <summary> Remove an existing inheritance from a collection. </summary>
|
||||
public void RemoveInheritance(ModCollection inheritor, int idx)
|
||||
{
|
||||
var parent = inheritor.DirectlyInheritsFrom[idx];
|
||||
((List<ModCollection>)inheritor.DirectlyInheritsFrom).RemoveAt(idx);
|
||||
((List<ModCollection>)parent.DirectParentOf).Remove(inheritor);
|
||||
var parent = inheritor.Inheritance.RemoveInheritanceAt(inheritor, idx);
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.AnonymizedName} inheritances.");
|
||||
RecurseInheritanceChanges(inheritor, true);
|
||||
Penumbra.Log.Debug($"Removed {parent.Identity.AnonymizedName} from {inheritor.Identity.AnonymizedName} inheritances.");
|
||||
}
|
||||
|
||||
/// <summary> Order in the inheritance list is relevant. </summary>
|
||||
public void MoveInheritance(ModCollection inheritor, int from, int to)
|
||||
{
|
||||
if (!((List<ModCollection>)inheritor.DirectlyInheritsFrom).Move(from, to))
|
||||
if (!inheritor.Inheritance.MoveInheritance(inheritor, from, to))
|
||||
return;
|
||||
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}.");
|
||||
RecurseInheritanceChanges(inheritor, true);
|
||||
Penumbra.Log.Debug($"Moved {inheritor.Identity.AnonymizedName}s inheritance {from} to {to}.");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddInheritance(ModCollection, ModCollection)"/>
|
||||
|
|
@ -110,16 +107,16 @@ public class InheritanceManager : IDisposable, IService
|
|||
if (CheckValidInheritance(inheritor, parent) != ValidInheritance.Valid)
|
||||
return false;
|
||||
|
||||
((List<ModCollection>)inheritor.DirectlyInheritsFrom).Add(parent);
|
||||
((List<ModCollection>)parent.DirectParentOf).Add(inheritor);
|
||||
inheritor.Inheritance.AddInheritance(inheritor, parent);
|
||||
if (invokeEvent)
|
||||
{
|
||||
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug($"Added {parent.AnonymizedName} to {inheritor.AnonymizedName} inheritances.");
|
||||
RecurseInheritanceChanges(inheritor, invokeEvent);
|
||||
|
||||
Penumbra.Log.Debug($"Added {parent.Identity.AnonymizedName} to {inheritor.Identity.AnonymizedName} inheritances.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -131,11 +128,11 @@ public class InheritanceManager : IDisposable, IService
|
|||
{
|
||||
foreach (var collection in _storage)
|
||||
{
|
||||
if (collection.InheritanceByName == null)
|
||||
if (collection.Inheritance.ConsumeNames() is not { } byName)
|
||||
continue;
|
||||
|
||||
var changes = false;
|
||||
foreach (var subCollectionName in collection.InheritanceByName)
|
||||
foreach (var subCollectionName in byName)
|
||||
{
|
||||
if (Guid.TryParse(subCollectionName, out var guid) && _storage.ById(guid, out var subCollection))
|
||||
{
|
||||
|
|
@ -143,29 +140,30 @@ public class InheritanceManager : IDisposable, IService
|
|||
continue;
|
||||
|
||||
changes = true;
|
||||
Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.",
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
else if (_storage.ByName(subCollectionName, out subCollection))
|
||||
{
|
||||
changes = true;
|
||||
Penumbra.Log.Information($"Migrating inheritance for {collection.AnonymizedName} from name to GUID.");
|
||||
Penumbra.Log.Information($"Migrating inheritance for {collection.Identity.AnonymizedName} from name to GUID.");
|
||||
if (AddInheritance(collection, subCollection, false))
|
||||
continue;
|
||||
|
||||
Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.",
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Inherited collection {subCollectionName} for {collection.AnonymizedName} does not exist, it was removed.",
|
||||
$"Inherited collection {subCollectionName} for {collection.Identity.AnonymizedName} does not exist, it was removed.",
|
||||
NotificationType.Warning);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
collection.InheritanceByName = null;
|
||||
if (changes)
|
||||
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
|
|
@ -178,20 +176,22 @@ public class InheritanceManager : IDisposable, IService
|
|||
|
||||
foreach (var c in _storage)
|
||||
{
|
||||
var inheritedIdx = c.DirectlyInheritsFrom.IndexOf(old);
|
||||
var inheritedIdx = c.Inheritance.DirectlyInheritsFrom.IndexOf(old);
|
||||
if (inheritedIdx >= 0)
|
||||
RemoveInheritance(c, inheritedIdx);
|
||||
|
||||
((List<ModCollection>)c.DirectParentOf).Remove(old);
|
||||
c.Inheritance.RemoveChild(old);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecurseInheritanceChanges(ModCollection newInheritor)
|
||||
private void RecurseInheritanceChanges(ModCollection newInheritor, bool invokeEvent)
|
||||
{
|
||||
foreach (var inheritor in newInheritor.DirectParentOf)
|
||||
foreach (var inheritor in newInheritor.Inheritance.DirectlyInheritedBy)
|
||||
{
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, true);
|
||||
RecurseInheritanceChanges(inheritor);
|
||||
ModCollectionInheritance.UpdateFlattenedInheritance(inheritor);
|
||||
RecurseInheritanceChanges(inheritor, invokeEvent);
|
||||
if (invokeEvent)
|
||||
_communicator.CollectionInheritanceChanged.Invoke(inheritor, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ internal static class ModCollectionMigration
|
|||
// Remove all completely defaulted settings from active and inactive mods.
|
||||
for (var i = 0; i < collection.Settings.Count; ++i)
|
||||
{
|
||||
if (SettingIsDefaultV0(collection.Settings[i]))
|
||||
((List<ModSettings?>)collection.Settings)[i] = null;
|
||||
if (SettingIsDefaultV0(collection.GetOwnSettings(i)))
|
||||
collection.Settings.SetAll(i, FullModSettings.Empty);
|
||||
}
|
||||
|
||||
foreach (var (key, _) in collection.UnusedSettings.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList())
|
||||
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(key);
|
||||
foreach (var (key, _) in collection.Settings.Unused.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList())
|
||||
collection.Settings.RemoveUnused(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class TempCollectionManager : IDisposable, IService
|
|||
=> _customCollections.Values;
|
||||
|
||||
public bool CollectionByName(string name, [NotNullWhen(true)] out ModCollection? collection)
|
||||
=> _customCollections.Values.FindFirst(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase), out collection);
|
||||
=> _customCollections.Values.FindFirst(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase), out collection);
|
||||
|
||||
public bool CollectionById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
|
||||
=> _customCollections.TryGetValue(id, out collection);
|
||||
|
|
@ -54,12 +54,12 @@ public class TempCollectionManager : IDisposable, IService
|
|||
if (GlobalChangeCounter == int.MaxValue)
|
||||
GlobalChangeCounter = 0;
|
||||
var collection = _storage.CreateTemporary(name, ~Count, GlobalChangeCounter++);
|
||||
Penumbra.Log.Debug($"Creating temporary collection {collection.Name} with {collection.Id}.");
|
||||
if (_customCollections.TryAdd(collection.Id, collection))
|
||||
Penumbra.Log.Debug($"Creating temporary collection {collection.Identity.Name} with {collection.Identity.Id}.");
|
||||
if (_customCollections.TryAdd(collection.Identity.Id, collection))
|
||||
{
|
||||
// Temporary collection created.
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, string.Empty);
|
||||
return collection.Id;
|
||||
return collection.Identity.Id;
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
|
|
@ -74,8 +74,8 @@ public class TempCollectionManager : IDisposable, IService
|
|||
}
|
||||
|
||||
_storage.Delete(collection);
|
||||
Penumbra.Log.Debug($"Deleted temporary collection {collection.Id}.");
|
||||
GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0);
|
||||
Penumbra.Log.Debug($"Deleted temporary collection {collection.Identity.Id}.");
|
||||
GlobalChangeCounter += Math.Max(collection.Counters.Change + 1 - GlobalChangeCounter, 0);
|
||||
for (var i = 0; i < Collections.Count; ++i)
|
||||
{
|
||||
if (Collections[i].Collection != collection)
|
||||
|
|
@ -83,7 +83,7 @@ public class TempCollectionManager : IDisposable, IService
|
|||
|
||||
// Temporary collection assignment removed.
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName);
|
||||
Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Id} from {Collections[i].DisplayName}.");
|
||||
Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Identity.Id} from {Collections[i].DisplayName}.");
|
||||
Collections.Delete(i--);
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ public class TempCollectionManager : IDisposable, IService
|
|||
return false;
|
||||
|
||||
// Temporary collection assignment added.
|
||||
Penumbra.Log.Verbose($"Assigned temporary collection {collection.AnonymizedName} to {Collections.Last().DisplayName}.");
|
||||
Penumbra.Log.Verbose($"Assigned temporary collection {collection.Identity.AnonymizedName} to {Collections.Last().DisplayName}.");
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -127,6 +127,6 @@ public class TempCollectionManager : IDisposable, IService
|
|||
return false;
|
||||
|
||||
var identifier = _actors.CreatePlayer(byteString, worldId);
|
||||
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Id);
|
||||
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Identity.Id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods.Settings;
|
||||
|
|
@ -13,111 +12,83 @@ namespace Penumbra.Collections;
|
|||
/// - Index is the collections index in the ModCollection.Manager
|
||||
/// - Settings has the same size as ModManager.Mods.
|
||||
/// - any change in settings or inheritance of the collection causes a Save.
|
||||
/// - the name can not contain invalid path characters and has to be unique when lower-cased.
|
||||
/// </summary>
|
||||
public partial class ModCollection
|
||||
{
|
||||
public const int CurrentVersion = 2;
|
||||
public const string DefaultCollectionName = "Default";
|
||||
public const string EmptyCollectionName = "None";
|
||||
public const int CurrentVersion = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Create the always available Empty Collection that will always sit at index 0,
|
||||
/// can not be deleted and does never create a cache.
|
||||
/// </summary>
|
||||
public static readonly ModCollection Empty = new(Guid.Empty, EmptyCollectionName, LocalCollectionId.Zero, 0, 0, CurrentVersion, [], [], []);
|
||||
public static readonly ModCollection Empty = new(ModCollectionIdentity.Empty, 0, CurrentVersion, new ModSettingProvider(),
|
||||
new ModCollectionInheritance());
|
||||
|
||||
/// <summary> The name of a collection. </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public LocalCollectionId LocalId { get; }
|
||||
|
||||
public string Identifier
|
||||
=> Id.ToString();
|
||||
|
||||
public string ShortIdentifier
|
||||
=> Identifier[..8];
|
||||
public ModCollectionIdentity Identity;
|
||||
|
||||
public override string ToString()
|
||||
=> Name.Length > 0 ? Name : ShortIdentifier;
|
||||
=> Identity.ToString();
|
||||
|
||||
/// <summary> Get the first two letters of a collection name and its Index (or None if it is the empty collection). </summary>
|
||||
public string AnonymizedName
|
||||
=> this == Empty ? Empty.Name : Name == DefaultCollectionName ? Name : ShortIdentifier;
|
||||
public readonly ModSettingProvider Settings;
|
||||
public ModCollectionInheritance Inheritance;
|
||||
public CollectionCounters Counters;
|
||||
|
||||
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
|
||||
public int Index { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Count the number of changes of the effective file list.
|
||||
/// This is used for material and imc changes.
|
||||
/// </summary>
|
||||
public int ChangeCounter { get; private set; }
|
||||
|
||||
public uint ImcChangeCounter { get; set; }
|
||||
public uint AtchChangeCounter { get; set; }
|
||||
|
||||
/// <summary> Increment the number of changes in the effective file list. </summary>
|
||||
public int IncrementCounter()
|
||||
=> ++ChangeCounter;
|
||||
|
||||
/// <summary>
|
||||
/// If a ModSetting is null, it can be inherited from other collections.
|
||||
/// If no collection provides a setting for the mod, it is just disabled.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<ModSettings?> Settings;
|
||||
|
||||
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
|
||||
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
|
||||
|
||||
/// <summary> Inheritances stored before they can be applied. </summary>
|
||||
public IReadOnlyList<string>? InheritanceByName;
|
||||
|
||||
/// <summary> Contains all direct parent collections this collection inherits settings from. </summary>
|
||||
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom;
|
||||
|
||||
/// <summary> Contains all direct child collections that inherit from this collection. </summary>
|
||||
public readonly IReadOnlyList<ModCollection> DirectParentOf = new List<ModCollection>();
|
||||
|
||||
/// <summary> All inherited collections in application order without filtering for duplicates. </summary>
|
||||
public static IEnumerable<ModCollection> InheritedCollections(ModCollection collection)
|
||||
=> collection.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(collection);
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all collections inherited from in depth-first order.
|
||||
/// Skip already visited collections to avoid circular dependencies.
|
||||
/// </summary>
|
||||
public IEnumerable<ModCollection> GetFlattenedInheritance()
|
||||
=> InheritedCollections(this).Distinct();
|
||||
|
||||
/// <summary>
|
||||
/// Obtain the actual settings for a given mod via index.
|
||||
/// Also returns the collection the settings are taken from.
|
||||
/// If no collection provides settings for this mod, this collection is returned together with null.
|
||||
/// </summary>
|
||||
public (ModSettings? Settings, ModCollection Collection) this[Index idx]
|
||||
public ModSettings? GetOwnSettings(Index idx)
|
||||
{
|
||||
get
|
||||
if (Identity.Index <= 0)
|
||||
return ModSettings.Empty;
|
||||
|
||||
return Settings.Settings[idx].Settings;
|
||||
}
|
||||
|
||||
public TemporaryModSettings? GetTempSettings(Index idx)
|
||||
{
|
||||
if (Identity.Index <= 0)
|
||||
return null;
|
||||
|
||||
return Settings.Settings[idx].TempSettings;
|
||||
}
|
||||
|
||||
public (ModSettings? Settings, ModCollection Collection) GetInheritedSettings(Index idx)
|
||||
{
|
||||
if (Identity.Index <= 0)
|
||||
return (ModSettings.Empty, this);
|
||||
|
||||
foreach (var collection in Inheritance.FlatHierarchy)
|
||||
{
|
||||
if (Index <= 0)
|
||||
return (ModSettings.Empty, this);
|
||||
|
||||
foreach (var collection in GetFlattenedInheritance())
|
||||
{
|
||||
var settings = collection.Settings[idx];
|
||||
if (settings != null)
|
||||
return (settings, collection);
|
||||
}
|
||||
|
||||
return (null, this);
|
||||
var settings = collection.Settings.Settings[idx].Settings;
|
||||
if (settings != null)
|
||||
return (settings, collection);
|
||||
}
|
||||
|
||||
return (null, this);
|
||||
}
|
||||
|
||||
public (ModSettings? Settings, ModCollection Collection) GetActualSettings(Index idx)
|
||||
{
|
||||
if (Identity.Index <= 0)
|
||||
return (ModSettings.Empty, this);
|
||||
|
||||
// Check temp settings.
|
||||
var ownTempSettings = Settings.Settings[idx].Resolve();
|
||||
if (ownTempSettings != null)
|
||||
return (ownTempSettings, this);
|
||||
|
||||
// Ignore temp settings for inherited collections.
|
||||
foreach (var collection in Inheritance.FlatHierarchy.Skip(1))
|
||||
{
|
||||
var settings = collection.Settings.Settings[idx].Settings;
|
||||
if (settings != null)
|
||||
return (settings, collection);
|
||||
}
|
||||
|
||||
return (null, this);
|
||||
}
|
||||
|
||||
/// <summary> Evaluates all settings along the whole inheritance tree. </summary>
|
||||
public IEnumerable<ModSettings?> ActualSettings
|
||||
=> Enumerable.Range(0, Settings.Count).Select(i => this[i].Settings);
|
||||
=> Enumerable.Range(0, Settings.Count).Select(i => GetActualSettings(i).Settings);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for duplication. Deep copies all settings and parent collections and adds the new collection to their children lists.
|
||||
|
|
@ -125,21 +96,16 @@ public partial class ModCollection
|
|||
public ModCollection Duplicate(string name, LocalCollectionId localId, int index)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection duplicated with non-positive index.");
|
||||
return new ModCollection(Guid.NewGuid(), name, localId, index, 0, CurrentVersion, Settings.Select(s => s?.DeepCopy()).ToList(),
|
||||
[.. DirectlyInheritsFrom], UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()));
|
||||
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion, Settings.Clone(), Inheritance.Clone());
|
||||
}
|
||||
|
||||
/// <summary> Constructor for reading from files. </summary>
|
||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, Guid id, string name, LocalCollectionId localId, int version,
|
||||
int index,
|
||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, ModCollectionIdentity identity, int version,
|
||||
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection read with non-positive index.");
|
||||
var ret = new ModCollection(id, name, localId, index, 0, version, [], [], allSettings)
|
||||
{
|
||||
InheritanceByName = inheritances,
|
||||
};
|
||||
ret.ApplyModSettings(saver, mods);
|
||||
Debug.Assert(identity.Index > 0, "Collection read with non-positive index.");
|
||||
var ret = new ModCollection(identity, 0, version, new ModSettingProvider(allSettings), new ModCollectionInheritance(inheritances));
|
||||
ret.Settings.ApplyModSettings(ret, saver, mods);
|
||||
ModCollectionMigration.Migrate(saver, mods, version, ret);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -148,7 +114,8 @@ public partial class ModCollection
|
|||
public static ModCollection CreateTemporary(string name, LocalCollectionId localId, int index, int changeCounter)
|
||||
{
|
||||
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
|
||||
var ret = new ModCollection(Guid.NewGuid(), name, localId, index, changeCounter, CurrentVersion, [], [], []);
|
||||
var ret = new ModCollection(ModCollectionIdentity.New(name, localId, index), changeCounter, CurrentVersion, new ModSettingProvider(),
|
||||
new ModCollectionInheritance());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -156,68 +123,18 @@ public partial class ModCollection
|
|||
public static ModCollection CreateEmpty(string name, LocalCollectionId localId, int index, int modCount)
|
||||
{
|
||||
Debug.Assert(index >= 0, "Empty collection created with negative index.");
|
||||
return new ModCollection(Guid.NewGuid(), name, localId, index, 0, CurrentVersion,
|
||||
Enumerable.Repeat((ModSettings?)null, modCount).ToList(), [],
|
||||
[]);
|
||||
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion, ModSettingProvider.Empty(modCount),
|
||||
new ModCollectionInheritance());
|
||||
}
|
||||
|
||||
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
|
||||
internal bool AddMod(Mod mod)
|
||||
private ModCollection(ModCollectionIdentity identity, int changeCounter, int version, ModSettingProvider settings,
|
||||
ModCollectionInheritance inheritance)
|
||||
{
|
||||
if (UnusedSettings.TryGetValue(mod.ModPath.Name, out var save))
|
||||
{
|
||||
var ret = save.ToSettings(mod, out var settings);
|
||||
((List<ModSettings?>)Settings).Add(settings);
|
||||
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings).Remove(mod.ModPath.Name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
((List<ModSettings?>)Settings).Add(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Move settings from the current mod list to the unused mod settings. </summary>
|
||||
internal void RemoveMod(Mod mod)
|
||||
{
|
||||
var settings = Settings[mod.Index];
|
||||
if (settings != null)
|
||||
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(settings, mod);
|
||||
|
||||
((List<ModSettings?>)Settings).RemoveAt(mod.Index);
|
||||
}
|
||||
|
||||
/// <summary> Move all settings to unused settings for rediscovery. </summary>
|
||||
internal void PrepareModDiscovery(ModStorage mods)
|
||||
{
|
||||
foreach (var (mod, setting) in mods.Zip(Settings).Where(s => s.Second != null))
|
||||
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(setting!, mod);
|
||||
|
||||
((List<ModSettings?>)Settings).Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply all mod settings from unused settings to the current set of mods.
|
||||
/// Also fixes invalid settings.
|
||||
/// </summary>
|
||||
internal void ApplyModSettings(SaveService saver, ModStorage mods)
|
||||
{
|
||||
((List<ModSettings?>)Settings).Capacity = Math.Max(((List<ModSettings?>)Settings).Capacity, mods.Count);
|
||||
if (mods.Aggregate(false, (current, mod) => current | AddMod(mod)))
|
||||
saver.ImmediateSave(new ModCollectionSave(mods, this));
|
||||
}
|
||||
|
||||
private ModCollection(Guid id, string name, LocalCollectionId localId, int index, int changeCounter, int version,
|
||||
List<ModSettings?> appliedSettings, List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
|
||||
{
|
||||
Name = name;
|
||||
Id = id;
|
||||
LocalId = localId;
|
||||
Index = index;
|
||||
ChangeCounter = changeCounter;
|
||||
Settings = appliedSettings;
|
||||
UnusedSettings = settings;
|
||||
DirectlyInheritsFrom = inheritsFrom;
|
||||
foreach (var c in DirectlyInheritsFrom)
|
||||
((List<ModCollection>)c.DirectParentOf).Add(this);
|
||||
Identity = identity;
|
||||
Counters = new CollectionCounters(changeCounter);
|
||||
Settings = settings;
|
||||
Inheritance = inheritance;
|
||||
ModCollectionInheritance.UpdateChildren(this);
|
||||
ModCollectionInheritance.UpdateFlattenedInheritance(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
Penumbra/Collections/ModCollectionIdentity.cs
Normal file
42
Penumbra/Collections/ModCollectionIdentity.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using OtterGui;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public struct ModCollectionIdentity(Guid id, LocalCollectionId localId)
|
||||
{
|
||||
public const string DefaultCollectionName = "Default";
|
||||
public const string EmptyCollectionName = "None";
|
||||
|
||||
public static readonly ModCollectionIdentity Empty = new(Guid.Empty, LocalCollectionId.Zero, EmptyCollectionName, 0);
|
||||
|
||||
public string Name { get; set; }
|
||||
public Guid Id { get; } = id;
|
||||
public LocalCollectionId LocalId { get; } = localId;
|
||||
|
||||
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
|
||||
public int Index { get; internal set; }
|
||||
|
||||
public string Identifier
|
||||
=> Id.ToString();
|
||||
|
||||
public string ShortIdentifier
|
||||
=> Id.ShortGuid();
|
||||
|
||||
/// <summary> Get the short identifier of a collection unless it is a well-known collection name. </summary>
|
||||
public string AnonymizedName
|
||||
=> Id == Guid.Empty ? EmptyCollectionName : Name == DefaultCollectionName ? Name : ShortIdentifier;
|
||||
|
||||
public override string ToString()
|
||||
=> Name.Length > 0 ? Name : ShortIdentifier;
|
||||
|
||||
public ModCollectionIdentity(Guid id, LocalCollectionId localId, string name, int index)
|
||||
: this(id, localId)
|
||||
{
|
||||
Name = name;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public static ModCollectionIdentity New(string name, LocalCollectionId id, int index)
|
||||
=> new(Guid.NewGuid(), id, name, index);
|
||||
}
|
||||
92
Penumbra/Collections/ModCollectionInheritance.cs
Normal file
92
Penumbra/Collections/ModCollectionInheritance.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using OtterGui.Filesystem;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public struct ModCollectionInheritance
|
||||
{
|
||||
public IReadOnlyList<string>? InheritanceByName { get; private set; }
|
||||
private readonly List<ModCollection> _directlyInheritsFrom = [];
|
||||
private readonly List<ModCollection> _directlyInheritedBy = [];
|
||||
private readonly List<ModCollection> _flatHierarchy = [];
|
||||
|
||||
public ModCollectionInheritance()
|
||||
{ }
|
||||
|
||||
private ModCollectionInheritance(List<ModCollection> inheritsFrom)
|
||||
=> _directlyInheritsFrom = [.. inheritsFrom];
|
||||
|
||||
public ModCollectionInheritance(IReadOnlyList<string> byName)
|
||||
=> InheritanceByName = byName;
|
||||
|
||||
public ModCollectionInheritance Clone()
|
||||
=> new(_directlyInheritsFrom);
|
||||
|
||||
public IEnumerable<string> Identifiers
|
||||
=> InheritanceByName ?? _directlyInheritsFrom.Select(c => c.Identity.Identifier);
|
||||
|
||||
public IReadOnlyList<string>? ConsumeNames()
|
||||
{
|
||||
var ret = InheritanceByName;
|
||||
InheritanceByName = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void UpdateChildren(ModCollection parent)
|
||||
{
|
||||
foreach (var inheritance in parent.Inheritance.DirectlyInheritsFrom)
|
||||
inheritance.Inheritance._directlyInheritedBy.Add(parent);
|
||||
}
|
||||
|
||||
public void AddInheritance(ModCollection inheritor, ModCollection newParent)
|
||||
{
|
||||
_directlyInheritsFrom.Add(newParent);
|
||||
newParent.Inheritance._directlyInheritedBy.Add(inheritor);
|
||||
UpdateFlattenedInheritance(inheritor);
|
||||
}
|
||||
|
||||
public ModCollection RemoveInheritanceAt(ModCollection inheritor, int idx)
|
||||
{
|
||||
var parent = DirectlyInheritsFrom[idx];
|
||||
_directlyInheritsFrom.RemoveAt(idx);
|
||||
parent.Inheritance._directlyInheritedBy.Remove(parent);
|
||||
UpdateFlattenedInheritance(inheritor);
|
||||
return parent;
|
||||
}
|
||||
|
||||
public bool MoveInheritance(ModCollection inheritor, int from, int to)
|
||||
{
|
||||
if (!_directlyInheritsFrom.Move(from, to))
|
||||
return false;
|
||||
|
||||
UpdateFlattenedInheritance(inheritor);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveChild(ModCollection child)
|
||||
=> _directlyInheritedBy.Remove(child);
|
||||
|
||||
/// <summary> Contains all direct parent collections this collection inherits settings from. </summary>
|
||||
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom
|
||||
=> _directlyInheritsFrom;
|
||||
|
||||
/// <summary> Contains all direct child collections that inherit from this collection. </summary>
|
||||
public readonly IReadOnlyList<ModCollection> DirectlyInheritedBy
|
||||
=> _directlyInheritedBy;
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all collections inherited from in depth-first order.
|
||||
/// Skip already visited collections to avoid circular dependencies.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<ModCollection> FlatHierarchy
|
||||
=> _flatHierarchy;
|
||||
|
||||
public static void UpdateFlattenedInheritance(ModCollection parent)
|
||||
{
|
||||
parent.Inheritance._flatHierarchy.Clear();
|
||||
parent.Inheritance._flatHierarchy.AddRange(InheritedCollections(parent).Distinct());
|
||||
}
|
||||
|
||||
/// <summary> All inherited collections in application order without filtering for duplicates. </summary>
|
||||
private static IEnumerable<ModCollection> InheritedCollections(ModCollection parent)
|
||||
=> parent.Inheritance.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(parent);
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
=> fileNames.CollectionFile(modCollection);
|
||||
|
||||
public string LogName(string _)
|
||||
=> modCollection.AnonymizedName;
|
||||
=> modCollection.Identity.AnonymizedName;
|
||||
|
||||
public string TypeName
|
||||
=> "Collection";
|
||||
|
|
@ -28,23 +28,23 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
j.WriteStartObject();
|
||||
j.WritePropertyName("Version");
|
||||
j.WriteValue(ModCollection.CurrentVersion);
|
||||
j.WritePropertyName(nameof(ModCollection.Id));
|
||||
j.WriteValue(modCollection.Identifier);
|
||||
j.WritePropertyName(nameof(ModCollection.Name));
|
||||
j.WriteValue(modCollection.Name);
|
||||
j.WritePropertyName(nameof(ModCollection.Settings));
|
||||
j.WritePropertyName(nameof(ModCollectionIdentity.Id));
|
||||
j.WriteValue(modCollection.Identity.Identifier);
|
||||
j.WritePropertyName(nameof(ModCollectionIdentity.Name));
|
||||
j.WriteValue(modCollection.Identity.Name);
|
||||
j.WritePropertyName("Settings");
|
||||
|
||||
// Write all used and unused settings by mod directory name.
|
||||
j.WriteStartObject();
|
||||
var list = new List<(string, ModSettings.SavedSettings)>(modCollection.Settings.Count + modCollection.UnusedSettings.Count);
|
||||
var list = new List<(string, ModSettings.SavedSettings)>(modCollection.Settings.Count + modCollection.Settings.Unused.Count);
|
||||
for (var i = 0; i < modCollection.Settings.Count; ++i)
|
||||
{
|
||||
var settings = modCollection.Settings[i];
|
||||
var settings = modCollection.GetOwnSettings(i);
|
||||
if (settings != null)
|
||||
list.Add((modStorage[i].ModPath.Name, new ModSettings.SavedSettings(settings, modStorage[i])));
|
||||
}
|
||||
|
||||
list.AddRange(modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value)));
|
||||
list.AddRange(modCollection.Settings.Unused.Select(kvp => (kvp.Key, kvp.Value)));
|
||||
list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var (modDir, settings) in list)
|
||||
|
|
@ -57,7 +57,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
|
||||
// Inherit by collection name.
|
||||
j.WritePropertyName("Inheritance");
|
||||
x.Serialize(j, modCollection.InheritanceByName ?? modCollection.DirectlyInheritsFrom.Select(c => c.Identifier));
|
||||
x.Serialize(j, modCollection.Inheritance.Identifiers);
|
||||
j.WriteEndObject();
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +79,10 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
{
|
||||
var obj = JObject.Parse(File.ReadAllText(file.FullName));
|
||||
version = obj["Version"]?.ToObject<int>() ?? 0;
|
||||
name = obj[nameof(ModCollection.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
id = obj[nameof(ModCollection.Id)]?.ToObject<Guid>() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty);
|
||||
name = obj[nameof(ModCollectionIdentity.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
id = obj[nameof(ModCollectionIdentity.Id)]?.ToObject<Guid>() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty);
|
||||
// Custom deserialization that is converted with the constructor.
|
||||
settings = obj[nameof(ModCollection.Settings)]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>() ?? settings;
|
||||
settings = obj["Settings"]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>() ?? settings;
|
||||
inheritance = obj["Inheritance"]?.ToObject<List<string>>() ?? inheritance;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
98
Penumbra/Collections/ModSettingProvider.cs
Normal file
98
Penumbra/Collections/ModSettingProvider.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Mods.Settings;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public readonly struct ModSettingProvider
|
||||
{
|
||||
private ModSettingProvider(IEnumerable<FullModSettings> settings, Dictionary<string, ModSettings.SavedSettings> unusedSettings)
|
||||
{
|
||||
_settings = settings.Select(s => s.DeepCopy()).ToList();
|
||||
_unused = unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy());
|
||||
}
|
||||
|
||||
public ModSettingProvider()
|
||||
{ }
|
||||
|
||||
public static ModSettingProvider Empty(int count)
|
||||
=> new(Enumerable.Repeat(FullModSettings.Empty, count), []);
|
||||
|
||||
public ModSettingProvider(Dictionary<string, ModSettings.SavedSettings> allSettings)
|
||||
=> _unused = allSettings;
|
||||
|
||||
private readonly List<FullModSettings> _settings = [];
|
||||
|
||||
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
|
||||
private readonly Dictionary<string, ModSettings.SavedSettings> _unused = [];
|
||||
|
||||
public int Count
|
||||
=> _settings.Count;
|
||||
|
||||
public bool RemoveUnused(string key)
|
||||
=> _unused.Remove(key);
|
||||
|
||||
internal void Set(Index index, ModSettings? settings)
|
||||
=> _settings[index] = _settings[index] with { Settings = settings };
|
||||
|
||||
internal void SetTemporary(Index index, TemporaryModSettings? settings)
|
||||
=> _settings[index] = _settings[index] with { TempSettings = settings };
|
||||
|
||||
internal void SetAll(Index index, FullModSettings settings)
|
||||
=> _settings[index] = settings;
|
||||
|
||||
public IReadOnlyList<FullModSettings> Settings
|
||||
=> _settings;
|
||||
|
||||
public IReadOnlyDictionary<string, ModSettings.SavedSettings> Unused
|
||||
=> _unused;
|
||||
|
||||
public ModSettingProvider Clone()
|
||||
=> new(_settings, _unused);
|
||||
|
||||
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
|
||||
internal bool AddMod(Mod mod)
|
||||
{
|
||||
if (_unused.Remove(mod.ModPath.Name, out var save))
|
||||
{
|
||||
var ret = save.ToSettings(mod, out var settings);
|
||||
_settings.Add(new FullModSettings(settings));
|
||||
return ret;
|
||||
}
|
||||
|
||||
_settings.Add(FullModSettings.Empty);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Move settings from the current mod list to the unused mod settings. </summary>
|
||||
internal void RemoveMod(Mod mod)
|
||||
{
|
||||
var settings = _settings[mod.Index];
|
||||
if (settings.Settings != null)
|
||||
_unused[mod.ModPath.Name] = new ModSettings.SavedSettings(settings.Settings, mod);
|
||||
|
||||
_settings.RemoveAt(mod.Index);
|
||||
}
|
||||
|
||||
/// <summary> Move all settings to unused settings for rediscovery. </summary>
|
||||
internal void PrepareModDiscovery(ModStorage mods)
|
||||
{
|
||||
foreach (var (mod, setting) in mods.Zip(_settings).Where(s => s.Second.Settings != null))
|
||||
_unused[mod.ModPath.Name] = new ModSettings.SavedSettings(setting.Settings!, mod);
|
||||
|
||||
_settings.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply all mod settings from unused settings to the current set of mods.
|
||||
/// Also fixes invalid settings.
|
||||
/// </summary>
|
||||
internal void ApplyModSettings(ModCollection parent, SaveService saver, ModStorage mods)
|
||||
{
|
||||
_settings.Capacity = Math.Max(_settings.Capacity, mods.Count);
|
||||
var settings = this;
|
||||
if (mods.Aggregate(false, (current, mod) => current | settings.AddMod(mod)))
|
||||
saver.ImmediateSave(new ModCollectionSave(mods, parent));
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ public readonly struct ResolveData(ModCollection collection, nint gameObject)
|
|||
{ }
|
||||
|
||||
public override string ToString()
|
||||
=> ModCollection.Name;
|
||||
=> ModCollection.Identity.Name;
|
||||
}
|
||||
|
||||
public static class ResolveDataExtensions
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
{
|
||||
_chat.Print(collection == null
|
||||
? $"The {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}" : string.Empty)} is already unassigned"
|
||||
: $"{collection.Name} already is the {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}");
|
||||
: $"{collection.Identity.Name} already is the {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -363,13 +363,13 @@ public class CommandHandler : IDisposable, IApiService
|
|||
}
|
||||
|
||||
Print(
|
||||
$"Removed {oldCollection.Name} as {type.ToName()} Collection assignment {(identifier.IsValid ? $" for {identifier}." : ".")}");
|
||||
$"Removed {oldCollection.Identity.Name} as {type.ToName()} Collection assignment {(identifier.IsValid ? $" for {identifier}." : ".")}");
|
||||
anySuccess = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
_collectionManager.Active.SetCollection(collection!, type, individualIndex);
|
||||
Print($"Assigned {collection!.Name} as {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}");
|
||||
Print($"Assigned {collection!.Identity.Name} as {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}");
|
||||
}
|
||||
|
||||
return anySuccess;
|
||||
|
|
@ -440,7 +440,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
|
||||
_chat.Print(new SeStringBuilder().AddText("Mod ").AddPurple(mod.Name, true)
|
||||
.AddText("already had the desired state in collection ")
|
||||
.AddYellow(collection!.Name, true).AddText(".").BuiltString);
|
||||
.AddYellow(collection!.Identity.Name, true).AddText(".").BuiltString);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -458,7 +458,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
_collectionEditor.SetModSetting(collection!, mod, groupIndex, setting);
|
||||
Print(() => new SeStringBuilder().AddText("Changed settings of group ").AddGreen(groupName, true).AddText(" in mod ")
|
||||
.AddPurple(mod.Name, true).AddText(" in collection ")
|
||||
.AddYellow(collection!.Name, true).AddText(".").BuiltString);
|
||||
.AddYellow(collection!.Identity.Name, true).AddText(".").BuiltString);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -543,7 +543,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
changes |= HandleModState(state, collection!, mod);
|
||||
|
||||
if (!changes)
|
||||
Print(() => new SeStringBuilder().AddText("No mod states were changed in collection ").AddYellow(collection!.Name, true)
|
||||
Print(() => new SeStringBuilder().AddText("No mod states were changed in collection ").AddYellow(collection!.Identity.Name, true)
|
||||
.AddText(".").BuiltString);
|
||||
|
||||
return true;
|
||||
|
|
@ -558,7 +558,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
return true;
|
||||
}
|
||||
|
||||
collection = string.Equals(lowerName, ModCollection.Empty.Name, StringComparison.OrdinalIgnoreCase)
|
||||
collection = string.Equals(lowerName, ModCollection.Empty.Identity.Name, StringComparison.OrdinalIgnoreCase)
|
||||
? ModCollection.Empty
|
||||
: _collectionManager.Storage.ByIdentifier(lowerName, out var c)
|
||||
? c
|
||||
|
|
@ -606,7 +606,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
|
||||
private bool HandleModState(int settingState, ModCollection collection, Mod mod)
|
||||
{
|
||||
var settings = collection.Settings[mod.Index];
|
||||
var settings = collection.GetOwnSettings(mod.Index);
|
||||
switch (settingState)
|
||||
{
|
||||
case 0:
|
||||
|
|
@ -614,7 +614,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
return false;
|
||||
|
||||
Print(() => new SeStringBuilder().AddText("Enabled mod ").AddPurple(mod.Name, true).AddText(" in collection ")
|
||||
.AddYellow(collection.Name, true)
|
||||
.AddYellow(collection.Identity.Name, true)
|
||||
.AddText(".").BuiltString);
|
||||
return true;
|
||||
|
||||
|
|
@ -623,7 +623,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
return false;
|
||||
|
||||
Print(() => new SeStringBuilder().AddText("Disabled mod ").AddPurple(mod.Name, true).AddText(" in collection ")
|
||||
.AddYellow(collection.Name, true)
|
||||
.AddYellow(collection.Identity.Name, true)
|
||||
.AddText(".").BuiltString);
|
||||
return true;
|
||||
|
||||
|
|
@ -634,7 +634,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
|
||||
Print(() => new SeStringBuilder().AddText(setting ? "Enabled mod " : "Disabled mod ").AddPurple(mod.Name, true)
|
||||
.AddText(" in collection ")
|
||||
.AddYellow(collection.Name, true)
|
||||
.AddYellow(collection.Identity.Name, true)
|
||||
.AddText(".").BuiltString);
|
||||
return true;
|
||||
|
||||
|
|
@ -643,7 +643,7 @@ public class CommandHandler : IDisposable, IApiService
|
|||
return false;
|
||||
|
||||
Print(() => new SeStringBuilder().AddText("Set mod ").AddPurple(mod.Name, true).AddText(" in collection ")
|
||||
.AddYellow(collection.Name, true)
|
||||
.AddYellow(collection.Identity.Name, true)
|
||||
.AddText(" to inherit.").BuiltString);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public unsafe class AtchCallerHook1 : FastHook<AtchCallerHook1.Delegate>, IDispo
|
|||
Task.Result.Original(data, slot, unk, playerModel);
|
||||
_metaState.AtchCollection.Pop();
|
||||
Penumbra.Log.Excessive(
|
||||
$"[AtchCaller1] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, identified to {collection.ModCollection.AnonymizedName}.");
|
||||
$"[AtchCaller1] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, identified to {collection.ModCollection.Identity.AnonymizedName}.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public unsafe class AtchCallerHook2 : FastHook<AtchCallerHook2.Delegate>, IDispo
|
|||
Task.Result.Original(data, slot, unk, playerModel, unk2);
|
||||
_metaState.AtchCollection.Pop();
|
||||
Penumbra.Log.Excessive(
|
||||
$"[AtchCaller2] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, {unk2}, identified to {collection.ModCollection.AnonymizedName}.");
|
||||
$"[AtchCaller2] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, {unk2}, identified to {collection.ModCollection.Identity.AnonymizedName}.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public sealed unsafe class MetaState : IDisposable, IService
|
|||
_lastCreatedCollection = _collectionResolver.IdentifyLastGameObjectCollection(true);
|
||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
|
||||
_communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
||||
_lastCreatedCollection.ModCollection.Id, (nint)modelCharaId, (nint)customize, (nint)equipData);
|
||||
_lastCreatedCollection.ModCollection.Identity.Id, (nint)modelCharaId, (nint)customize, (nint)equipData);
|
||||
|
||||
var decal = new DecalReverter(Config, _characterUtility, _resources, _lastCreatedCollection,
|
||||
UsesDecal(*(uint*)modelCharaId, (nint)customize));
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public static class PathDataHandler
|
|||
/// <summary> Create the encoding path for an IMC file. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FullPath CreateImc(CiByteString path, ModCollection collection)
|
||||
=> new($"|{collection.LocalId.Id}_{collection.ImcChangeCounter}_{DiscriminatorString}|{path}");
|
||||
=> new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Imc}_{DiscriminatorString}|{path}");
|
||||
|
||||
/// <summary> Create the encoding path for a TMB file. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
@ -47,17 +47,17 @@ public static class PathDataHandler
|
|||
/// <summary> Create the encoding path for an ATCH file. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FullPath CreateAtch(CiByteString path, ModCollection collection)
|
||||
=> new($"|{collection.LocalId.Id}_{collection.AtchChangeCounter}_{DiscriminatorString}|{path}");
|
||||
=> new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Atch}_{DiscriminatorString}|{path}");
|
||||
|
||||
/// <summary> Create the encoding path for a MTRL file. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FullPath CreateMtrl(CiByteString path, ModCollection collection, Utf8GamePath originalPath)
|
||||
=> new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{originalPath.Path.Crc32:X8}_{DiscriminatorString}|{path}");
|
||||
=> new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Change}_{originalPath.Path.Crc32:X8}_{DiscriminatorString}|{path}");
|
||||
|
||||
/// <summary> The base function shared by most file types. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static FullPath CreateBase(CiByteString path, ModCollection collection)
|
||||
=> new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{DiscriminatorString}|{path}");
|
||||
=> new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Change}_{DiscriminatorString}|{path}");
|
||||
|
||||
/// <summary> Read an additional data blurb and parse it into usable data for all file types but Materials. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
|
|||
|
|
@ -26,6 +26,6 @@ public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFileP
|
|||
|
||||
file.Replace(resource);
|
||||
Penumbra.Log.Verbose(
|
||||
$"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}.");
|
||||
$"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.Identity.AnonymizedName}.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,6 @@ public class ResourceNode : ICloneable
|
|||
public readonly record struct UiData(string? Name, ChangedItemIconFlag IconFlag)
|
||||
{
|
||||
public UiData PrependName(string prefix)
|
||||
=> Name == null ? this : new UiData(prefix + Name, IconFlag);
|
||||
=> Name == null ? this : this with { Name = prefix + Name };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class ResourceTreeFactory(
|
|||
var (name, anonymizedName, related) = GetCharacterName(character);
|
||||
var networked = character.EntityId != 0xE0000000;
|
||||
var tree = new ResourceTree(name, anonymizedName, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
||||
networked, collectionResolveData.ModCollection.Name, collectionResolveData.ModCollection.AnonymizedName);
|
||||
networked, collectionResolveData.ModCollection.Identity.Name, collectionResolveData.ModCollection.Identity.AnonymizedName);
|
||||
var globalContext = new GlobalResolveContext(metaFileManager, objectIdentifier, collectionResolveData.ModCollection,
|
||||
cache, (flags & Flags.WithUiData) != 0);
|
||||
using (var _ = pathState.EnterInternalResolve())
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public unsafe class SchedulerResourceManagementService : IService, IDisposable
|
|||
if (_actionTmbs.TryGetValue(tmb, out var rowId))
|
||||
_listedTmbIds[rowId] = tmb;
|
||||
else
|
||||
Penumbra.Log.Debug($"Action TMB {gamePath} encountered with no corresponding row ID.");
|
||||
Penumbra.Log.Verbose($"Action TMB {gamePath} encountered with no corresponding row ID.");
|
||||
}
|
||||
|
||||
[Signature(Sigs.SchedulerResourceManagementInstance, ScanType = ScanType.StaticAddress)]
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ public class ModMetaEditor(
|
|||
dict.ClearForDefault();
|
||||
|
||||
var count = 0;
|
||||
foreach (var value in clone.GlobalEqp)
|
||||
dict.TryAdd(value);
|
||||
|
||||
foreach (var (key, value) in clone.Imc)
|
||||
{
|
||||
var defaultEntry = ImcChecker.GetDefaultEntry(key, false);
|
||||
|
|
|
|||
|
|
@ -36,10 +36,11 @@ public class ModSelection : EventWrapper<Mod?, Mod?, ModSelection.Priority>
|
|||
_communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModSelection);
|
||||
}
|
||||
|
||||
public ModSettings Settings { get; private set; } = ModSettings.Empty;
|
||||
public ModCollection Collection { get; private set; } = ModCollection.Empty;
|
||||
public Mod? Mod { get; private set; }
|
||||
|
||||
public ModSettings Settings { get; private set; } = ModSettings.Empty;
|
||||
public ModCollection Collection { get; private set; } = ModCollection.Empty;
|
||||
public Mod? Mod { get; private set; }
|
||||
public ModSettings? OwnSettings { get; private set; }
|
||||
public TemporaryModSettings? TemporarySettings { get; private set; }
|
||||
|
||||
public void SelectMod(Mod? mod)
|
||||
{
|
||||
|
|
@ -83,12 +84,15 @@ public class ModSelection : EventWrapper<Mod?, Mod?, ModSelection.Priority>
|
|||
{
|
||||
if (Mod == null)
|
||||
{
|
||||
Settings = ModSettings.Empty;
|
||||
Collection = ModCollection.Empty;
|
||||
Settings = ModSettings.Empty;
|
||||
Collection = ModCollection.Empty;
|
||||
OwnSettings = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
(var settings, Collection) = _collections.Current[Mod.Index];
|
||||
(var settings, Collection) = _collections.Current.GetActualSettings(Mod.Index);
|
||||
OwnSettings = _collections.Current.GetOwnSettings(Mod.Index);
|
||||
TemporarySettings = _collections.Current.GetTempSettings(Mod.Index);
|
||||
Settings = settings ?? ModSettings.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
Penumbra/Mods/Settings/FullModSettings.cs
Normal file
19
Penumbra/Mods/Settings/FullModSettings.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
namespace Penumbra.Mods.Settings;
|
||||
|
||||
public readonly record struct FullModSettings(ModSettings? Settings = null, TemporaryModSettings? TempSettings = null)
|
||||
{
|
||||
public static readonly FullModSettings Empty = new();
|
||||
|
||||
public ModSettings? Resolve()
|
||||
{
|
||||
if (TempSettings == null)
|
||||
return Settings;
|
||||
if (TempSettings.ForceInherit)
|
||||
return null;
|
||||
|
||||
return TempSettings;
|
||||
}
|
||||
|
||||
public FullModSettings DeepCopy()
|
||||
=> new(Settings?.DeepCopy());
|
||||
}
|
||||
|
|
@ -12,7 +12,8 @@ namespace Penumbra.Mods.Settings;
|
|||
public class ModSettings
|
||||
{
|
||||
public static readonly ModSettings Empty = new();
|
||||
public SettingList Settings { get; private init; } = [];
|
||||
|
||||
public SettingList Settings { get; internal init; } = [];
|
||||
public ModPriority Priority { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
|
|
|
|||
41
Penumbra/Mods/Settings/TemporaryModSettings.cs
Normal file
41
Penumbra/Mods/Settings/TemporaryModSettings.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
namespace Penumbra.Mods.Settings;
|
||||
|
||||
public sealed class TemporaryModSettings : ModSettings
|
||||
{
|
||||
public string Source = string.Empty;
|
||||
public int Lock = 0;
|
||||
public bool ForceInherit;
|
||||
|
||||
// Create default settings for a given mod.
|
||||
public static TemporaryModSettings DefaultSettings(Mod mod, string source, int key = 0)
|
||||
=> new()
|
||||
{
|
||||
Enabled = false,
|
||||
Source = source,
|
||||
Lock = key,
|
||||
Priority = ModPriority.Default,
|
||||
Settings = SettingList.Default(mod),
|
||||
};
|
||||
|
||||
public TemporaryModSettings()
|
||||
{ }
|
||||
|
||||
public TemporaryModSettings(ModSettings? clone, string source, int key = 0)
|
||||
{
|
||||
Source = source;
|
||||
Lock = key;
|
||||
ForceInherit = clone == null;
|
||||
if (clone != null)
|
||||
{
|
||||
Enabled = clone.Enabled;
|
||||
Priority = clone.Priority;
|
||||
Settings = clone.Settings.Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModSettingsExtensions
|
||||
{
|
||||
public static bool IsTemporary(this ModSettings? settings)
|
||||
=> settings is TemporaryModSettings;
|
||||
}
|
||||
|
|
@ -63,10 +63,10 @@ public class TemporaryMod : IMod
|
|||
DirectoryInfo? dir = null;
|
||||
try
|
||||
{
|
||||
dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Name, config.ReplaceNonAsciiOnImport, true);
|
||||
dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Identity.Name, config.ReplaceNonAsciiOnImport, true);
|
||||
var fileDir = Directory.CreateDirectory(Path.Combine(dir.FullName, "files"));
|
||||
modManager.DataEditor.CreateMeta(dir, collection.Name, character ?? config.DefaultModAuthor,
|
||||
$"Mod generated from temporary collection {collection.Id} for {character ?? "Unknown Character"} with name {collection.Name}.",
|
||||
modManager.DataEditor.CreateMeta(dir, collection.Identity.Name, character ?? config.DefaultModAuthor,
|
||||
$"Mod generated from temporary collection {collection.Identity.Id} for {character ?? "Unknown Character"} with name {collection.Identity.Name}.",
|
||||
null, null);
|
||||
var mod = new Mod(dir);
|
||||
var defaultMod = mod.Default;
|
||||
|
|
@ -99,11 +99,11 @@ public class TemporaryMod : IMod
|
|||
saveService.ImmediateSaveSync(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
|
||||
modManager.AddMod(dir, false);
|
||||
Penumbra.Log.Information(
|
||||
$"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identifier}.");
|
||||
$"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identity.Identifier}.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not save temporary collection {collection.Identifier} to permanent Mod:\n{e}");
|
||||
Penumbra.Log.Error($"Could not save temporary collection {collection.Identity.Identifier} to permanent Mod:\n{e}");
|
||||
if (dir != null && Directory.Exists(dir.FullName))
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -245,24 +245,24 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
void PrintCollection(ModCollection c, CollectionCache _)
|
||||
=> sb.Append(
|
||||
$"> **`Collection {c.AnonymizedName + ':',-18}`** Inheritances: `{c.DirectlyInheritsFrom.Count,3}`, Enabled Mods: `{c.ActualSettings.Count(s => s is { Enabled: true }),4}`, Conflicts: `{c.AllConflicts.SelectMany(x => x).Sum(x => x is { HasPriority: true, Solved: true } ? x.Conflicts.Count : 0),5}/{c.AllConflicts.SelectMany(x => x).Sum(x => x.HasPriority ? x.Conflicts.Count : 0),5}`\n");
|
||||
$"> **`Collection {c.Identity.AnonymizedName + ':',-18}`** Inheritances: `{c.Inheritance.DirectlyInheritsFrom.Count,3}`, Enabled Mods: `{c.ActualSettings.Count(s => s is { Enabled: true }),4}`, Conflicts: `{c.AllConflicts.SelectMany(x => x).Sum(x => x is { HasPriority: true, Solved: true } ? x.Conflicts.Count : 0),5}/{c.AllConflicts.SelectMany(x => x).Sum(x => x.HasPriority ? x.Conflicts.Count : 0),5}`\n");
|
||||
|
||||
sb.AppendLine("**Collections**");
|
||||
sb.Append($"> **`#Collections: `** {_collectionManager.Storage.Count - 1}\n");
|
||||
sb.Append($"> **`#Temp Collections: `** {_tempCollections.Count}\n");
|
||||
sb.Append($"> **`Active Collections: `** {_collectionManager.Caches.Count}\n");
|
||||
sb.Append($"> **`Base Collection: `** {_collectionManager.Active.Default.AnonymizedName}\n");
|
||||
sb.Append($"> **`Interface Collection: `** {_collectionManager.Active.Interface.AnonymizedName}\n");
|
||||
sb.Append($"> **`Selected Collection: `** {_collectionManager.Active.Current.AnonymizedName}\n");
|
||||
sb.Append($"> **`Base Collection: `** {_collectionManager.Active.Default.Identity.AnonymizedName}\n");
|
||||
sb.Append($"> **`Interface Collection: `** {_collectionManager.Active.Interface.Identity.AnonymizedName}\n");
|
||||
sb.Append($"> **`Selected Collection: `** {_collectionManager.Active.Current.Identity.AnonymizedName}\n");
|
||||
foreach (var (type, name, _) in CollectionTypeExtensions.Special)
|
||||
{
|
||||
var collection = _collectionManager.Active.ByType(type);
|
||||
if (collection != null)
|
||||
sb.Append($"> **`{name,-29}`** {collection.AnonymizedName}\n");
|
||||
sb.Append($"> **`{name,-29}`** {collection.Identity.AnonymizedName}\n");
|
||||
}
|
||||
|
||||
foreach (var (name, id, collection) in _collectionManager.Active.Individuals.Assignments)
|
||||
sb.Append($"> **`{id[0].Incognito(name) + ':',-29}`** {collection.AnonymizedName}\n");
|
||||
sb.Append($"> **`{id[0].Incognito(name) + ':',-29}`** {collection.Identity.AnonymizedName}\n");
|
||||
|
||||
foreach (var collection in _collectionManager.Caches.Active)
|
||||
PrintCollection(collection, collection._cache!);
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu
|
|||
private Configuration _config = null!;
|
||||
private JObject _data = null!;
|
||||
|
||||
public string CurrentCollection = ModCollection.DefaultCollectionName;
|
||||
public string DefaultCollection = ModCollection.DefaultCollectionName;
|
||||
public string CurrentCollection = ModCollectionIdentity.DefaultCollectionName;
|
||||
public string DefaultCollection = ModCollectionIdentity.DefaultCollectionName;
|
||||
public string ForcedCollection = string.Empty;
|
||||
public Dictionary<string, string> CharacterCollections = [];
|
||||
public Dictionary<string, string> ModSortOrder = [];
|
||||
|
|
@ -240,7 +240,7 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu
|
|||
if (jObject["Name"]?.ToObject<string>() == ForcedCollection)
|
||||
continue;
|
||||
|
||||
jObject[nameof(ModCollection.DirectlyInheritsFrom)] = JToken.FromObject(new List<string> { ForcedCollection });
|
||||
jObject[nameof(ModCollectionInheritance.DirectlyInheritsFrom)] = JToken.FromObject(new List<string> { ForcedCollection });
|
||||
File.WriteAllText(collection.FullName, jObject.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -346,7 +346,7 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu
|
|||
if (!collectionJson.Exists)
|
||||
return;
|
||||
|
||||
var defaultCollectionFile = new FileInfo(saveService.FileNames.CollectionFile(ModCollection.DefaultCollectionName));
|
||||
var defaultCollectionFile = new FileInfo(saveService.FileNames.CollectionFile(ModCollectionIdentity.DefaultCollectionName));
|
||||
if (defaultCollectionFile.Exists)
|
||||
return;
|
||||
|
||||
|
|
@ -380,7 +380,7 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu
|
|||
|
||||
var emptyStorage = new ModStorage();
|
||||
// Only used for saving and immediately discarded, so the local collection id here is irrelevant.
|
||||
var collection = ModCollection.CreateFromData(saveService, emptyStorage, Guid.NewGuid(), ModCollection.DefaultCollectionName, LocalCollectionId.Zero, 0, 1, dict, []);
|
||||
var collection = ModCollection.CreateFromData(saveService, emptyStorage, ModCollectionIdentity.New(ModCollectionIdentity.DefaultCollectionName, LocalCollectionId.Zero, 1), 0, dict, []);
|
||||
saveService.ImmediateSaveSync(new ModCollectionSave(emptyStorage, collection));
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ public sealed class CrashHandlerService : IDisposable, IService
|
|||
var name = GetActorName(character);
|
||||
lock (_eventWriter)
|
||||
{
|
||||
_eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Id, type);
|
||||
_eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Identity.Id, type);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -293,7 +293,7 @@ public sealed class CrashHandlerService : IDisposable, IService
|
|||
var name = GetActorName(resolveData.AssociatedGameObject);
|
||||
lock (_eventWriter)
|
||||
{
|
||||
_eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Id,
|
||||
_eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Identity.Id,
|
||||
manipulatedPath.Value.InternalName.Span, originalPath.Path.Span);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public class FilenameService(IDalamudPluginInterface pi) : IService
|
|||
|
||||
/// <summary> Obtain the path of a collection file given its name.</summary>
|
||||
public string CollectionFile(ModCollection collection)
|
||||
=> CollectionFile(collection.Identifier);
|
||||
=> CollectionFile(collection.Identity.Identifier);
|
||||
|
||||
/// <summary> Obtain the path of a collection file given its name. </summary>
|
||||
public string CollectionFile(string collectionName)
|
||||
|
|
|
|||
|
|
@ -737,12 +737,12 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
|
|||
if (collectionType is not CollectionType.Current || _mod == null || newCollection == null)
|
||||
return;
|
||||
|
||||
UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection[_mod.Index].Settings : null);
|
||||
UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection.GetInheritedSettings(_mod.Index).Settings : null);
|
||||
}
|
||||
|
||||
private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool inherited)
|
||||
{
|
||||
if (collection != _collectionManager.Active.Current || mod != _mod)
|
||||
if (collection != _collectionManager.Active.Current || mod != _mod || type is ModSettingChange.TemporarySetting)
|
||||
return;
|
||||
|
||||
_swapData.LoadMod(_mod, _modSettings);
|
||||
|
|
@ -754,7 +754,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
|
|||
if (collection != _collectionManager.Active.Current || _mod == null)
|
||||
return;
|
||||
|
||||
UpdateMod(_mod, collection[_mod.Index].Settings);
|
||||
UpdateMod(_mod, collection.GetInheritedSettings(_mod.Index).Settings);
|
||||
_swapData.LoadMod(_mod, _modSettings);
|
||||
_dirty = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService
|
|||
_modelTab.Reset();
|
||||
_materialTab.Reset();
|
||||
_shaderPackageTab.Reset();
|
||||
_itemSwapTab.UpdateMod(mod, _activeCollections.Current[mod.Index].Settings);
|
||||
_itemSwapTab.UpdateMod(mod, _activeCollections.Current.GetInheritedSettings(mod.Index).Settings);
|
||||
UpdateModels();
|
||||
_forceTextureStartPath = true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public class CollectionSelectHeader : IUiService
|
|||
_selection = selection;
|
||||
_resolver = resolver;
|
||||
_activeCollections = collectionManager.Active;
|
||||
_collectionCombo = new CollectionCombo(collectionManager, () => collectionManager.Storage.OrderBy(c => c.Name).ToList());
|
||||
_collectionCombo = new CollectionCombo(collectionManager, () => collectionManager.Storage.OrderBy(c => c.Identity.Name).ToList());
|
||||
}
|
||||
|
||||
/// <summary> Draw the header line that can quick switch between collections. </summary>
|
||||
|
|
@ -77,10 +77,10 @@ public class CollectionSelectHeader : IUiService
|
|||
return CheckCollection(collection) switch
|
||||
{
|
||||
CollectionState.Empty => (collection, "None", "The base collection is configured to use no mods.", true),
|
||||
CollectionState.Selected => (collection, collection.Name,
|
||||
CollectionState.Selected => (collection, collection.Identity.Name,
|
||||
"The configured base collection is already selected as the current collection.", true),
|
||||
CollectionState.Available => (collection, collection.Name,
|
||||
$"Select the configured base collection {collection.Name} as the current collection.", false),
|
||||
CollectionState.Available => (collection, collection.Identity.Name,
|
||||
$"Select the configured base collection {collection.Identity.Name} as the current collection.", false),
|
||||
_ => throw new Exception("Can not happen."),
|
||||
};
|
||||
}
|
||||
|
|
@ -91,10 +91,11 @@ public class CollectionSelectHeader : IUiService
|
|||
return CheckCollection(collection) switch
|
||||
{
|
||||
CollectionState.Empty => (collection, "None", "The loaded player character is configured to use no mods.", true),
|
||||
CollectionState.Selected => (collection, collection.Name,
|
||||
CollectionState.Selected => (collection, collection.Identity.Name,
|
||||
"The collection configured to apply to the loaded player character is already selected as the current collection.", true),
|
||||
CollectionState.Available => (collection, collection.Name,
|
||||
$"Select the collection {collection.Name} that applies to the loaded player character as the current collection.", false),
|
||||
CollectionState.Available => (collection, collection.Identity.Name,
|
||||
$"Select the collection {collection.Identity.Name} that applies to the loaded player character as the current collection.",
|
||||
false),
|
||||
_ => throw new Exception("Can not happen."),
|
||||
};
|
||||
}
|
||||
|
|
@ -105,10 +106,10 @@ public class CollectionSelectHeader : IUiService
|
|||
return CheckCollection(collection) switch
|
||||
{
|
||||
CollectionState.Empty => (collection, "None", "The interface collection is configured to use no mods.", true),
|
||||
CollectionState.Selected => (collection, collection.Name,
|
||||
CollectionState.Selected => (collection, collection.Identity.Name,
|
||||
"The configured interface collection is already selected as the current collection.", true),
|
||||
CollectionState.Available => (collection, collection.Name,
|
||||
$"Select the configured interface collection {collection.Name} as the current collection.", false),
|
||||
CollectionState.Available => (collection, collection.Identity.Name,
|
||||
$"Select the configured interface collection {collection.Identity.Name} as the current collection.", false),
|
||||
_ => throw new Exception("Can not happen."),
|
||||
};
|
||||
}
|
||||
|
|
@ -120,8 +121,8 @@ public class CollectionSelectHeader : IUiService
|
|||
{
|
||||
CollectionState.Unavailable => (null, "Not Inherited",
|
||||
"The settings of the selected mod are not inherited from another collection.", true),
|
||||
CollectionState.Available => (collection, collection!.Name,
|
||||
$"Select the collection {collection!.Name} from which the selected mod inherits its settings as the current collection.",
|
||||
CollectionState.Available => (collection, collection!.Identity.Name,
|
||||
$"Select the collection {collection!.Identity.Name} from which the selected mod inherits its settings as the current collection.",
|
||||
false),
|
||||
_ => throw new Exception("Can not happen."),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using ImGuiNET;
|
||||
using OtterGui.Custom;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public enum ColorId
|
||||
public enum ColorId : short
|
||||
{
|
||||
EnabledMod,
|
||||
DisabledMod,
|
||||
|
|
@ -10,6 +11,7 @@ public enum ColorId
|
|||
InheritedMod,
|
||||
InheritedDisabledMod,
|
||||
NewMod,
|
||||
NewModTint,
|
||||
ConflictingMod,
|
||||
HandledConflictMod,
|
||||
FolderExpanded,
|
||||
|
|
@ -31,6 +33,8 @@ public enum ColorId
|
|||
ResTreeNonNetworked,
|
||||
PredefinedTagAdd,
|
||||
PredefinedTagRemove,
|
||||
TemporaryModSettingsTint,
|
||||
NoTint,
|
||||
}
|
||||
|
||||
public static class Colors
|
||||
|
|
@ -48,38 +52,65 @@ public static class Colors
|
|||
public const uint ReniColorHovered = CustomGui.ReniColorHovered;
|
||||
public const uint ReniColorActive = CustomGui.ReniColorActive;
|
||||
|
||||
public static uint Tinted(this ColorId color, ColorId tint)
|
||||
{
|
||||
var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value());
|
||||
var value = ImGui.ColorConvertU32ToFloat4(color.Value());
|
||||
return ImGui.ColorConvertFloat4ToU32(TintColor(value, tintValue));
|
||||
}
|
||||
|
||||
public static unsafe uint Tinted(this ImGuiCol color, ColorId tint)
|
||||
{
|
||||
var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value());
|
||||
ref var value = ref *ImGui.GetStyleColorVec4(color);
|
||||
return ImGui.ColorConvertFloat4ToU32(TintColor(value, tintValue));
|
||||
}
|
||||
|
||||
private static unsafe Vector4 TintColor(in Vector4 color, in Vector4 tint)
|
||||
{
|
||||
var negAlpha = 1 - tint.W;
|
||||
var newAlpha = negAlpha * color.W + tint.W;
|
||||
var newR = (negAlpha * color.W * color.X + tint.W * tint.X) / newAlpha;
|
||||
var newG = (negAlpha * color.W * color.Y + tint.W * tint.Y) / newAlpha;
|
||||
var newB = (negAlpha * color.W * color.Z + tint.W * tint.Z) / newAlpha;
|
||||
return new Vector4(newR, newG, newB, newAlpha);
|
||||
}
|
||||
|
||||
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
|
||||
=> color switch
|
||||
{
|
||||
// @formatter:off
|
||||
ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ),
|
||||
ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ),
|
||||
ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ),
|
||||
ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ),
|
||||
ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."),
|
||||
ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ),
|
||||
ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ),
|
||||
ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ),
|
||||
ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ),
|
||||
ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ),
|
||||
ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ),
|
||||
ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ),
|
||||
ColorId.IncreasedMetaValue => ( 0x80008000, "Increased Meta Manipulation Value", "An increased meta manipulation value for floats or an enabled toggle where the default is disabled."),
|
||||
ColorId.DecreasedMetaValue => ( 0x80000080, "Decreased Meta Manipulation Value", "A decreased meta manipulation value for floats or a disabled toggle where the default is enabled."),
|
||||
ColorId.SelectedCollection => ( 0x6069C056, "Currently Selected Collection", "The collection that is currently selected and being edited."),
|
||||
ColorId.RedundantAssignment => ( 0x6050D0D0, "Redundant Collection Assignment", "A collection assignment that currently has no effect as it is redundant with more general assignments."),
|
||||
ColorId.NoModsAssignment => ( 0x50000080, "'Use No Mods' Collection Assignment", "A collection assignment set to not use any mods at all."),
|
||||
ColorId.NoAssignment => ( 0x00000000, "Unassigned Collection Assignment", "A collection assignment that is not configured to any collection and thus just has no specific treatment."),
|
||||
ColorId.SelectorPriority => ( 0xFF808080, "Mod Selector Priority", "The priority displayed for non-zero priority mods in the mod selector."),
|
||||
ColorId.InGameHighlight => ( 0xFFEBCF89, "In-Game Highlight (Primary)", "An in-game element that has been highlighted for ease of editing."),
|
||||
ColorId.InGameHighlight2 => ( 0xFF446CC0, "In-Game Highlight (Secondary)", "Another in-game element that has been highlighted for ease of editing."),
|
||||
ColorId.ResTreeLocalPlayer => ( 0xFFFFE0A0, "On-Screen: You", "You and what you own (mount, minion, accessory, pets and so on), in the On-Screen tab." ),
|
||||
ColorId.ResTreePlayer => ( 0xFFC0FFC0, "On-Screen: Other Players", "Other players and what they own, in the On-Screen tab." ),
|
||||
ColorId.ResTreeNetworked => ( 0xFFFFFFFF, "On-Screen: Non-Players (Networked)", "Non-player entities handled by the game server, in the On-Screen tab." ),
|
||||
ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ),
|
||||
ColorId.PredefinedTagAdd => ( 0xFF44AA44, "Predefined Tags: Add Tag", "A predefined tag that is not present on the current mod and can be added." ),
|
||||
ColorId.PredefinedTagRemove => ( 0xFF2222AA, "Predefined Tags: Remove Tag", "A predefined tag that is already present on the current mod and can be removed." ),
|
||||
_ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ),
|
||||
ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ),
|
||||
ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ),
|
||||
ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ),
|
||||
ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ),
|
||||
ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."),
|
||||
ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ),
|
||||
ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ),
|
||||
ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ),
|
||||
ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ),
|
||||
ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ),
|
||||
ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ),
|
||||
ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ),
|
||||
ColorId.IncreasedMetaValue => ( 0x80008000, "Increased Meta Manipulation Value", "An increased meta manipulation value for floats or an enabled toggle where the default is disabled."),
|
||||
ColorId.DecreasedMetaValue => ( 0x80000080, "Decreased Meta Manipulation Value", "A decreased meta manipulation value for floats or a disabled toggle where the default is enabled."),
|
||||
ColorId.SelectedCollection => ( 0x6069C056, "Currently Selected Collection", "The collection that is currently selected and being edited."),
|
||||
ColorId.RedundantAssignment => ( 0x6050D0D0, "Redundant Collection Assignment", "A collection assignment that currently has no effect as it is redundant with more general assignments."),
|
||||
ColorId.NoModsAssignment => ( 0x50000080, "'Use No Mods' Collection Assignment", "A collection assignment set to not use any mods at all."),
|
||||
ColorId.NoAssignment => ( 0x00000000, "Unassigned Collection Assignment", "A collection assignment that is not configured to any collection and thus just has no specific treatment."),
|
||||
ColorId.SelectorPriority => ( 0xFF808080, "Mod Selector Priority", "The priority displayed for non-zero priority mods in the mod selector."),
|
||||
ColorId.InGameHighlight => ( 0xFFEBCF89, "In-Game Highlight (Primary)", "An in-game element that has been highlighted for ease of editing."),
|
||||
ColorId.InGameHighlight2 => ( 0xFF446CC0, "In-Game Highlight (Secondary)", "Another in-game element that has been highlighted for ease of editing."),
|
||||
ColorId.ResTreeLocalPlayer => ( 0xFFFFE0A0, "On-Screen: You", "You and what you own (mount, minion, accessory, pets and so on), in the On-Screen tab." ),
|
||||
ColorId.ResTreePlayer => ( 0xFFC0FFC0, "On-Screen: Other Players", "Other players and what they own, in the On-Screen tab." ),
|
||||
ColorId.ResTreeNetworked => ( 0xFFFFFFFF, "On-Screen: Non-Players (Networked)", "Non-player entities handled by the game server, in the On-Screen tab." ),
|
||||
ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ),
|
||||
ColorId.PredefinedTagAdd => ( 0xFF44AA44, "Predefined Tags: Add Tag", "A predefined tag that is not present on the current mod and can be added." ),
|
||||
ColorId.PredefinedTagRemove => ( 0xFF2222AA, "Predefined Tags: Remove Tag", "A predefined tag that is already present on the current mod and can be removed." ),
|
||||
ColorId.TemporaryModSettingsTint => ( 0x30FF0000, "Mod with Temporary Settings", "A mod that has temporary settings. This color is used as a tint for the regular state colors." ),
|
||||
ColorId.NewModTint => ( 0x8000FF00, "New Mod Tint", "A mod that was newly imported or created during this session and has not been enabled yet. This color is used as a tint for the regular state colors."),
|
||||
ColorId.NoTint => ( 0x00000000, "No Tint", "The default tint for all mods."),
|
||||
_ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ),
|
||||
// @formatter:on
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ public sealed class CollectionCombo(CollectionManager manager, Func<IReadOnlyLis
|
|||
}
|
||||
|
||||
_color.Push(ImGuiCol.FrameBg, color).Push(ImGuiCol.FrameBgHovered, color);
|
||||
if (Draw(label, current.Name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) && CurrentSelection != null)
|
||||
if (Draw(label, current.Identity.Name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) && CurrentSelection != null)
|
||||
manager.Active.SetCollection(CurrentSelection, CollectionType.Current);
|
||||
_color.Dispose();
|
||||
}
|
||||
|
||||
protected override string ToString(ModCollection obj)
|
||||
=> obj.Name;
|
||||
=> obj.Identity.Name;
|
||||
|
||||
protected override void DrawCombo(string label, string preview, string tooltip, int currentSelected, float previewWidth, float itemHeight,
|
||||
ImGuiComboFlags flags)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Penumbra.Collections.Manager;
|
|||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Mods.Settings;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
|
|
@ -221,16 +222,16 @@ public sealed class CollectionPanel(
|
|||
ImGui.SameLine();
|
||||
ImGui.BeginGroup();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
|
||||
var name = _newName ?? collection.Name;
|
||||
var identifier = collection.Identifier;
|
||||
var name = _newName ?? collection.Identity.Name;
|
||||
var identifier = collection.Identity.Identifier;
|
||||
var width = ImGui.GetContentRegionAvail().X;
|
||||
var fileName = saveService.FileNames.CollectionFile(collection);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
if (ImGui.InputText("##name", ref name, 128))
|
||||
_newName = name;
|
||||
if (ImGui.IsItemDeactivatedAfterEdit() && _newName != null && _newName != collection.Name)
|
||||
if (ImGui.IsItemDeactivatedAfterEdit() && _newName != null && _newName != collection.Identity.Name)
|
||||
{
|
||||
collection.Name = _newName;
|
||||
collection.Identity.Name = _newName;
|
||||
saveService.QueueSave(new ModCollectionSave(mods, collection));
|
||||
selector.RestoreCollections();
|
||||
_newName = null;
|
||||
|
|
@ -242,7 +243,7 @@ public sealed class CollectionPanel(
|
|||
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
if (ImGui.Button(collection.Identifier, new Vector2(width, 0)))
|
||||
if (ImGui.Button(collection.Identity.Identifier, new Vector2(width, 0)))
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });
|
||||
|
|
@ -289,9 +290,9 @@ public sealed class CollectionPanel(
|
|||
_active.SetCollection(null, type, _active.Individuals.GetGroup(identifier));
|
||||
}
|
||||
|
||||
foreach (var coll in _collections.OrderBy(c => c.Name))
|
||||
foreach (var coll in _collections.OrderBy(c => c.Identity.Name))
|
||||
{
|
||||
if (coll != collection && ImGui.MenuItem($"Use {coll.Name}."))
|
||||
if (coll != collection && ImGui.MenuItem($"Use {coll.Identity.Name}."))
|
||||
_active.SetCollection(coll, type, _active.Individuals.GetGroup(identifier));
|
||||
}
|
||||
}
|
||||
|
|
@ -418,7 +419,7 @@ public sealed class CollectionPanel(
|
|||
private string Name(ModCollection? collection)
|
||||
=> collection == null ? "Unassigned" :
|
||||
collection == ModCollection.Empty ? "Use No Mods" :
|
||||
incognito.IncognitoMode ? collection.AnonymizedName : collection.Name;
|
||||
incognito.IncognitoMode ? collection.Identity.AnonymizedName : collection.Identity.Name;
|
||||
|
||||
private void DrawIndividualButton(string intro, Vector2 width, string tooltip, char suffix, params ActorIdentifier[] identifiers)
|
||||
{
|
||||
|
|
@ -497,7 +498,7 @@ public sealed class CollectionPanel(
|
|||
ImGui.Separator();
|
||||
|
||||
var buttonHeight = 2 * ImGui.GetTextLineHeightWithSpacing();
|
||||
if (_inUseCache.Count == 0 && collection.DirectParentOf.Count == 0)
|
||||
if (_inUseCache.Count == 0 && collection.Inheritance.DirectlyInheritedBy.Count == 0)
|
||||
{
|
||||
ImGui.Dummy(Vector2.One);
|
||||
using var f = _nameFont.Push();
|
||||
|
|
@ -559,7 +560,7 @@ public sealed class CollectionPanel(
|
|||
|
||||
private void DrawInheritanceStatistics(ModCollection collection, Vector2 buttonWidth)
|
||||
{
|
||||
if (collection.DirectParentOf.Count <= 0)
|
||||
if (collection.Inheritance.DirectlyInheritedBy.Count <= 0)
|
||||
return;
|
||||
|
||||
using (var _ = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero))
|
||||
|
|
@ -570,11 +571,11 @@ public sealed class CollectionPanel(
|
|||
using var f = _nameFont.Push();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.MetaInfoText);
|
||||
ImGuiUtil.DrawTextButton(Name(collection.DirectParentOf[0]), Vector2.Zero, 0);
|
||||
ImGuiUtil.DrawTextButton(Name(collection.Inheritance.DirectlyInheritedBy[0]), Vector2.Zero, 0);
|
||||
var constOffset = (ImGui.GetStyle().FramePadding.X + ImGuiHelpers.GlobalScale) * 2
|
||||
+ ImGui.GetStyle().ItemSpacing.X
|
||||
+ ImGui.GetStyle().WindowPadding.X;
|
||||
foreach (var parent in collection.DirectParentOf.Skip(1))
|
||||
foreach (var parent in collection.Inheritance.DirectlyInheritedBy.Skip(1))
|
||||
{
|
||||
var name = Name(parent);
|
||||
var size = ImGui.CalcTextSize(name).X;
|
||||
|
|
@ -602,7 +603,7 @@ public sealed class CollectionPanel(
|
|||
ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, 1.75f * ImGui.GetFrameHeight());
|
||||
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, 2.5f * ImGui.GetFrameHeight());
|
||||
ImGui.TableHeadersRow();
|
||||
foreach (var (mod, (settings, parent)) in mods.Select(m => (m, collection[m.Index]))
|
||||
foreach (var (mod, (settings, parent)) in mods.Select(m => (m, collection.GetInheritedSettings(m.Index)))
|
||||
.Where(t => t.Item2.Settings != null)
|
||||
.OrderBy(t => t.m.Name))
|
||||
{
|
||||
|
|
@ -625,12 +626,12 @@ public sealed class CollectionPanel(
|
|||
|
||||
private void DrawInactiveSettingsList(ModCollection collection)
|
||||
{
|
||||
if (collection.UnusedSettings.Count == 0)
|
||||
if (collection.Settings.Unused.Count == 0)
|
||||
return;
|
||||
|
||||
ImGui.Dummy(Vector2.One);
|
||||
var text = collection.UnusedSettings.Count > 1
|
||||
? $"Clear all {collection.UnusedSettings.Count} unused settings from deleted mods."
|
||||
var text = collection.Settings.Unused.Count > 1
|
||||
? $"Clear all {collection.Settings.Unused.Count} unused settings from deleted mods."
|
||||
: "Clear the currently unused setting from a deleted mods.";
|
||||
if (ImGui.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0)))
|
||||
_collections.CleanUnavailableSettings(collection);
|
||||
|
|
@ -638,7 +639,7 @@ public sealed class CollectionPanel(
|
|||
ImGui.Dummy(Vector2.One);
|
||||
|
||||
var size = new Vector2(ImGui.GetContentRegionAvail().X,
|
||||
Math.Min(10, collection.UnusedSettings.Count + 1) * ImGui.GetFrameHeightWithSpacing());
|
||||
Math.Min(10, collection.Settings.Unused.Count + 1) * ImGui.GetFrameHeightWithSpacing());
|
||||
using var table = ImRaii.Table("##inactiveSettings", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, size);
|
||||
if (!table)
|
||||
return;
|
||||
|
|
@ -650,7 +651,7 @@ public sealed class CollectionPanel(
|
|||
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, 2.5f * ImGui.GetFrameHeight());
|
||||
ImGui.TableHeadersRow();
|
||||
string? delete = null;
|
||||
foreach (var (name, settings) in collection.UnusedSettings.OrderBy(n => n.Key))
|
||||
foreach (var (name, settings) in collection.Settings.Unused.OrderBy(n => n.Key))
|
||||
{
|
||||
using var id = ImRaii.PushId(name);
|
||||
ImGui.TableNextColumn();
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
|||
}
|
||||
|
||||
protected override bool Filtered(int idx)
|
||||
=> !Items[idx].Name.Contains(Filter, StringComparison.OrdinalIgnoreCase);
|
||||
=> !Items[idx].Identity.Name.Contains(Filter, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private const string PayloadString = "Collection";
|
||||
|
||||
|
|
@ -111,12 +111,12 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
|||
}
|
||||
|
||||
private string Name(ModCollection collection)
|
||||
=> _incognito.IncognitoMode || collection.Name.Length == 0 ? collection.AnonymizedName : collection.Name;
|
||||
=> _incognito.IncognitoMode || collection.Identity.Name.Length == 0 ? collection.Identity.AnonymizedName : collection.Identity.Name;
|
||||
|
||||
public void RestoreCollections()
|
||||
{
|
||||
Items.Clear();
|
||||
foreach (var c in _storage.OrderBy(c => c.Name))
|
||||
foreach (var c in _storage.OrderBy(c => c.Identity.Name))
|
||||
Items.Add(c);
|
||||
SetFilterDirty();
|
||||
SetCurrent(_active.Current);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
/// </summary>
|
||||
private void DrawInheritedChildren(ModCollection collection)
|
||||
{
|
||||
using var id = ImRaii.PushId(collection.Index);
|
||||
using var id = ImRaii.PushId(collection.Identity.Index);
|
||||
using var indent = ImRaii.PushIndent();
|
||||
|
||||
// Get start point for the lines (top of the selector).
|
||||
|
|
@ -107,14 +107,14 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
var lineEnd = lineStart;
|
||||
|
||||
// Skip the collection itself.
|
||||
foreach (var inheritance in collection.GetFlattenedInheritance().Skip(1))
|
||||
foreach (var inheritance in collection.Inheritance.FlatHierarchy.Skip(1))
|
||||
{
|
||||
// Draw the child, already seen collections are colored as conflicts.
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(),
|
||||
_seenInheritedCollections.Contains(inheritance));
|
||||
_seenInheritedCollections.Add(inheritance);
|
||||
|
||||
ImRaii.TreeNode($"{Name(inheritance)}###{inheritance.Id}",
|
||||
ImRaii.TreeNode($"{Name(inheritance)}###{inheritance.Identity.Id}",
|
||||
ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet);
|
||||
var (minRect, maxRect) = (ImGui.GetItemRectMin(), ImGui.GetItemRectMax());
|
||||
DrawInheritanceTreeClicks(inheritance, false);
|
||||
|
|
@ -140,7 +140,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(),
|
||||
_seenInheritedCollections.Contains(collection));
|
||||
_seenInheritedCollections.Add(collection);
|
||||
using var tree = ImRaii.TreeNode($"{Name(collection)}###{collection.Name}", ImGuiTreeNodeFlags.NoTreePushOnOpen);
|
||||
using var tree = ImRaii.TreeNode($"{Name(collection)}###{collection.Identity.Name}", ImGuiTreeNodeFlags.NoTreePushOnOpen);
|
||||
color.Pop();
|
||||
DrawInheritanceTreeClicks(collection, true);
|
||||
DrawInheritanceDropSource(collection);
|
||||
|
|
@ -150,7 +150,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
DrawInheritedChildren(collection);
|
||||
else
|
||||
// We still want to keep track of conflicts.
|
||||
_seenInheritedCollections.UnionWith(collection.GetFlattenedInheritance());
|
||||
_seenInheritedCollections.UnionWith(collection.Inheritance.FlatHierarchy);
|
||||
}
|
||||
|
||||
/// <summary> Draw the list box containing the current inheritance information. </summary>
|
||||
|
|
@ -163,7 +163,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
|
||||
_seenInheritedCollections.Clear();
|
||||
_seenInheritedCollections.Add(_active.Current);
|
||||
foreach (var collection in _active.Current.DirectlyInheritsFrom.ToList())
|
||||
foreach (var collection in _active.Current.Inheritance.DirectlyInheritsFrom.ToList())
|
||||
DrawInheritance(collection);
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
|
||||
using var target = ImRaii.DragDropTarget();
|
||||
if (target.Success && ImGuiUtil.IsDropping(InheritanceDragDropLabel))
|
||||
_inheritanceAction = (_active.Current.DirectlyInheritsFrom.IndexOf(_movedInheritance!), -1);
|
||||
_inheritanceAction = (_active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(_movedInheritance!), -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -244,7 +244,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
{
|
||||
ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton);
|
||||
_newInheritance ??= _collections.FirstOrDefault(c
|
||||
=> c != _active.Current && !_active.Current.DirectlyInheritsFrom.Contains(c))
|
||||
=> c != _active.Current && !_active.Current.Inheritance.DirectlyInheritsFrom.Contains(c))
|
||||
?? ModCollection.Empty;
|
||||
using var combo = ImRaii.Combo("##newInheritance", Name(_newInheritance));
|
||||
if (!combo)
|
||||
|
|
@ -252,7 +252,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
|
||||
foreach (var collection in _collections
|
||||
.Where(c => InheritanceManager.CheckValidInheritance(_active.Current, c) == InheritanceManager.ValidInheritance.Valid)
|
||||
.OrderBy(c => c.Name))
|
||||
.OrderBy(c => c.Identity.Name))
|
||||
{
|
||||
if (ImGui.Selectable(Name(collection), _newInheritance == collection))
|
||||
_newInheritance = collection;
|
||||
|
|
@ -271,8 +271,8 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
|
||||
if (_movedInheritance != null)
|
||||
{
|
||||
var idx1 = _active.Current.DirectlyInheritsFrom.IndexOf(_movedInheritance);
|
||||
var idx2 = _active.Current.DirectlyInheritsFrom.IndexOf(collection);
|
||||
var idx1 = _active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(_movedInheritance);
|
||||
var idx2 = _active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(collection);
|
||||
if (idx1 >= 0 && idx2 >= 0)
|
||||
_inheritanceAction = (idx1, idx2);
|
||||
}
|
||||
|
|
@ -302,7 +302,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
{
|
||||
if (withDelete && ImGui.GetIO().KeyShift)
|
||||
_inheritanceAction = (_active.Current.DirectlyInheritsFrom.IndexOf(collection), -1);
|
||||
_inheritanceAction = (_active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(collection), -1);
|
||||
else
|
||||
_newCurrentCollection = collection;
|
||||
}
|
||||
|
|
@ -312,5 +312,5 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService
|
|||
}
|
||||
|
||||
private string Name(ModCollection collection)
|
||||
=> incognito.IncognitoMode ? collection.AnonymizedName : collection.Name;
|
||||
=> incognito.IncognitoMode ? collection.Identity.AnonymizedName : collection.Identity.Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
|
@ -16,13 +17,19 @@ namespace Penumbra.UI.ModsTab.Groups;
|
|||
public sealed class ModGroupDrawer(Configuration config, CollectionManager collectionManager) : IUiService
|
||||
{
|
||||
private readonly List<(IModGroup, int)> _blockGroupCache = [];
|
||||
private bool _temporary;
|
||||
private bool _locked;
|
||||
private TemporaryModSettings? _tempSettings;
|
||||
|
||||
public void Draw(Mod mod, ModSettings settings)
|
||||
public void Draw(Mod mod, ModSettings settings, TemporaryModSettings? tempSettings)
|
||||
{
|
||||
if (mod.Groups.Count <= 0)
|
||||
return;
|
||||
|
||||
_blockGroupCache.Clear();
|
||||
_tempSettings = tempSettings;
|
||||
_temporary = tempSettings != null;
|
||||
_locked = (tempSettings?.Lock ?? 0) > 0;
|
||||
var useDummy = true;
|
||||
foreach (var (group, idx) in mod.Groups.WithIndex())
|
||||
{
|
||||
|
|
@ -63,22 +70,23 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
/// </summary>
|
||||
private void DrawSingleGroupCombo(IModGroup group, int groupIdx, Setting setting)
|
||||
{
|
||||
using var id = ImRaii.PushId(groupIdx);
|
||||
var selectedOption = setting.AsIndex;
|
||||
using var id = ImUtf8.PushId(groupIdx);
|
||||
var selectedOption = setting.AsIndex;
|
||||
using var disabled = ImRaii.Disabled(_locked);
|
||||
ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X * 3 / 4);
|
||||
var options = group.Options;
|
||||
using (var combo = ImRaii.Combo(string.Empty, options[selectedOption].Name))
|
||||
using (var combo = ImUtf8.Combo(""u8, options[selectedOption].Name))
|
||||
{
|
||||
if (combo)
|
||||
for (var idx2 = 0; idx2 < options.Count; ++idx2)
|
||||
{
|
||||
id.Push(idx2);
|
||||
var option = options[idx2];
|
||||
if (ImGui.Selectable(option.Name, idx2 == selectedOption))
|
||||
if (ImUtf8.Selectable(option.Name, idx2 == selectedOption))
|
||||
SetModSetting(group, groupIdx, Setting.Single(idx2));
|
||||
|
||||
if (option.Description.Length > 0)
|
||||
ImGuiUtil.SelectableHelpMarker(option.Description);
|
||||
ImUtf8.SelectableHelpMarker(option.Description);
|
||||
|
||||
id.Pop();
|
||||
}
|
||||
|
|
@ -86,9 +94,9 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
|
||||
ImGui.SameLine();
|
||||
if (group.Description.Length > 0)
|
||||
ImGuiUtil.LabeledHelpMarker(group.Name, group.Description);
|
||||
ImUtf8.LabeledHelpMarker(group.Name, group.Description);
|
||||
else
|
||||
ImGui.TextUnformatted(group.Name);
|
||||
ImUtf8.Text(group.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -97,10 +105,10 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
/// </summary>
|
||||
private void DrawSingleGroupRadio(IModGroup group, int groupIdx, Setting setting)
|
||||
{
|
||||
using var id = ImRaii.PushId(groupIdx);
|
||||
var selectedOption = setting.AsIndex;
|
||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
||||
var options = group.Options;
|
||||
using var id = ImUtf8.PushId(groupIdx);
|
||||
var selectedOption = setting.AsIndex;
|
||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
||||
var options = group.Options;
|
||||
DrawCollapseHandling(options, minWidth, DrawOptions);
|
||||
|
||||
Widget.EndFramedGroup();
|
||||
|
|
@ -108,11 +116,12 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
|
||||
void DrawOptions()
|
||||
{
|
||||
using var disabled = ImRaii.Disabled(_locked);
|
||||
for (var idx = 0; idx < group.Options.Count; ++idx)
|
||||
{
|
||||
using var i = ImRaii.PushId(idx);
|
||||
var option = options[idx];
|
||||
if (ImGui.RadioButton(option.Name, selectedOption == idx))
|
||||
using var i = ImUtf8.PushId(idx);
|
||||
var option = options[idx];
|
||||
if (ImUtf8.RadioButton(option.Name, selectedOption == idx))
|
||||
SetModSetting(group, groupIdx, Setting.Single(idx));
|
||||
|
||||
if (option.Description.Length <= 0)
|
||||
|
|
@ -130,28 +139,29 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
/// </summary>
|
||||
private void DrawMultiGroup(IModGroup group, int groupIdx, Setting setting)
|
||||
{
|
||||
using var id = ImRaii.PushId(groupIdx);
|
||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
||||
var options = group.Options;
|
||||
using var id = ImUtf8.PushId(groupIdx);
|
||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
||||
var options = group.Options;
|
||||
DrawCollapseHandling(options, minWidth, DrawOptions);
|
||||
|
||||
Widget.EndFramedGroup();
|
||||
var label = $"##multi{groupIdx}";
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
ImGui.OpenPopup($"##multi{groupIdx}");
|
||||
ImUtf8.OpenPopup($"##multi{groupIdx}");
|
||||
|
||||
DrawMultiPopup(group, groupIdx, label);
|
||||
return;
|
||||
|
||||
void DrawOptions()
|
||||
{
|
||||
using var disabled = ImRaii.Disabled(_locked);
|
||||
for (var idx = 0; idx < options.Count; ++idx)
|
||||
{
|
||||
using var i = ImRaii.PushId(idx);
|
||||
var option = options[idx];
|
||||
var enabled = setting.HasFlag(idx);
|
||||
using var i = ImUtf8.PushId(idx);
|
||||
var option = options[idx];
|
||||
var enabled = setting.HasFlag(idx);
|
||||
|
||||
if (ImGui.Checkbox(option.Name, ref enabled))
|
||||
if (ImUtf8.Checkbox(option.Name, ref enabled))
|
||||
SetModSetting(group, groupIdx, setting.SetBit(idx, enabled));
|
||||
|
||||
if (option.Description.Length > 0)
|
||||
|
|
@ -171,11 +181,12 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
return;
|
||||
|
||||
ImGui.TextUnformatted(group.Name);
|
||||
using var disabled = ImRaii.Disabled(_locked);
|
||||
ImGui.Separator();
|
||||
if (ImGui.Selectable("Enable All"))
|
||||
if (ImUtf8.Selectable("Enable All"u8))
|
||||
SetModSetting(group, groupIdx, Setting.AllBits(group.Options.Count));
|
||||
|
||||
if (ImGui.Selectable("Disable All"))
|
||||
if (ImUtf8.Selectable("Disable All"u8))
|
||||
SetModSetting(group, groupIdx, Setting.Zero);
|
||||
}
|
||||
|
||||
|
|
@ -187,11 +198,11 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
}
|
||||
else
|
||||
{
|
||||
var collapseId = ImGui.GetID("Collapse");
|
||||
var shown = ImGui.GetStateStorage().GetBool(collapseId, true);
|
||||
var collapseId = ImUtf8.GetId("Collapse");
|
||||
var shown = ImGui.GetStateStorage().GetBool(collapseId, true);
|
||||
var buttonTextShow = $"Show {options.Count} Options";
|
||||
var buttonTextHide = $"Hide {options.Count} Options";
|
||||
var buttonWidth = Math.Max(ImGui.CalcTextSize(buttonTextShow).X, ImGui.CalcTextSize(buttonTextHide).X)
|
||||
var buttonWidth = Math.Max(ImUtf8.CalcTextSize(buttonTextShow).X, ImUtf8.CalcTextSize(buttonTextHide).X)
|
||||
+ 2 * ImGui.GetStyle().FramePadding.X;
|
||||
minWidth = Math.Max(buttonWidth, minWidth);
|
||||
if (shown)
|
||||
|
|
@ -204,22 +215,22 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
}
|
||||
|
||||
|
||||
var width = Math.Max(ImGui.GetItemRectSize().X, minWidth);
|
||||
var width = Math.Max(ImGui.GetItemRectSize().X, minWidth);
|
||||
var endPos = ImGui.GetCursorPos();
|
||||
ImGui.SetCursorPos(pos);
|
||||
if (ImGui.Button(buttonTextHide, new Vector2(width, 0)))
|
||||
if (ImUtf8.Button(buttonTextHide, new Vector2(width, 0)))
|
||||
ImGui.GetStateStorage().SetBool(collapseId, !shown);
|
||||
|
||||
ImGui.SetCursorPos(endPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
var optionWidth = options.Max(o => ImGui.CalcTextSize(o.Name).X)
|
||||
var optionWidth = options.Max(o => ImUtf8.CalcTextSize(o.Name).X)
|
||||
+ ImGui.GetStyle().ItemInnerSpacing.X
|
||||
+ ImGui.GetFrameHeight()
|
||||
+ ImGui.GetStyle().FramePadding.X;
|
||||
var width = Math.Max(optionWidth, minWidth);
|
||||
if (ImGui.Button(buttonTextShow, new Vector2(width, 0)))
|
||||
if (ImUtf8.Button(buttonTextShow, new Vector2(width, 0)))
|
||||
ImGui.GetStateStorage().SetBool(collapseId, !shown);
|
||||
}
|
||||
}
|
||||
|
|
@ -228,6 +239,18 @@ public sealed class ModGroupDrawer(Configuration config, CollectionManager colle
|
|||
private ModCollection Current
|
||||
=> collectionManager.Active.Current;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void SetModSetting(IModGroup group, int groupIdx, Setting setting)
|
||||
=> collectionManager.Editor.SetModSetting(Current, group.Mod, groupIdx, setting);
|
||||
{
|
||||
if (_temporary)
|
||||
{
|
||||
_tempSettings!.ForceInherit = false;
|
||||
_tempSettings!.Settings[groupIdx] = setting;
|
||||
collectionManager.Editor.SetTemporarySettings(Current, group.Mod, _tempSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionManager.Editor.SetModSetting(Current, group.Mod, groupIdx, setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
SubscribeRightClickFolder(f => SetQuickMove(f, 1, _config.QuickMoveFolder2, s => { _config.QuickMoveFolder2 = s; _config.Save(); }), 120);
|
||||
SubscribeRightClickFolder(f => SetQuickMove(f, 2, _config.QuickMoveFolder3, s => { _config.QuickMoveFolder3 = s; _config.Save(); }), 130);
|
||||
SubscribeRightClickLeaf(ToggleLeafFavorite);
|
||||
SubscribeRightClickLeaf(RemoveTemporarySettings);
|
||||
SubscribeRightClickLeaf(DisableTemporarily);
|
||||
SubscribeRightClickLeaf(l => QuickMove(l, _config.QuickMoveFolder1, _config.QuickMoveFolder2, _config.QuickMoveFolder3));
|
||||
SubscribeRightClickMain(ClearDefaultImportFolder, 100);
|
||||
SubscribeRightClickMain(() => ClearQuickMove(0, _config.QuickMoveFolder1, () => {_config.QuickMoveFolder1 = string.Empty; _config.Save();}), 110);
|
||||
|
|
@ -194,14 +196,14 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
protected override void DrawLeafName(FileSystem<Mod>.Leaf leaf, in ModState state, bool selected)
|
||||
{
|
||||
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Value())
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Tinted(state.Tint))
|
||||
.Push(ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite);
|
||||
using var id = ImRaii.PushId(leaf.Value.Index);
|
||||
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
|
||||
{
|
||||
_modManager.SetKnown(leaf.Value);
|
||||
var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index];
|
||||
var (setting, collection) = _collectionManager.Active.Current.GetActualSettings(leaf.Value.Index);
|
||||
if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive())
|
||||
{
|
||||
_collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, true);
|
||||
|
|
@ -264,6 +266,23 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
_modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite);
|
||||
}
|
||||
|
||||
private void RemoveTemporarySettings(FileSystem<Mod>.Leaf mod)
|
||||
{
|
||||
var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index);
|
||||
if (tempSettings is { Lock: <= 0 })
|
||||
if (ImUtf8.MenuItem("Remove Temporary Settings"))
|
||||
_collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, null);
|
||||
}
|
||||
|
||||
private void DisableTemporarily(FileSystem<Mod>.Leaf mod)
|
||||
{
|
||||
var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index);
|
||||
if (tempSettings is not { Lock: > 0 })
|
||||
if (ImUtf8.MenuItem("Disable Temporarily"))
|
||||
_collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value,
|
||||
TemporaryModSettings.DefaultSettings(mod.Value, "User Context-Menu"));
|
||||
}
|
||||
|
||||
private void SetDefaultImportFolder(ModFileSystem.Folder folder)
|
||||
{
|
||||
if (!ImGui.MenuItem("Set As Default Import Folder"))
|
||||
|
|
@ -392,8 +411,6 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
ImGuiUtil.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection.");
|
||||
ImGuiUtil.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection.");
|
||||
ImGuiUtil.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections.");
|
||||
ImGuiUtil.BulletTextColored(ColorId.NewMod.Value(),
|
||||
"newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded.");
|
||||
ImGuiUtil.BulletTextColored(ColorId.HandledConflictMod.Value(),
|
||||
"enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved).");
|
||||
ImGuiUtil.BulletTextColored(ColorId.ConflictingMod.Value(),
|
||||
|
|
@ -501,6 +518,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
public struct ModState
|
||||
{
|
||||
public ColorId Color;
|
||||
public ColorId Tint;
|
||||
public ModPriority Priority;
|
||||
}
|
||||
|
||||
|
|
@ -571,24 +589,31 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
=> !_filter.IsVisible(leaf);
|
||||
|
||||
/// <summary> Only get the text color for a mod if no filters are set. </summary>
|
||||
private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection)
|
||||
private (ColorId Color, ColorId Tint) GetTextColor(Mod mod, ModSettings? settings, ModCollection collection)
|
||||
{
|
||||
if (_modManager.IsNew(mod))
|
||||
return ColorId.NewMod;
|
||||
var tint = settings.IsTemporary()
|
||||
? ColorId.TemporaryModSettingsTint
|
||||
: _modManager.IsNew(mod)
|
||||
? ColorId.NewModTint
|
||||
: ColorId.NoTint;
|
||||
if (settings.IsTemporary())
|
||||
tint = ColorId.TemporaryModSettingsTint;
|
||||
|
||||
if (settings == null)
|
||||
return ColorId.UndefinedMod;
|
||||
return (ColorId.UndefinedMod, tint);
|
||||
|
||||
if (!settings.Enabled)
|
||||
return collection != _collectionManager.Active.Current ? ColorId.InheritedDisabledMod : ColorId.DisabledMod;
|
||||
return (collection != _collectionManager.Active.Current
|
||||
? ColorId.InheritedDisabledMod
|
||||
: ColorId.DisabledMod, tint);
|
||||
|
||||
var conflicts = _collectionManager.Active.Current.Conflicts(mod);
|
||||
if (conflicts.Count == 0)
|
||||
return collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod;
|
||||
return (collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod, tint);
|
||||
|
||||
return conflicts.Any(c => !c.Solved)
|
||||
return (conflicts.Any(c => !c.Solved)
|
||||
? ColorId.ConflictingMod
|
||||
: ColorId.HandledConflictMod;
|
||||
: ColorId.HandledConflictMod, tint);
|
||||
}
|
||||
|
||||
private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state)
|
||||
|
|
@ -620,6 +645,15 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
return true;
|
||||
}
|
||||
|
||||
// isNew color takes precedence before other colors.
|
||||
if (settings.IsTemporary())
|
||||
state.Tint = ColorId.TemporaryModSettingsTint;
|
||||
else if (isNew)
|
||||
state.Tint = ColorId.NewModTint;
|
||||
else
|
||||
state.Tint = ColorId.NoTint;
|
||||
|
||||
|
||||
// Handle settings.
|
||||
if (settings == null)
|
||||
{
|
||||
|
|
@ -631,7 +665,9 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
}
|
||||
else if (!settings.Enabled)
|
||||
{
|
||||
state.Color = collection == _collectionManager.Active.Current ? ColorId.DisabledMod : ColorId.InheritedDisabledMod;
|
||||
state.Color = collection != _collectionManager.Active.Current
|
||||
? ColorId.InheritedDisabledMod
|
||||
: ColorId.DisabledMod;
|
||||
if (!_stateFilter.HasFlag(ModFilter.Disabled)
|
||||
|| !_stateFilter.HasFlag(ModFilter.NoConflict))
|
||||
return true;
|
||||
|
|
@ -666,9 +702,6 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
}
|
||||
}
|
||||
|
||||
// isNew color takes precedence before other colors.
|
||||
if (isNew)
|
||||
state.Color = ColorId.NewMod;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -677,7 +710,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
private bool ApplyFiltersAndState(ModFileSystem.Leaf leaf, out ModState state)
|
||||
{
|
||||
var mod = leaf.Value;
|
||||
var (settings, collection) = _collectionManager.Active.Current[mod.Index];
|
||||
var (settings, collection) = _collectionManager.Active.Current.GetActualSettings(mod.Index);
|
||||
|
||||
state = new ModState
|
||||
{
|
||||
|
|
@ -690,7 +723,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
if (_stateFilter != ModFilterExtensions.UnfilteredStateMods)
|
||||
return CheckStateFilters(mod, settings, collection, ref state);
|
||||
|
||||
state.Color = GetTextColor(mod, settings, collection);
|
||||
(state.Color, state.Tint) = GetTextColor(mod, settings, collection);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
|
|||
foreach (var ((collection, parent, color, state), idx) in _cache.WithIndex())
|
||||
{
|
||||
using var id = ImUtf8.PushId(idx);
|
||||
ImUtf8.DrawTableColumn(collection.Name);
|
||||
ImUtf8.DrawTableColumn(collection.Identity.Name);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.Text(ToText(state), color);
|
||||
|
|
@ -65,7 +65,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
|
|||
{
|
||||
if (context)
|
||||
{
|
||||
ImUtf8.Text(collection.Name);
|
||||
ImUtf8.Text(collection.Identity.Name);
|
||||
ImGui.Separator();
|
||||
using (ImRaii.Disabled(state is ModState.Enabled && parent == collection))
|
||||
{
|
||||
|
|
@ -95,7 +95,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
|
|||
}
|
||||
}
|
||||
|
||||
ImUtf8.DrawTableColumn(parent == collection ? string.Empty : parent.Name);
|
||||
ImUtf8.DrawTableColumn(parent == collection ? string.Empty : parent.Identity.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
|
|||
var inheritedCount = 0;
|
||||
foreach (var collection in manager.Storage)
|
||||
{
|
||||
var (settings, parent) = collection[mod.Index];
|
||||
var (settings, parent) = collection.GetInheritedSettings(mod.Index);
|
||||
var (color, text) = settings == null
|
||||
? (undefined, ModState.Unconfigured)
|
||||
: settings.Enabled
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
|
|||
if (conflicts.Mod2.Index < 0)
|
||||
return conflicts.Mod2.Priority;
|
||||
|
||||
return collectionManager.Active.Current[conflicts.Mod2.Index].Settings?.Priority ?? ModPriority.Default;
|
||||
return collectionManager.Active.Current.GetActualSettings(conflicts.Mod2.Index).Settings?.Priority ?? ModPriority.Default;
|
||||
}
|
||||
|
||||
public void DrawContent()
|
||||
|
|
@ -74,22 +74,27 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
|
|||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(selector.Selected!.Name);
|
||||
ImGui.TableNextColumn();
|
||||
var priority = collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value;
|
||||
ImGui.SetNextItemWidth(priorityWidth);
|
||||
if (ImGui.InputInt("##priority", ref priority, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
_currentPriority = priority;
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue)
|
||||
var actualSettings = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!;
|
||||
var priority = actualSettings.Priority.Value;
|
||||
// TODO
|
||||
using (ImRaii.Disabled(actualSettings is TemporaryModSettings))
|
||||
{
|
||||
if (_currentPriority != collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value)
|
||||
collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!,
|
||||
new ModPriority(_currentPriority.Value));
|
||||
ImGui.SetNextItemWidth(priorityWidth);
|
||||
if (ImGui.InputInt("##priority", ref priority, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
_currentPriority = priority;
|
||||
|
||||
_currentPriority = null;
|
||||
}
|
||||
else if (ImGui.IsItemDeactivated())
|
||||
{
|
||||
_currentPriority = null;
|
||||
if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue)
|
||||
{
|
||||
if (_currentPriority != actualSettings.Priority.Value)
|
||||
collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!,
|
||||
new ModPriority(_currentPriority.Value));
|
||||
|
||||
_currentPriority = null;
|
||||
}
|
||||
else if (ImGui.IsItemDeactivated())
|
||||
{
|
||||
_currentPriority = null;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -138,7 +143,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
|
|||
ImGui.TableNextColumn();
|
||||
var conflictPriority = DrawPriorityInput(conflict, priorityWidth);
|
||||
ImGui.SameLine();
|
||||
var selectedPriority = collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value;
|
||||
var selectedPriority = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!.Priority.Value;
|
||||
DrawPriorityButtons(conflict.Mod2 as Mod, conflictPriority, selectedPriority, buttonSize);
|
||||
ImGui.TableNextColumn();
|
||||
DrawExpandButton(conflict.Mod2, expanded, buttonSize);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Widgets;
|
||||
|
|
@ -24,6 +23,8 @@ public class ModPanelSettingsTab(
|
|||
: ITab, IUiService
|
||||
{
|
||||
private bool _inherited;
|
||||
private bool _temporary;
|
||||
private bool _locked;
|
||||
private int? _currentPriority;
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -37,11 +38,14 @@ public class ModPanelSettingsTab(
|
|||
|
||||
public void DrawContent()
|
||||
{
|
||||
using var child = ImRaii.Child("##settings");
|
||||
using var child = ImUtf8.Child("##settings"u8, default);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
_inherited = selection.Collection != collectionManager.Active.Current;
|
||||
_inherited = selection.Collection != collectionManager.Active.Current;
|
||||
_temporary = selection.TemporarySettings != null;
|
||||
_locked = (selection.TemporarySettings?.Lock ?? 0) > 0;
|
||||
DrawTemporaryWarning();
|
||||
DrawInheritedWarning();
|
||||
UiHelpers.DefaultLineSpace();
|
||||
communicator.PreSettingsPanelDraw.Invoke(selection.Mod!.Identifier);
|
||||
|
|
@ -54,11 +58,27 @@ public class ModPanelSettingsTab(
|
|||
|
||||
communicator.PostEnabledDraw.Invoke(selection.Mod!.Identifier);
|
||||
|
||||
modGroupDrawer.Draw(selection.Mod!, selection.Settings);
|
||||
modGroupDrawer.Draw(selection.Mod!, selection.Settings, selection.TemporarySettings);
|
||||
UiHelpers.DefaultLineSpace();
|
||||
communicator.PostSettingsPanelDraw.Invoke(selection.Mod!.Identifier);
|
||||
}
|
||||
|
||||
/// <summary> Draw a big tinted bar if the current setting is temporary. </summary>
|
||||
private void DrawTemporaryWarning()
|
||||
{
|
||||
if (!_temporary)
|
||||
return;
|
||||
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGuiCol.Button.Tinted(ColorId.TemporaryModSettingsTint));
|
||||
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
|
||||
if (ImUtf8.ButtonEx($"These settings are temporary from {selection.TemporarySettings!.Source}{(_locked ? " and locked." : ".")}", width,
|
||||
_locked))
|
||||
collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, null);
|
||||
|
||||
ImUtf8.HoverTooltip("Changing settings in temporary settings will not save them across sessions.\n"u8
|
||||
+ "You can click this button to remove the temporary settings and return to your normal settings."u8);
|
||||
}
|
||||
|
||||
/// <summary> Draw a big red bar if the current setting is inherited. </summary>
|
||||
private void DrawInheritedWarning()
|
||||
{
|
||||
|
|
@ -67,22 +87,42 @@ public class ModPanelSettingsTab(
|
|||
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Button, Colors.PressEnterWarningBg);
|
||||
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
|
||||
if (ImGui.Button($"These settings are inherited from {selection.Collection.Name}.", width))
|
||||
collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, false);
|
||||
if (ImUtf8.ButtonEx($"These settings are inherited from {selection.Collection.Identity.Name}.", width, _locked))
|
||||
{
|
||||
if (_temporary)
|
||||
{
|
||||
selection.TemporarySettings!.ForceInherit = false;
|
||||
collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, selection.TemporarySettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, false);
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip("You can click this button to copy the current settings to the current selection.\n"
|
||||
+ "You can also just change any setting, which will copy the settings with the single setting changed to the current selection.");
|
||||
ImUtf8.HoverTooltip("You can click this button to copy the current settings to the current selection.\n"u8
|
||||
+ "You can also just change any setting, which will copy the settings with the single setting changed to the current selection."u8);
|
||||
}
|
||||
|
||||
/// <summary> Draw a checkbox for the enabled status of the mod. </summary>
|
||||
private void DrawEnabledInput()
|
||||
{
|
||||
var enabled = selection.Settings.Enabled;
|
||||
if (!ImGui.Checkbox("Enabled", ref enabled))
|
||||
var enabled = selection.Settings.Enabled;
|
||||
using var disabled = ImRaii.Disabled(_locked);
|
||||
if (!ImUtf8.Checkbox("Enabled"u8, ref enabled))
|
||||
return;
|
||||
|
||||
modManager.SetKnown(selection.Mod!);
|
||||
collectionManager.Editor.SetModState(collectionManager.Active.Current, selection.Mod!, enabled);
|
||||
if (_temporary)
|
||||
{
|
||||
selection.TemporarySettings!.ForceInherit = false;
|
||||
selection.TemporarySettings!.Enabled = enabled;
|
||||
collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, selection.TemporarySettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionManager.Editor.SetModState(collectionManager.Active.Current, selection.Mod!, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -91,45 +131,66 @@ public class ModPanelSettingsTab(
|
|||
/// </summary>
|
||||
private void DrawPriorityInput()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
using var group = ImUtf8.Group();
|
||||
var settings = selection.Settings;
|
||||
var priority = _currentPriority ?? settings.Priority.Value;
|
||||
ImGui.SetNextItemWidth(50 * UiHelpers.Scale);
|
||||
if (ImGui.InputInt("##Priority", ref priority, 0, 0))
|
||||
using var disabled = ImRaii.Disabled(_locked);
|
||||
if (ImUtf8.InputScalar("##Priority"u8, ref priority))
|
||||
_currentPriority = priority;
|
||||
if (new ModPriority(priority).IsHidden)
|
||||
ImUtf8.HoverTooltip($"This priority is special-cased to hide this mod in conflict tabs ({ModPriority.HiddenMin}, {ModPriority.HiddenMax}).");
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled,
|
||||
$"This priority is special-cased to hide this mod in conflict tabs ({ModPriority.HiddenMin}, {ModPriority.HiddenMax}).");
|
||||
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue)
|
||||
{
|
||||
if (_currentPriority != settings.Priority.Value)
|
||||
collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!,
|
||||
new ModPriority(_currentPriority.Value));
|
||||
{
|
||||
if (_temporary)
|
||||
{
|
||||
selection.TemporarySettings!.ForceInherit = false;
|
||||
selection.TemporarySettings!.Priority = new ModPriority(_currentPriority.Value);
|
||||
collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!,
|
||||
selection.TemporarySettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!,
|
||||
new ModPriority(_currentPriority.Value));
|
||||
}
|
||||
}
|
||||
|
||||
_currentPriority = null;
|
||||
}
|
||||
|
||||
ImGuiUtil.LabeledHelpMarker("Priority", "Mods with a higher number here take precedence before Mods with a lower number.\n"
|
||||
+ "That means, if Mod A should overwrite changes from Mod B, Mod A should have a higher priority number than Mod B.");
|
||||
ImUtf8.LabeledHelpMarker("Priority"u8, "Mods with a higher number here take precedence before Mods with a lower number.\n"u8
|
||||
+ "That means, if Mod A should overwrite changes from Mod B, Mod A should have a higher priority number than Mod B."u8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a button to remove the current settings and inherit them instead
|
||||
/// on the top-right corner of the window/tab.
|
||||
/// in the top-right corner of the window/tab.
|
||||
/// </summary>
|
||||
private void DrawRemoveSettings()
|
||||
{
|
||||
const string text = "Inherit Settings";
|
||||
if (_inherited || selection.Settings == ModSettings.Empty)
|
||||
return;
|
||||
|
||||
var scroll = ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0;
|
||||
ImGui.SameLine(ImGui.GetWindowWidth() - ImGui.CalcTextSize(text).X - ImGui.GetStyle().FramePadding.X * 2 - scroll);
|
||||
if (ImGui.Button(text))
|
||||
collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, true);
|
||||
ImGui.SameLine(ImGui.GetWindowWidth() - ImUtf8.CalcTextSize("Inherit Settings"u8).X - ImGui.GetStyle().FramePadding.X * 2 - scroll);
|
||||
if (!ImUtf8.ButtonEx("Inherit Settings"u8, "Remove current settings from this collection so that it can inherit them.\n"u8
|
||||
+ "If no inherited collection has settings for this mod, it will be disabled."u8, default, _locked))
|
||||
return;
|
||||
|
||||
ImGuiUtil.HoverTooltip("Remove current settings from this collection so that it can inherit them.\n"
|
||||
+ "If no inherited collection has settings for this mod, it will be disabled.");
|
||||
if (_temporary)
|
||||
{
|
||||
selection.TemporarySettings!.ForceInherit = true;
|
||||
collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, selection.TemporarySettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService
|
|||
{
|
||||
var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name;
|
||||
Penumbra.Log.Information(
|
||||
$"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) ");
|
||||
$"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.Identity.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ internal sealed class ResourceWatcherTable : Table<Record>
|
|||
=> 80 * UiHelpers.Scale;
|
||||
|
||||
public override string ToName(Record item)
|
||||
=> item.Collection?.Name ?? string.Empty;
|
||||
=> (item.Collection != null ? item.Collection.Identity.Name : null) ?? string.Empty;
|
||||
}
|
||||
|
||||
private sealed class ObjectColumn : ColumnString<Record>
|
||||
|
|
|
|||
|
|
@ -204,11 +204,45 @@ public class DebugTab : Window, ITab, IUiService
|
|||
if (collection.HasCache)
|
||||
{
|
||||
using var color = PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
||||
using var node = TreeNode($"{collection.Name} (Change Counter {collection.ChangeCounter})###{collection.Name}");
|
||||
using var node =
|
||||
TreeNode($"{collection.Identity.Name} (Change Counter {collection.Counters.Change})###{collection.Identity.Name}");
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
color.Pop();
|
||||
using (var inheritanceNode = ImUtf8.TreeNode("Inheritance"u8))
|
||||
{
|
||||
if (inheritanceNode)
|
||||
{
|
||||
using var table = ImUtf8.Table("table"u8, 3,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV);
|
||||
if (table)
|
||||
{
|
||||
var max = Math.Max(
|
||||
Math.Max(collection.Inheritance.DirectlyInheritedBy.Count, collection.Inheritance.DirectlyInheritsFrom.Count),
|
||||
collection.Inheritance.FlatHierarchy.Count);
|
||||
for (var i = 0; i < max; ++i)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
if (i < collection.Inheritance.DirectlyInheritsFrom.Count)
|
||||
ImUtf8.Text(collection.Inheritance.DirectlyInheritsFrom[i].Identity.Name);
|
||||
else
|
||||
ImGui.Dummy(new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetTextLineHeight()));
|
||||
ImGui.TableNextColumn();
|
||||
if (i < collection.Inheritance.DirectlyInheritedBy.Count)
|
||||
ImUtf8.Text(collection.Inheritance.DirectlyInheritedBy[i].Identity.Name);
|
||||
else
|
||||
ImGui.Dummy(new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetTextLineHeight()));
|
||||
ImGui.TableNextColumn();
|
||||
if (i < collection.Inheritance.FlatHierarchy.Count)
|
||||
ImUtf8.Text(collection.Inheritance.FlatHierarchy[i].Identity.Name);
|
||||
else
|
||||
ImGui.Dummy(new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetTextLineHeight()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var resourceNode = ImUtf8.TreeNode("Custom Resources"u8))
|
||||
{
|
||||
if (resourceNode)
|
||||
|
|
@ -239,7 +273,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
else
|
||||
{
|
||||
using var color = PushColor(ImGuiCol.Text, ColorId.UndefinedMod.Value());
|
||||
TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})",
|
||||
TreeNode($"{collection.Identity.Name} (Change Counter {collection.Counters.Change})",
|
||||
ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -265,9 +299,9 @@ public class DebugTab : Window, ITab, IUiService
|
|||
{
|
||||
PrintValue("Penumbra Version", $"{_validityChecker.Version} {DebugVersionString}");
|
||||
PrintValue("Git Commit Hash", _validityChecker.CommitHash);
|
||||
PrintValue(TutorialService.SelectedCollection, _collectionManager.Active.Current.Name);
|
||||
PrintValue(TutorialService.SelectedCollection, _collectionManager.Active.Current.Identity.Name);
|
||||
PrintValue(" has Cache", _collectionManager.Active.Current.HasCache.ToString());
|
||||
PrintValue(TutorialService.DefaultCollection, _collectionManager.Active.Default.Name);
|
||||
PrintValue(TutorialService.DefaultCollection, _collectionManager.Active.Default.Identity.Name);
|
||||
PrintValue(" has Cache", _collectionManager.Active.Default.HasCache.ToString());
|
||||
PrintValue("Mod Manager BasePath", _modManager.BasePath.Name);
|
||||
PrintValue("Mod Manager BasePath-Full", _modManager.BasePath.FullName);
|
||||
|
|
@ -518,7 +552,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
return;
|
||||
|
||||
ImGui.TextUnformatted(
|
||||
$"Last Game Object: 0x{_collectionResolver.IdentifyLastGameObjectCollection(true).AssociatedGameObject:X} ({_collectionResolver.IdentifyLastGameObjectCollection(true).ModCollection.Name})");
|
||||
$"Last Game Object: 0x{_collectionResolver.IdentifyLastGameObjectCollection(true).AssociatedGameObject:X} ({_collectionResolver.IdentifyLastGameObjectCollection(true).ModCollection.Identity.Name})");
|
||||
using (var drawTree = TreeNode("Draw Object to Object"))
|
||||
{
|
||||
if (drawTree)
|
||||
|
|
@ -545,7 +579,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
ImGui.TextUnformatted(name);
|
||||
ImGui.TableNextColumn();
|
||||
var collection = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||
ImGui.TextUnformatted(collection.ModCollection.Name);
|
||||
ImGui.TextUnformatted(collection.ModCollection.Identity.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -561,7 +595,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{data.AssociatedGameObject:X}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.ModCollection.Name);
|
||||
ImGui.TextUnformatted(data.ModCollection.Identity.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -574,12 +608,12 @@ public class DebugTab : Window, ITab, IUiService
|
|||
if (table)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn("Current Mtrl Data");
|
||||
ImGuiUtil.DrawTableColumn(_subfileHelper.MtrlData.ModCollection.Name);
|
||||
ImGuiUtil.DrawTableColumn(_subfileHelper.MtrlData.ModCollection.Identity.Name);
|
||||
ImGuiUtil.DrawTableColumn($"0x{_subfileHelper.MtrlData.AssociatedGameObject:X}");
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Current Avfx Data");
|
||||
ImGuiUtil.DrawTableColumn(_subfileHelper.AvfxData.ModCollection.Name);
|
||||
ImGuiUtil.DrawTableColumn(_subfileHelper.AvfxData.ModCollection.Identity.Name);
|
||||
ImGuiUtil.DrawTableColumn($"0x{_subfileHelper.AvfxData.AssociatedGameObject:X}");
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
|
|
@ -591,7 +625,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
foreach (var (resource, resolve) in _subfileHelper)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn($"0x{resource:X}");
|
||||
ImGuiUtil.DrawTableColumn(resolve.ModCollection.Name);
|
||||
ImGuiUtil.DrawTableColumn(resolve.ModCollection.Identity.Name);
|
||||
ImGuiUtil.DrawTableColumn($"0x{resolve.AssociatedGameObject:X}");
|
||||
ImGuiUtil.DrawTableColumn($"{((ResourceHandle*)resource)->FileName()}");
|
||||
}
|
||||
|
|
@ -611,7 +645,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
ImGuiUtil.DrawTableColumn($"{((GameObject*)address)->ObjectIndex}");
|
||||
ImGuiUtil.DrawTableColumn($"0x{address:X}");
|
||||
ImGuiUtil.DrawTableColumn(identifier.ToString());
|
||||
ImGuiUtil.DrawTableColumn(collection.Name);
|
||||
ImGuiUtil.DrawTableColumn(collection.Identity.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -682,13 +716,11 @@ public class DebugTab : Window, ITab, IUiService
|
|||
{
|
||||
using var table = Table("###TmbTable", 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (table)
|
||||
{
|
||||
foreach (var (id, name) in _schedulerService.ListedTmbs.OrderBy(kvp => kvp.Key))
|
||||
{
|
||||
ImUtf8.DrawTableColumn($"{id:D6}");
|
||||
ImUtf8.DrawTableColumn(name.Span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -759,19 +791,25 @@ public class DebugTab : Window, ITab, IUiService
|
|||
ImGuiClip.DrawEndDummy(dummy, ImGui.GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
private string _tmbKeyFilter = string.Empty;
|
||||
private CiByteString _tmbKeyFilterU8 = CiByteString.Empty;
|
||||
|
||||
private void DrawActionTmbs()
|
||||
{
|
||||
using var mainTree = TreeNode("Action TMBs");
|
||||
if (!mainTree)
|
||||
return;
|
||||
|
||||
if (ImGui.InputText("Key", ref _tmbKeyFilter, 256))
|
||||
_tmbKeyFilterU8 = CiByteString.FromString(_tmbKeyFilter, out var r, MetaDataComputation.All) ? r : CiByteString.Empty;
|
||||
using var table = Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
||||
new Vector2(-1, 12 * ImGui.GetTextLineHeightWithSpacing()));
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
|
||||
var dummy = ImGuiClip.ClippedDraw(_schedulerService.ActionTmbs.OrderBy(r => r.Value), skips,
|
||||
var dummy = ImGuiClip.FilteredClippedDraw(_schedulerService.ActionTmbs.OrderBy(r => r.Value), skips,
|
||||
kvp => kvp.Key.Contains(_tmbKeyFilterU8),
|
||||
p =>
|
||||
{
|
||||
ImUtf8.DrawTableColumn($"{p.Value}");
|
||||
|
|
|
|||
|
|
@ -77,12 +77,12 @@ public class ModsTab(
|
|||
{
|
||||
Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}");
|
||||
Penumbra.Log.Error($"{modManager.Count} Mods\n"
|
||||
+ $"{_activeCollections.Current.AnonymizedName} Current Collection\n"
|
||||
+ $"{_activeCollections.Current.Identity.AnonymizedName} Current Collection\n"
|
||||
+ $"{_activeCollections.Current.Settings.Count} Settings\n"
|
||||
+ $"{selector.SortMode.Name} Sort Mode\n"
|
||||
+ $"{selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n"
|
||||
+ $"{selector.Selected?.Name ?? "NULL"} Selected Mod\n"
|
||||
+ $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n");
|
||||
+ $"{string.Join(", ", _activeCollections.Current.Inheritance.DirectlyInheritsFrom.Select(c => c.Identity.AnonymizedName))} Inheritances\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,14 +83,14 @@ public class TutorialService : IUiService
|
|||
+ "Go here after setting up your root folder to continue the tutorial!")
|
||||
.Register("Initial Setup, Step 4: Managing Collections",
|
||||
"On the left, we have the collection selector. Here, we can create new collections - either empty ones or by duplicating existing ones - and delete any collections not needed anymore.\n"
|
||||
+ $"There will always be one collection called {ModCollection.DefaultCollectionName} that can not be deleted.")
|
||||
+ $"There will always be one collection called {ModCollectionIdentity.DefaultCollectionName} that can not be deleted.")
|
||||
.Register($"Initial Setup, Step 5: {SelectedCollection}",
|
||||
$"The {SelectedCollection} is the one we highlighted in the selector. It is the collection we are currently looking at and editing.\nAny changes we make in our mod settings later in the next tab will edit this collection.\n"
|
||||
+ $"We should already have the collection named {ModCollection.DefaultCollectionName} selected, and for our simple setup, we do not need to do anything here.\n\n")
|
||||
+ $"We should already have the collection named {ModCollectionIdentity.DefaultCollectionName} selected, and for our simple setup, we do not need to do anything here.\n\n")
|
||||
.Register("Initial Setup, Step 6: Simple Assignments",
|
||||
"Aside from being a collection of settings, we can also assign collections to different functions. This is used to make different mods apply to different characters.\n"
|
||||
+ "The Simple Assignments panel shows you the possible assignments that are enough for most people along with descriptions.\n"
|
||||
+ $"If you are just starting, you can see that the {ModCollection.DefaultCollectionName} is currently assigned to {CollectionType.Default.ToName()} and {CollectionType.Interface.ToName()}.\n"
|
||||
+ $"If you are just starting, you can see that the {ModCollectionIdentity.DefaultCollectionName} is currently assigned to {CollectionType.Default.ToName()} and {CollectionType.Interface.ToName()}.\n"
|
||||
+ "You can also assign 'Use No Mods' instead of a collection by clicking on the function buttons.")
|
||||
.Register("Individual Assignments",
|
||||
"In the Individual Assignments panel, you can manually create assignments for very specific characters or monsters, not just yourself or ones you can currently target.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue